容器扩展示例
为Qt Widgets Designer 创建自定义多页插件。
“容器扩展”示例演示了如何使用QDesignerContainerExtension 类为Qt Widgets Designer 创建自定义多页插件。
Qt Widgets Designer 界面编辑器的屏幕截图,显示了在对象中“在当前页面之前插入页面”的选项" src="images/containerextension-example.webp" title="Qt Widgets Designer 界面编辑器的屏幕截图,显示了在对象中“在当前页面之前插入页面”的选项"/>
要提供一个可与Qt Widgets Designer 配合使用的自定义小部件,我们需要提供一个自包含的实现。在本示例中,我们使用了一个自定义的多页小部件,旨在展示容器扩展功能。
扩展是一个用于修改Qt Widgets Designer 行为的对象。QDesignerContainerExtension 使Qt Widgets Designer 能够管理和操作自定义多页小部件,即向小部件添加或删除页面。
Qt Widgets Designer 中提供了四种扩展类型:
- QDesignerMemberSheetExtension 提供一种扩展,允许您操作小部件的成员函数,该扩展在使用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 Designer 。MultiPageWidgetExtensionFactory用于创建MultiPageWidgetContainerExtension对象。MultiPageWidgetContainerExtension提供容器扩展功能。
自定义控件插件的项目文件需要一些额外信息,以确保它们能在Qt Widgets Designer 中正常运行。例如,自定义控件插件依赖于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
)以下示例展示了如何添加该小部件的头文件和源文件:
target_sources(containerextension PRIVATE
multipagewidget.cpp multipagewidget.h
multipagewidgetcontainerextension.cpp multipagewidgetcontainerextension.h
multipagewidgetextensionfactory.cpp multipagewidgetextensionfactory.h
multipagewidgetplugin.cpp multipagewidgetplugin.h
)我们提供了插件接口的实现,以便Qt Widgets Designer 能够使用该自定义控件。在此示例中,我们还提供了容器扩展接口和扩展工厂的实现。
务必确保插件安装在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以下示例展示了如何添加该控件的头文件和源文件:
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 += targetMultiPageWidgetPlugin 类定义
MultiPageWidgetPlugin 类向Qt Widgets Designer 公开了MultiPageWidget 类。其定义与“自定义控件插件”示例中的插件类类似,该示例已进行详细说明。该类定义中与此特定自定义控件相关的部分包括类名和几个私有槽:
#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 小部件的实例。MultiPageWidgetPlugin 还定义了initialize()函数,该函数在插件加载到Qt Widgets Designer 后会被调用。该函数的QDesignerFormEditorInterface 参数为插件提供了访问Qt Widgets Designer 所有API的入口。
对于像我们这样的多页小部件,我们还必须实现两个私有槽:currentIndexChanged() 和 pageTitleChanged(),以便在用户浏览其他页面或更改某个页面标题时,能够更新Qt Widgets Designer 的属性编辑器。 为了给每个页面设置各自的标题,我们选择使用QWidget::windowTitle 属性来存储页面标题(更多信息请参见containerextension/multipagewidget.cpp 中的 MultiPageWidget 类实现)。 请注意,目前无法在不使用预定义属性作为占位符的情况下,向页面添加自定义属性(例如页面标题)。
MultiPageWidgetPlugin 类同时继承自QObject 和QDesignerCustomWidgetInterface 。需要注意的是,在使用多重继承时,必须确保所有接口(即未继承Q_OBJECT 的类)都通过Q_INTERFACES()宏向元对象系统声明。这使得Qt Widgets Designer 能够仅使用QObject 指针,通过qobject_cast()查询支持的接口。
为了确保 Qt 将该控件识别为插件,请通过添加Q_PLUGIN_METADATA() 宏来导出该控件的相关信息:
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget")通过此宏,Qt Widgets Designer 可以访问并构建自定义控件。如果没有此宏,Qt Widgets Designer 将无法使用该控件。
MultiPageWidgetPlugin 类的实现
MultiPageWidgetPlugin 类的实现大部分与“自定义小部件插件”示例中的插件类等同:
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,因为我们的自定义小部件旨在用作容器。
bool MultiPageWidgetPlugin::isContainer() const
{
return true;
}另一个不同的函数是创建自定义小部件的函数:
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;
}除了创建并返回小部件外,我们还将自定义容器小部件的 currentIndexChanged() 信号连接到插件的 currentIndexChanged() 槽,以确保每当用户浏览其他页面时,Qt Widgets Designer 的属性编辑器都会更新。我们还将小部件的 pageTitleChanged() 信号连接到插件的 pageTitleChanged() 槽。
每当我们的自定义小部件发出 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()来获取包含该控件的QDesignerFormWindowInterface 对象。QDesignerFormWindowInterface 类允许你查询和操作Qt Widgets Designer 工作区中显示的表单窗口。然后,我们只需发出其emitSelectionChanged()信号,即可强制更新属性编辑器。
在更改页面标题时,仅对属性编辑器进行常规刷新是不够的,因为实际上需要更新的是该页面的属性扩展。因此,我们需要访问要更改标题的页面的QDesignerPropertySheetExtension 对象。QDesignerPropertySheetExtension 类同样允许你操作小部件的属性,但要获取该扩展,我们必须先获取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()函数获取发出信号的小部件。然后从发出信号的小部件中获取当前页面,并使用静态函数QDesignerFormWindowInterface::findFormWindow()来获取包含该小部件的表单。
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()函数获取要修改标题的页面的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();在创建与自定义小部件插件相关的扩展时,我们需要访问Qt Widgets Designer 的当前扩展管理器,该管理器可通过QDesignerFormEditorInterface 参数获取。
除了允许您操作小部件的属性外,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格式,为控件提供了默认设置。在此情况下,我们指定了容器的第一页;多页控件的任何初始页面都必须在此函数中指定。
MultiPageWidgetExtensionFactory 类定义
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 提供访问您自定义多页小部件功能的途径;您的自定义多页小部件必须实现与该扩展函数对应的功能。
另请注意,我们实现了一个接受两个参数的构造函数:父小部件,以及请求任务菜单的MultiPageWidget 对象。
QDesignerContainerExtension 默认情况下,该扩展会在Qt Widgets Designer 的任务菜单中提供几个菜单项,使用户能够在Qt Widgets Designer 的工作区中向关联的自定义多页小部件添加或删除页面。
MultiPageWidgetContainerExtension 类的实现
在构造函数中,我们将作为参数传入的MultiPageWidget 对象(即与该扩展关联的小部件)的引用保存下来。稍后我们需要使用该引用来访问自定义多页小部件,以执行请求的操作。
MultiPageWidgetContainerExtension::MultiPageWidgetContainerExtension(MultiPageWidget *widget,
QObject *parent)
: QObject(parent)
, myWidget(widget)
{
}要使Qt Widgets Designer 能够全面管理和操作您的自定义多页小部件,您必须重写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 类定义
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;
};需要注意的主要细节是,您的自定义多页控件必须实现与QDesignerContainerExtension 成员函数对应的功能,因为QDesignerContainerExtension 类仅旨在提供对您自定义多页控件功能的Qt Designer 访问。
此外,我们声明了currentIndex 和pageTitle 属性,以及与其关联的set和get函数。通过将这些属性声明为属性,我们允许Qt Widgets Designer 以与管理MultiPageWidget控件从QWidget 和QObject 继承的属性相同的方式来管理它们,例如利用属性编辑器。
请注意pageTitle 属性声明中的STORED 属性:该STORED 属性表示持久性,即声明在存储对象状态时是否必须记住该属性的值。 如上所述,我们选择使用QWidget::windowTitle 属性来存储页面标题,以便为每个页面设置独立的标题。因此,pageTitle 属性是一个仅用于编辑目的的“虚拟”属性,无需进行存储。
我们还必须实现并发出 currentIndexChanged() 和 pageTitleChanged() 信号,以确保每当用户浏览其他页面或更改其中一个页面标题时,Qt Widgets Designer 的属性编辑器都能得到更新。
更多详细信息请参阅containerextension/multipagewidget.cpp中的 MultiPageWidget 类实现。
© 2026 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.