COM App 范例 (ActiveQt)

The COM App example shows how to use ActiveQt to develop a Qt application that can be automated via COM. Different QObject based classes are exposed as COM objects that communicate with the GUI of the running Qt application. The APIs of those COM objects has been designed to resemble the APIs of standard COM applications; i.e. those from Microsoft Office.

class Application : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{b50a71db-c4a7-4551-8d14-49983566afee}")
    Q_CLASSINFO("InterfaceID", "{4a427759-16ef-4ed8-be79-59ffe5789042}")
    Q_CLASSINFO("RegisterObject", "yes")
    Q_PROPERTY(DocumentList* documents READ documents)
    Q_PROPERTY(QString id READ id)
    Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
public:
    explicit Application(QObject *parent = nullptr);
    DocumentList *documents() const;
    QString id() const { return objectName(); }
    void setVisible(bool on);
    bool isVisible() const;
    QTabWidget *window() const { return m_ui.data(); }
public slots:
    void quit();
private:
    QScopedPointer <DocumentList> m_docs;
    QScopedPointer <QTabWidget> m_ui;
};
					

第 1 个类 应用程序 表示应用程序对象。它暴露只读特性 documents and id 以访问文档列表和标识符。读/写特性 visible 控制是否 QTabWidget 基应用程序的用户界面应该可见,和槽 quit() 终止应用程序。

The RegisterObject 属性的设置是为确保在 COM ROT (运行对象表) 中注册此类的实例 - 这允许 COM 客户端连接到已实例化的 COM 对象。

class DocumentList : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{496b761d-924b-4554-a18a-8f3704d2a9a6}")
    Q_CLASSINFO("InterfaceID", "{6c9e30e8-3ff6-4e6a-9edc-d219d074a148}")
    Q_PROPERTY(Application* application READ application)
    Q_PROPERTY(int count READ count)
public:
    explicit DocumentList(Application *application);
    int count() const;
    Application *application() const;
public slots:
    Document *addDocument();
    Document *item(int index) const;
private:
    QVector<Document *> m_list;
};
					

The DocumentList 类存储文档的列表。它提供读取文档数、按索引访问各文档及创建新文档的 API。 application 特性返回根对象。

class Document : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("ClassID", "{2b5775cd-72c2-43da-bc3b-b0e8d1e1c4f7}")
    Q_CLASSINFO("InterfaceID", "{2ce1761e-07a3-415c-bd11-0eab2c7283de}")
    Q_PROPERTY(Application *application READ application)
    Q_PROPERTY(QString title READ title WRITE setTitle)
public:
    explicit Document(DocumentList *list);
    virtual ~Document();
    Application *application() const;
    QString title() const;
    void setTitle(const QString &title);
private:
    QScopedPointer <QWidget> m_page;
};
					

The Document 类表示最终应用程序文档。各文档由应用程序选项卡 Widget 中的页面表示,且拥有的标题透过文档 API 可读写。 application 特性再次返回根对象。

Document::Document(DocumentList *list)
: QObject(list)
{
    QTabWidget *tabs = list->application()->window();
    m_page.reset(new QWidget(tabs));
    m_page->setWindowTitle(tr("Unnamed"));
    tabs->addTab(m_page.data(), m_page->windowTitle());
    m_page->show();
}
Document::~Document() = default;
Application *Document::application() const
{
    return qobject_cast<DocumentList *>(parent())->application();
}
QString Document::title() const
{
    return m_page->windowTitle();
}
void Document::setTitle(const QString &t)
{
    m_page->setWindowTitle(t);
    QTabWidget *tabs = application()->window();
    int index = tabs->indexOf(m_page.data());
    tabs->setTabText(index, m_page->windowTitle());
}
					

实现为 Document 类为选项卡 Widget 创建新页面,并将该页面标题用于 title 特性。会删除页面当删除文档时。

DocumentList::DocumentList(Application *application)
: QObject(application)
{
}
Application *DocumentList::application() const
{
    return qobject_cast<Application *>(parent());
}
int DocumentList::count() const
{
    return m_list.count();
}
Document *DocumentList::item(int index) const
{
    return m_list.value(index, nullptr);
}
Document *DocumentList::addDocument()
{
    Document *document = new Document(this);
    m_list.append(document);
    return document;
}
					

