容器扩展示例
为Qt Widgets Designer 创建自定义多页面插件。
Container Extension 示例展示了如何使用QDesignerContainerExtension 类为Qt Widgets Designer 创建自定义多页面插件。
要提供可与Qt Widgets Designer 一起使用的自定义部件,我们需要提供一个独立的实现。在本示例中,我们使用了一个自定义多页面 widget,旨在显示容器扩展功能。
扩展是一个可以修改Qt Widgets Designer 行为的对象。通过QDesignerContainerExtension ,Qt Widgets Designer 可以管理和操作自定义多页面 widget,即添加和删除 widget 中的页面。
Qt Widgets Designer 中有四种可用的扩展类型:
- QDesignerMemberSheetExtension 提供了一种扩展,允许用户操作 widget 的成员函数,这些函数会在使用 的编辑信号和插槽模式配置连接时显示。Qt Widgets Designer
- QDesignerPropertySheetExtension 提供一个扩展,允许你操作部件的属性,显示在 的属性编辑器中。Qt Widgets Designer
- QDesignerTaskMenuExtension 提供了一个扩展功能,允许用户在 的任务菜单中添加自定义菜单条目。Qt Widgets Designer
- QDesignerContainerExtension 提供了一个扩展,允许你在 的多页面容器插件中添加(和删除)页面。Qt Widgets Designer
您可以按照本示例中的相同模式使用所有扩展,只需替换各自的扩展基类即可。更多信息,请参阅 Qt Widgets Designer C++ Classes.
容器扩展示例由四个类组成:
MultiPageWidget
是一个自定义容器部件,允许用户操作和填充其页面,并使用组合框在这些页面之间导航。MultiPageWidgetPlugin
将 类公开给 。MultiPageWidget
Qt Widgets DesignerMultiPageWidgetExtensionFactory
创建 对象。MultiPageWidgetContainerExtension
MultiPageWidgetContainerExtension
提供容器扩展名。
自定义 widget 插件的项目文件需要一些附加信息,以确保它们能在Qt Widgets Designer 中运行。例如,自定义 widget 插件依赖于随Qt Widgets Designer 提供的组件,这必须在我们使用的项目文件中指定。我们将首先查看插件的项目文件。
然后,我们将继续查看MultiPageWidgetPlugin
类,并查看MultiPageWidgetExtensionFactory
和MultiPageWidgetContainerExtension
类。最后,我们将快速查看MultiPageWidget
类的定义。
项目文件
CMake
项目文件需要说明要构建一个链接到Qt Widgets Designer 库的插件:
find_package(Qt6 REQUIRED COMPONENTS Core Designer Gui Widgets) qt_add_plugin(containerextension) target_link_libraries(containerextension PUBLIC Qt::Core Qt::Designer Qt::Gui Qt::Widgets )
下面的示例展示了如何添加 widget 的头文件和源文件:
target_sources(containerextension PRIVATE multipagewidget.cpp multipagewidget.h multipagewidgetcontainerextension.cpp multipagewidgetcontainerextension.h multipagewidgetextensionfactory.cpp multipagewidgetextensionfactory.h multipagewidgetplugin.cpp multipagewidgetplugin.h )
我们提供了插件接口的实现,以便Qt Widgets Designer 可以使用自定义 widget。在这个示例中,我们还提供了容器扩展接口和扩展工厂的实现。
必须确保插件安装在Qt Widgets Designer 可以搜索到的位置。为此,我们为项目指定了目标路径,并将其添加到要安装的项目列表中:
set(INSTALL_EXAMPLEDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer") install(TARGETS containerextension RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" )
容器扩展作为库创建。在安装项目(使用ninja install
或类似的安装程序)时,它将与其他Qt Widgets Designer 插件一起安装。
有关插件的更多信息,请参阅如何创建 Qt 插件文档。
qmake
下面的示例展示了如何将插件链接到Qt Widgets Designer 库:
TEMPLATE = lib CONFIG += plugin QT += widgets designer
下面的示例演示了如何添加 widget 的头文件和源文件:
HEADERS += multipagewidget.h \ multipagewidgetplugin.h \ multipagewidgetcontainerextension.h \ multipagewidgetextensionfactory.h SOURCES += multipagewidget.cpp \ multipagewidgetplugin.cpp \ multipagewidgetcontainerextension.cpp \ multipagewidgetextensionfactory.cpp OTHER_FILES += multipagewidget.json
下面的示例展示了如何将插件安装到Qt Widgets Designer 的插件路径:
target.path = $$[QT_INSTALL_PLUGINS]/designer INSTALLS += target
多页面小部件插件类定义
MultiPageWidgetPlugin
类将MultiPageWidget
类公开给Qt Widgets Designer 。该类的定义与自定义 Widget插件示例的插件类类似,后者有详细说明。类定义中与该自定义部件相关的部分是类名和几个私有槽:
#ifndef MULTIPAGEWIDGETPLUGIN_H #define MULTIPAGEWIDGETPLUGIN_H #include <QtUiPlugin/QDesignerCustomWidgetInterface> class QIcon; class QWidget; class MultiPageWidgetPlugin: public QObject, public QDesignerCustomWidgetInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget") Q_INTERFACES(QDesignerCustomWidgetInterface) public: explicit MultiPageWidgetPlugin(QObject *parent = nullptr); QString name() const override; QString group() const override; QString toolTip() const override; QString whatsThis() const override; QString includeFile() const override; QIcon icon() const override; bool isContainer() const override; QWidget *createWidget(QWidget *parent) override; bool isInitialized() const override; void initialize(QDesignerFormEditorInterface *formEditor) override; QString domXml() const override; private slots: void currentIndexChanged(int index); void pageTitleChanged(const QString &title); private: bool initialized = false; }; #endif
插件类为Qt Widgets Designer 提供了插件的基本信息,如类名和包含文件。此外,它还知道如何创建MultiPageWidget
widget 的实例。MultiPageWidgetPlugin
还定义了initialize() 函数,该函数在插件加载到Qt Widgets Designer 后被调用。该函数的QDesignerFormEditorInterface 参数为插件提供了通向所有Qt Widgets Designer API 的网关。
对于像我们这样的多页面 widget,我们还必须实现两个私有插槽:currentIndexChanged() 和 pageTitleChanged(),以便在用户查看其他页面或更改其中一个页面标题时更新Qt Widgets Designer 的属性编辑器。为了让每个页面都有自己的标题,我们选择使用QWidget::windowTitle 属性来存储页面标题(更多信息请参见containerextension/multipagewidget.cpp 中的MultiPageWidget 类实现)。请注意,如果不使用预定义属性作为占位符,目前无法向页面添加自定义属性(例如页面标题)。
MultiPageWidgetPlugin
类继承自QObject 和QDesignerCustomWidgetInterface 。在使用多重继承时,请务必牢记使用Q_INTERFACES() 宏确保元对象系统了解所有接口(即不继承Q_OBJECT 的类)。这样,Qt Widgets Designer 就可以使用qobject_cast() 查询支持的接口,而只需使用QObject 指针。
为确保 Qt XML 将 Widget 识别为插件,请通过添加Q_PLUGIN_METADATA()
宏导出 Widget 的相关信息:
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget")
有了这个宏,Qt Widgets Designer 就可以访问和构建自定义 widget。如果没有该宏,Qt Widgets Designer 就无法使用该 widget。
多页面小部件插件类的实现
MultiPageWidgetPlugin 类的大部分实现与自定义 Widget插件示例的插件类相同:
MultiPageWidgetPlugin::MultiPageWidgetPlugin(QObject *parent) : QObject(parent) { } QString MultiPageWidgetPlugin::name() const { return u"MultiPageWidget"_s; } QString MultiPageWidgetPlugin::group() const { return u"Display Widgets [Examples]"_s; } QString MultiPageWidgetPlugin::toolTip() const { return {}; } QString MultiPageWidgetPlugin::whatsThis() const { return {}; } QString MultiPageWidgetPlugin::includeFile() const { return u"multipagewidget.h"_s; } QIcon MultiPageWidgetPlugin::icon() const { return {}; } bool MultiPageWidgetPlugin::isInitialized() const { return initialized; }
其中一个不同的函数是 isContainer() 函数,该函数在本示例中返回 true,因为我们的自定义 Widget 将用作容器。
bool MultiPageWidgetPlugin::isContainer() const { return true; }
另一个不同的函数是创建自定义 widget 的函数:
QWidget *MultiPageWidgetPlugin::createWidget(QWidget *parent) { auto *widget = new MultiPageWidget(parent); connect(widget, &MultiPageWidget::currentIndexChanged, this, &MultiPageWidgetPlugin::currentIndexChanged); connect(widget, &MultiPageWidget::pageTitleChanged, this, &MultiPageWidgetPlugin::pageTitleChanged); return widget; }
除了创建和返回 widget 之外,我们还将自定义容器 widget 的 currentIndexChanged() 信号连接到插件的 currentIndexChanged() 槽,以确保每当用户查看其他页面时,Qt Widgets Designer 的属性编辑器都会更新。我们还将 widget 的 pageTitleChanged() 信号连接到插件的 pageTitleChanged() 插槽。
每当我们自定义 widget 的 currentIndexChanged()信号发出时,即每当用户查看另一个页面时,currentIndexChanged() 槽就会被调用:
void MultiPageWidgetPlugin::currentIndexChanged(int index) { Q_UNUSED(index); auto *widget = qobject_cast<MultiPageWidget*>(sender());
首先,我们使用QObject::sender() 和qobject_cast() 函数获取发出信号的对象。如果是在信号激活的槽中调用,QObject::sender() 返回指向发送信号的对象的指针;否则返回 0。
if (widget) { auto *form = QDesignerFormWindowInterface::findFormWindow(widget); if (form) form->emitSelectionChanged(); } }
有了部件后,我们就可以更新属性编辑器了。Qt Widgets Designer 使用QDesignerPropertySheetExtension 类为其属性编辑器提供数据,每当在其工作区中选中一个部件时,Qt Widgets Designer 就会查询该部件的属性表扩展名并更新属性编辑器。
因此,我们要做的就是通知Qt Widgets Designer ,我们的部件的内部选择已发生变化:首先,我们使用静态QDesignerFormWindowInterface::findFormWindow() 函数获取包含 widget 的QDesignerFormWindowInterface 对象。QDesignerFormWindowInterface 类允许您查询和操作Qt Widgets Designer 工作区中出现的窗体窗口。然后,我们要做的就是发出emitSelectionChanged() 信号,强制更新属性编辑器。
在更改页面标题时,仅仅刷新属性编辑器是不够的,因为需要更新的实际上是页面的属性扩展名。因此,我们需要访问要更改标题的页面的QDesignerPropertySheetExtension 对象。QDesignerPropertySheetExtension 类也允许您操作 widget 的属性,但要获取扩展名,我们必须首先检索Qt Widgets Designer 的扩展名管理器:
void MultiPageWidgetPlugin::pageTitleChanged(const QString &title) { Q_UNUSED(title); auto *widget = qobject_cast<MultiPageWidget*>(sender()); if (widget) { auto *page = widget->widget(widget->currentIndex()); auto *form = QDesignerFormWindowInterface::findFormWindow(widget);
同样,我们首先使用QObject::sender() 和qobject_cast() 函数获取发出信号的 widget。然后,我们从发出信号的 widget 中获取当前页面,并使用静态QDesignerFormWindowInterface::findFormWindow() 函数获取包含我们 widget 的表单。
auto *editor = form->core(); auto *manager = editor->extensionManager();
现在我们有了表单窗口,QDesignerFormWindowInterface 类提供了core() 函数,可返回当前的QDesignerFormEditorInterface 对象。通过QDesignerFormEditorInterface 类,您可以访问Qt Designer 的各种组件。特别是,QDesignerFormEditorInterface::extensionManager() 函数会返回对当前扩展管理器的引用。
auto *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager, page); const int propertyIndex = sheet->indexOf(QLatin1String("windowTitle")); sheet->setChanged(propertyIndex, true); } } }
有了扩展管理器后,我们就可以更新扩展表了:首先,我们使用qt_extension() 函数获取要更改标题的页面的属性扩展名。然后使用QDesignerPropertySheetExtension::indexOf() 函数获取页面标题的索引。如前所述,我们选择使用QWidget::windowTitle 属性来存储页面标题(更多信息请参见containerextension/multipagewidget.cpp 中的MultiPageWidget 类实现)。最后,我们通过调用QDesignerPropertySheetExtension::setChanged() 函数隐式强制更新页面的属性表。
void MultiPageWidgetPlugin::initialize(QDesignerFormEditorInterface *formEditor) { if (initialized) return;
还请注意 initialize() 函数:initialize()
函数的参数是一个QDesignerFormEditorInterface 对象。
auto *manager = formEditor->extensionManager();
在创建与自定义 widget 插件相关的扩展时,我们需要访问Qt Widgets Designer 的当前扩展管理器,该管理器可从QDesignerFormEditorInterface 参数中获取。
除了允许您操作 widget 的属性外,QExtensionManager 类还为Qt Widgets Designer 提供了扩展管理功能。使用Qt Widgets Designer 的当前扩展管理器,您可以检索给定对象的扩展。您还可以为给定对象注册或取消注册扩展。请记住,扩展是一个可以修改Qt Widgets Designer 行为的对象。
注册扩展时,注册的实际上是相关的扩展工厂。在Qt Widgets Designer 中,扩展工厂用于查找和创建所需的命名扩展。因此,在本例中,容器扩展本身并不是在Qt Widgets Designer 必须知道相关部件是否是容器时才创建的。
auto *factory = new MultiPageWidgetExtensionFactory(manager); Q_ASSERT(manager != nullptr); manager->registerExtensions(factory, Q_TYPEID(QDesignerContainerExtension)); initialized = true; }
我们创建一个MultiPageWidgetExtensionFactory
对象,使用从QDesignerFormEditorInterface 参数获取的Qt Widgets Designer 的当前extension manager 注册该对象。第一个参数是新创建的工厂,第二个参数是扩展标识符(字符串)。Q_TYPEID()
宏简单地将字符串转换为QLatin1String 。
MultiPageWidgetExtensionFactory
类是QExtensionFactory 的子类。当Qt Widgets Designer 必须知道某个部件是否是容器时,Qt Widgets Designer 的扩展管理器将遍历其所有注册的工厂,调用第一个能够为该部件创建容器扩展的工厂。该工厂将反过来创建一个MultiPageWidgetExtension
对象。
QString MultiPageWidgetPlugin::domXml() const { return uR"( <ui language="c++"> <widget class="MultiPageWidget" name="multipagewidget"> <widget class="QWidget" name="page"/> </widget> <customwidgets> <customwidget> <class>MultiPageWidget</class> <extends>QWidget</extends> <addpagemethod>addPage</addpagemethod> </customwidget> </customwidgets> </ui>)"_s; }
最后,请查看domXml()
函数。该函数以Qt Widgets Designer 使用的标准 XML 格式包含 widget 的默认设置。在本例中,我们指定了容器的第一页;多页面 widget 的任何初始页面都必须在此函数中指定。
多页面 widgetExtensionFactory 类定义
MultiPageWidgetExtensionFactory
类继承于QExtensionFactory ,后者为Qt Widgets Designer 提供了一个标准扩展工厂。
class MultiPageWidgetExtensionFactory: public QExtensionFactory { Q_OBJECT public: explicit MultiPageWidgetExtensionFactory(QExtensionManager *parent = nullptr); protected: QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; };
该子类的目的是重新实现QExtensionFactory::createExtension() 函数,使其能够创建MultiPageWidget
容器扩展。
MultiPageWidgetExtensionFactory 类的实现
该类的构造函数只需调用QExtensionFactory 基类的构造函数:
MultiPageWidgetExtensionFactory::MultiPageWidgetExtensionFactory(QExtensionManager *parent) : QExtensionFactory(parent) {}
如上所述,当Qt Widgets Designer 必须知道相关部件是否是容器时,就会调用该工厂。
QObject *MultiPageWidgetExtensionFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const { auto *widget = qobject_cast<MultiPageWidget*>(object); if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) return new MultiPageWidgetContainerExtension(widget, parent); return nullptr; }
Qt Widgets Designer无论所请求的扩展是与容器、成员表、属性表还是任务菜单相关联,该工厂的行为都是一样的:它的扩展管理器会遍历所有已注册的扩展工厂,逐一调用createExtension()
,直到有一个工厂响应并创建了所请求的扩展。
因此,我们在MultiPageWidgetExtensionFactory::createExtension()
中要做的第一件事就是检查请求扩展的QObject 事实上是否是MultiPageWidget
对象。然后检查请求的扩展是否是容器扩展。
如果该对象是一个请求容器扩展的 MultiPageWidget,我们将创建并返回一个MultiPageWidgetExtension
对象。否则,我们只需返回一个空指针,让Qt Widgets Designer 的扩展管理器继续搜索已注册的工厂。
MultiPageWidgetContainerExtension 类定义
MultiPageWidgetContainerExtension
类继承于QDesignerContainerExtension ,它允许您向Qt Widgets Designer 中的多页面容器插件添加(和删除)页面。
class MultiPageWidgetContainerExtension: public QObject, public QDesignerContainerExtension { Q_OBJECT Q_INTERFACES(QDesignerContainerExtension) public: explicit MultiPageWidgetContainerExtension(MultiPageWidget *widget, QObject *parent); bool canAddWidget() const override; void addWidget(QWidget *widget) override; int count() const override; int currentIndex() const override; void insertWidget(int index, QWidget *widget) override; bool canRemove(int index) const override; void remove(int index) override; void setCurrentIndex(int index) override; QWidget *widget(int index) const override; private: MultiPageWidget *myWidget; };
需要注意的是,QDesignerContainerExtension 类只是为了提供Qt Widgets Designer 访问自定义多页面部件功能的途径;自定义多页面部件必须实现与扩展功能相对应的功能。
还请注意,我们实现了一个构造函数,它需要两个参数:父 widget 和请求任务菜单的MultiPageWidget
对象。
QDesignerContainerExtension 在 的任务菜单中,我们默认提供了几个菜单项,使用户能够在 的工作区中向相关的自定义多页面 widget 添加或删除页面。Qt Widgets Designer Qt Widgets Designer
多页面小工具容器扩展类的实现
在构造函数中,我们保存了作为参数发送的MultiPageWidget
对象的引用,即与扩展关联的 widget。稍后我们将需要它来访问自定义多页面小部件,以执行请求的操作。
MultiPageWidgetContainerExtension::MultiPageWidgetContainerExtension(MultiPageWidget *widget, QObject *parent) : QObject(parent) , myWidget(widget) { }
要完全启用Qt Widgets Designer 来管理和操作自定义多页面 widget,您必须重新实现QDesignerContainerExtension 的所有功能:
bool MultiPageWidgetContainerExtension::canAddWidget() const { return true; } void MultiPageWidgetContainerExtension::addWidget(QWidget *widget) { myWidget->addPage(widget); } int MultiPageWidgetContainerExtension::count() const { return myWidget->count(); } int MultiPageWidgetContainerExtension::currentIndex() const { return myWidget->currentIndex(); }
您必须重新实现canAddWidget() 和addWidget() 将给定页面添加到容器中,count() 返回容器中的页面数,以及currentIndex() 返回当前选定页面的索引。
void MultiPageWidgetContainerExtension::insertWidget(int index, QWidget *widget) { myWidget->insertPage(index, widget); } bool MultiPageWidgetContainerExtension::canRemove(int index) const { Q_UNUSED(index); return true; } void MultiPageWidgetContainerExtension::remove(int index) { myWidget->removePage(index); } void MultiPageWidgetContainerExtension::setCurrentIndex(int index) { myWidget->setCurrentIndex(index); } QWidget* MultiPageWidgetContainerExtension::widget(int index) const { return myWidget->widget(index); }
您必须重新实现insertWidget() 在给定索引处向容器添加给定页面,canRemove() 和remove() 在给定索引处删除页面,setCurrentIndex() 设置当前选定页面的索引,最后widget() 返回给定索引处的页面。
多页面小工具类定义
MultiPageWidget 类是一个自定义的容器部件,它允许用户操作和填充页面,并使用组合框在这些页面之间导航。
class MultiPageWidget : public QWidget { Q_OBJECT Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex) Q_PROPERTY(QString pageTitle READ pageTitle WRITE setPageTitle STORED false) public: explicit MultiPageWidget(QWidget *parent = nullptr); QSize sizeHint() const override; int count() const; int currentIndex() const; QWidget *widget(int index); QString pageTitle() const; public slots: void addPage(QWidget *page); void insertPage(int index, QWidget *page); void removePage(int index); void setPageTitle(const QString &newTitle); void setCurrentIndex(int index); private slots: void pageWindowTitleChanged(); signals: void currentIndexChanged(int index); void pageTitleChanged(const QString &title); private: QStackedWidget *stackWidget; QComboBox *comboBox; };
需要注意的主要细节是,您的自定义多页面 widget 必须实现与QDesignerContainerExtension 的成员函数相对应的功能,因为QDesignerContainerExtension 类只是为了提供Qt Designer 访问自定义多页面 widget 功能的途径。
此外,我们还声明了currentIndex
和pageTitle
属性及其相关的 set 和 get 函数。通过将这些属性声明为属性,我们允许Qt Widgets Designer 以管理多页面 Widget widget 从QWidget 和QObject 继承的属性的相同方式来管理这些属性,例如属性编辑器。
请注意pageTitle
属性声明中的STORED
属性:STORED
属性表示持久性,即声明在存储对象状态时是否必须记住属性值。如上所述,我们选择使用QWidget::windowTitle 属性来存储页面标题,以便为每个页面赋予自己的标题。因此,pageTitle
属性是一个 "假 "属性,用于编辑目的,无需存储。
我们还必须实现并发出 currentIndexChanged() 和 pageTitleChanged() 信号,以确保每当用户查看另一个页面或更改其中一个页面标题时,Qt Widgets Designer 的属性编辑器都会更新。
更多详情,请参阅containerextension/multipagewidget.cpp中的MultiPageWidget类实现。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.