The DocumentList 实现很简单。

Application::Application(QObject *parent)
: QObject(parent),
  m_ui(new QTabWidget),
  m_docs(new DocumentList(this))
{
    setObjectName(QStringLiteral("From QAxFactory"));
}
DocumentList *Application::documents() const
{
    return m_docs.data();
}
void Application::setVisible(bool on)
{
    m_ui->setVisible(on);
}
bool Application::isVisible() const
{
    return m_ui->isVisible();
}
void Application::quit()
{
    m_docs.reset();
    m_ui.reset();
    QTimer::singleShot(0 /*ms*/, qApp, &QCoreApplication::quit);
}
#include "main.moc"
					

The 应用程序 类在构造函数中初始化用户界面,并展示和隐藏它在实现 setVisible() 。对象名称 (可访问透过 id 特性) 被设为 "From QAxFactory " 以指示此 COM 对象已由 COM 所创建。注意,没有析构函数会删除 QTabWidget - 代替履行这的是在 quit() 槽,先于调用 quit() 透过单发计时器,确保完成 COM 槽的调用是必要的。

QAXFACTORY_BEGIN("{edd3e836-f537-4c6f-be7d-6014c155cc7a}", "{b7da3de8-83bb-4bbe-9ab7-99a05819e201}")
   QAXCLASS(Application)
   QAXTYPE(Document)
   QAXTYPE(DocumentList)
QAXFACTORY_END()
					

类的导出是从服务器使用 QAxFactory 宏。仅 应用程序 对象可以从外部实例化 - 才可以使用其它 API 后于访问各个对象纵观 应用程序 API.

int main(int argc, char *argv[])
{
    QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);
    app.setQuitOnLastWindowClosed(false);
    // started by COM - don't do anything
    if (QAxFactory::isServer())
        return app.exec();
    // started by user
    Application appobject;
    appobject.setObjectName(QStringLiteral("From Application"));
    QAxFactory::startServer();
    QAxFactory::registerActiveObject(&appobject);
    appobject.window()->setMinimumSize(300, 100);
    appobject.setVisible(true);
    QObject::connect(&app, &QGuiApplication::lastWindowClosed, &appobject, &Application::quit);
    return app.exec();
}
					

main() 入口点函数创建 QApplication ,且仅仅进入事件循环若应用程序已由 COM 所启动。若应用程序已由用户所启动,则 应用程序 对象被创建且对象名称被设为 From Application。然后启动 COM 服务器,并采用 COM 注册应用程序对象。现在,透过特定客户端 API 可访问 COM 客户端。

应用程序的退出是明确控制的 - 若 COM 启动了应用程序,则客户端代码必须调用 quit();若用户启动了应用程序,则终止应用程序当关闭最后窗口时。

最后,使用户界面可见,并启动事件循环。

简单的 Visual Basic 应用程序现在可以访问此 Qt 应用程序。以 VB,启动新 Standard Exe 工程并向 comappLib 类型库添加工程引用。创建具有 DocumentList 列表框、DocumentsCount 静态标签及 NewDocument 命令按钮的表单。最后,为表单实现像这样的代码:

Private Application As comappLib.Application
Private MyApp As Boolean
Private Sub UpdateList()
    DocumentList.Clear
    DocumentsCount.Caption = Application.documents.Count
    For Index = 0 To Application.documents.Count - 1
       DocumentList.AddItem (Application.documents.Item(Index).Title)
    下一
End Sub
Private Sub Form_Load()
    On Error GoTo CreateNew
    Set Application = GetObject(, "comapp.Application")
    MyApp = False
    GoTo Initialized
CreateNew:
    On Error GoTo InitializeFailed
    Set Application = New Application
    Application.Visible = True
    MyApp = True
Initialized:
    Caption = Application.id
    UpdateList
InitializeFailed:
End Sub
Private Sub Form_Unload(Cancel As Integer)
    If MyApp Then
        Application.quit
    End If
End Sub
Private Sub NewDocument_Click()
    Application.documents.addDocument
    UpdateList
End Sub
					

要构建范例必须先构建 QAxServer 库。然后运行 qmake 和 make 工具在 examples\activeqt\comapp .

范例工程 @ code.qt.io