警告

本节包含自动从C++转换到Python的代码片段,可能包含错误。

容器扩展示例#

Qt Designer创建自定义多页插件。

容器扩展示例展示了如何使用QDesignerContainerExtension类创建用于Qt Designer的自定义多页插件。

../_images/containerextension-example1.webp

为了提供一个可配合Qt Designer使用的自定义小部件,我们需要提供一个独立的实现。在这个例子中,我们使用了一个自定义的多页小部件,旨在展示容器扩展功能。

扩展是一个对象,可以改变Qt Designer的行为。类QDesignerContainerExtension使Qt Designer能够管理和操作一个自定义多页小部件,即向小部件中添加和删除页面。

Qt Designer中有四种可用的扩展类型

  • QDesignerMemberSheetExtension提供一种扩展,允许你操作小部件的成员函数,该函数在Qt Designer用于编辑信号和槽的模式中配置连接时显示。

  • QDesignerPropertySheetExtension提供一种扩展,允许你操作小部件的属性,该属性在Qt Designer的属性编辑器中显示。

  • QDesignerTaskMenuExtension提供一种扩展,允许你向Qt Designer的任务菜单中添加自定义菜单项。

  • QDesignerContainerExtension提供一种扩展,允许你向Qt Designer的多页容器插件(以及删除页面)。

你可以按照本例中的相同模式使用所有扩展,仅替换相应的扩展基类。对于更多信息,请参阅Qt Designer C++ Classes

容器扩展示例包含四个类

  • MultiPageWidget是一个自定义容器小部件,允许用户操作和填充其页面,并通过组合框在这些页面之间进行导航。

  • MultiPageWidgetPluginMultiPageWidget类公开给Qt Designer

  • MultiPageWidgetExtensionFactory 创建一个 MultiPageWidgetContainerExtension 对象。

  • MultiPageWidgetContainerExtension 提供容器扩展功能。

定制小工具插件的项目文件需要额外信息以确保它们可以在 Qt Designer 中正常工作。例如,定制小工具插件依赖于 Qt Designer 提供的组件,这一点必须在我们的项目文件中指定。我们将首先查看插件的项目文件。

然后,我们继续审查 MultiPageWidgetPlugin 类,并查看 MultiPageWidgetExtensionFactoryMultiPageWidgetContainerExtension 类。最后,我们将快速查看 MultiPageWidget 类的定义。

项目文件#

CMake#

项目文件需要明确表示要构建链接到 Qt 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 Designer 可以使用定制小工具。在特定示例中,我们还提供了容器扩展接口和扩展工厂的实现。

确保插件安装在一个由 Qt 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 Designer 插件一起安装。

有关插件的其他信息,请参阅《如何创建 Qt 插件》文档。

qmake#

以下示例显示了如何将插件链接到 Qt 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 Designer 的插件路径。

target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target

MultiPageWidgetPlugin 类定义#

MultiPageWidgetPlugin 类将 MultiPageWidget 类公开给 Qt Designer。其定义与《定制小工具插件》示例中的插件类类似,该插件类已详细解释。特定于本特定定制小工具的类定义部分是类名和一些私有槽。

#ifndef MULTIPAGEWIDGETPLUGIN_H
#define MULTIPAGEWIDGETPLUGIN_H

QT_BEGIN_NAMESPACE
class QIcon():
class QWidget():
QT_END_NAMESPACE
class MultiPageWidgetPlugin(QObject, QDesignerCustomWidgetInterface):

    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget")
    Q_INTERFACES(QDesignerCustomWidgetInterface)
# public
    MultiPageWidgetPlugin = explicit(QObject parent = None)
    QString name() override
    QString group() override
    QString toolTip() override
    QString whatsThis() override
    QString includeFile() override
    QIcon icon() override
    bool isContainer() override
    QWidget createWidget(QWidget parent) override
    bool isInitialized() override
    def initialize(formEditor):
    QString domXml() override
# private slots
    def currentIndexChanged(index):
    def pageTitleChanged(title):
# private
    initialized = False

#endif

该插件类为Qt Designer提供了有关插件的基本信息,例如其类名和包含文件。此外它还知道如何创建MultiPageWidget小部件的实例。MultiPageWidgetPlugin还定义了在插件加载到Qt Designer后调用的initialize()函数。该函数的QDesignerFormEditorInterface参数为插件提供了访问所有Qt Designer API的网关。

对于像我们这样的多页面小部件,我们还必须实现两个私有槽,即currentIndexChanged()和pageTitleChanged(),以便在用户查看另一页或更改页面标题时更新Qt Designer属性编辑器。为了给每个页面各自的标题,我们选择使用QWidget::windowTitle属性来存储页面标题(有关更多信息,请参阅containerextension/multipagewidget.cpp中MultiPageWidget类的实现。请注意,目前还没有在不使用预定义属性作为占位符的情况下向页面添加自定义属性(例如,页面标题)的方法。

MultiPageWidgetPlugin类从QObject和QDesignerCustomWidgetInterface继承。在使用多重继承时,要记住,必须使用Q_INTERFACES()宏确保所有接口(即不继承Q_OBJECT的类)被元对象系统所知晓。这使Qt Designer能够使用qobject_cast()仅通过QObject指针查询支持的接口。

为了确保Qt能识别该小部件为插件,通过添加Q_PLUGIN_METADATA()宏导出有关小部件的相关信息。

Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidget")

使用这个宏,Qt Designer可以访问和构造自定义小部件。没有这个宏,Qt Designer无法使用这个小部件。

MultiPageWidgetPlugin类实现#

MultiPageWidgetPlugin类的实现大部分相当于Custom Widget Plugin示例的插件类

def __init__(self, parent):
    super().__init__(parent)


def name(self):

    return "MultiPageWidget"

def group(self):

    return "Display Widgets [Examples]"

def toolTip(self):

    return {}

def whatsThis(self):

    return {}

def includeFile(self):

    return "multipagewidget.h"

def icon(self):

    return {}

def isInitialized(self):

    return initialized

其中不同的一个函数是isContainer()函数,在这个示例中返回true,因为我们的自定义小部件旨在用作容器。

def isContainer(self):

    return True

另一个不同的函数是我们创建自定义小部件的函数

QWidget MultiPageWidgetPlugin.createWidget(QWidget parent)

    widget = MultiPageWidget(parent)
    widget.currentIndexChanged.connect(
            self.currentIndexChanged)
    widget.pageTitleChanged.connect(
            self.pageTitleChanged)
    return widget

除了创建并返回小部件外,我们还将我们的自定义容器小部件的currentIndexChanged()信号连接到插件上的currentIndexChanged()槽,以确保在用户查看另一页时更新Qt Designer属性编辑器。我们还将小部件的pageTitleChanged()信号连接到插件的pageTitleChanged()槽。

当前IndexChanged()槽在自定义小部件的currentIndexChanged()信号发出时被调用,即当用户查看另一页时

def currentIndexChanged(self, index):

    Q_UNUSED(index)
    widget = MultiPageWidget(sender())

首先,我们使用QObject::sender()和qobject_cast()函数检索发出信号的对象。如果此信号是在由信号激活的槽中调用的,则QObject::sender()返回发送信号的对象的指针;否则返回0。

if widget:
    form = QDesignerFormWindowInterface.findFormWindow(widget)
    if form:
        form.emitSelectionChanged()

一旦获得小部件,我们就可以更新属性编辑器。 Qt Designer 使用 QDesignerPropertySheetExtension 类来填充其属性编辑器,每当在它的工作区中选择小部件时,Qt Designer 会查询该小部件的属性表扩展并与更新属性编辑器。

因此,我们想要实现的目标是通知 Qt Designer 我们的小部件的 内部 选择已更改:首先,我们使用静态函数 findFormWindow() 来获取包含小部件的 QDesignerFormWindowInterface 对象。该 QDesignerFormWindowInterface 类允许你查询和操纵在 Qt Designer 的工作区中出现的表单窗口。然后,我们只需要发射它的 emitSelectionChanged() 信号,强制更新属性编辑器。

当更改页面标题时,属性编辑器的一般刷新是不够的,因为实际上是页面的属性扩展需要更新。因此,我们需要访问要更改标题的页面及其 QDesignerPropertySheetExtension 对象。同样,QDesignerPropertySheetExtension 类还允许你操作小部件的属性,但为了获得扩展,我们必须首先获取对 Qt Designer 扩展管理器的访问权限。

def pageTitleChanged(self, title):

    Q_UNUSED(title)
    widget = MultiPageWidget(sender())
if widget:
    page = widget.widget(widget.currentIndex())
    form = QDesignerFormWindowInterface.findFormWindow(widget)

同样,我们首先使用 QObject::sender() 和 qobject_cast() 函数检索发出信号的 widget,然后从发出信号的 widget 中检索当前页面,并使用静态函数 findFormWindow() 检索包含我们的 widget 的表单。

editor = form.core()
manager = editor.extensionManager()

现在我们已经有了表单窗口,QDesignerFormWindowInterface 类提供了 core() 函数,该函数返回当前的 QDesignerFormEditorInterface 对象。QDesignerFormEditorInterface 类允许您访问 Qt Designer 的各种组件。特别是,extensionManager() 函数返回当前扩展管理器的引用。

sheet = qt_extension<QDesignerPropertySheetExtension*>(manager, page)
propertyIndex = sheet.indexOf("windowTitle")
sheet.setChanged(propertyIndex, True)

一旦我们有了扩展管理器,我们可以更新扩展表单:首先,我们使用 qt_extension() 函数检索我们想要更改标题的页面的属性扩展。然后,我们使用 indexOf() 函数检索页面标题的索引。如前所述,我们选择使用 QWidget::windowTitle 属性来存储页面标题(更多信息请参阅 containerextension/multipagewidget.cpp 中的 MultiPageWidget 类实现。最后,通过调用 setChanged() 函数,隐式地强制更新页面属性表单。

def initialize(self, formEditor):

    if initialized:
        return

注意 initialize() 函数:initialize() 函数将 QDesignerFormEditorInterface 对象作为参数。

manager = formEditor.extensionManager()

当创建与自定义小部件插件关联的扩展时,我们需要访问来自 QDesignerFormEditorInterface 参数的当前扩展管理器。

除了允许您操作小部件的属性外,QExtensionManager 类还提供了对 Qt Designer 的扩展管理功能。使用 Qt Designer 的当前扩展管理器,您可以检索给定对象的扩展。您还可以为给定对象注册和注销扩展。请记住,扩展是一种修改 Qt Designer 行为的对象。

在注册扩展时,实际上是注册了相关的扩展工厂。在 Qt Designer 中,扩展工厂用于查找和创建名为扩展的对象。因此,在此示例中,容器扩展本身仅在 Qt Designer 需要知道关联的小部件是否为容器时才创建。

factory = MultiPageWidgetExtensionFactory(manager)
Q_ASSERT(manager != None)
manager.registerExtensions(factory, Q_TYPEID(QDesignerContainerExtension))
initialized = True

我们创建了一个对象 MultiPageWidgetExtensionFactory 并使用 Qt Designer 当前的 扩展 管理器 来注册。这个管理器是通过 QDesignerFormEditorInterface 参数获取的。第一个参数是新创建的工厂,第二个参数是一个字符串类型的扩展标识符。宏 Q_TYPEID() 将简单地将字符串转换为 QLatin1String

MultiPageWidgetExtensionFactory 类是 QExtensionFactory 的子类。当 Qt Designer 必须知道一个窗口小部件是否是容器时,Qt Designer 的扩展管理器会通过它所有注册的工厂运行,调用第一个能为此窗口小部件创建容器扩展的工厂。这个工厂将创建一个 MultiPageWidgetExtension 对象。

def domXml(self):

    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() 函数。这个函数包含了在标准 XML 格式中使用的 Qt Designer 中窗口小部件的默认设置。在这种情况下,我们指定了容器第一个页面;一个多页窗口小部件的所有初始页面都必须在此函数中指定。

MultiPageWidgetExtensionFactory 类定义#

MultiPageWidgetExtensionFactory 类继承自 QExtensionFactory,它为 Qt Designer 提供了一个标准的扩展工厂。

class MultiPageWidgetExtensionFactory(QExtensionFactory):

    Q_OBJECT
# public
    MultiPageWidgetExtensionFactory = explicit(QExtensionManager parent = None)
# protected
    QObject createExtension(QObject object, QString iid, QObject parent) override

子类的目的是重新实现 createExtension() 函数,使其能够创建一个 MultiPageWidget 容器扩展。

MultiPageWidgetExtensionFactory 类实现#

类构造函数简单调用 QExtensionFactory 基类构造函数

def __init__(self, parent):
    super().__init__(parent)
{}

如上所述,当 Qt Designer 必须知道相关窗口小部件是否是容器时,将调用工厂。

QObject MultiPageWidgetExtensionFactory.createExtension(QObject object,
                                                          QString iid,
                                                          QObject parent)

    widget = MultiPageWidget(object)
    if widget and (iid == Q_TYPEID(QDesignerContainerExtension)):
        return MultiPageWidgetContainerExtension(widget, parent)
    return None

Qt Designer 的行为不受请求的扩展是否与容器、成员表格、属性表格或任务菜单相关联的影响:它的扩展管理器会遍历所有注册的扩展工厂,并对每个调用 createExtension(),直到有一个响应并创建请求的扩展。

因此,在 MultiPageWidgetExtensionFactory::createExtension() 中,我们首先检查请求扩展的 QObject 是否实际上是 MultiPageWidget 对象。然后检查请求的扩展是否是容器扩展。

如果对象是请求容器扩展的 MultiPageWidget,我们将创建并返回一个 MultiPageWidgetExtension 对象。否则,我们简单地返回一个空指针,允许 Qt Designer 的扩展管理器继续在已注册的工厂中进行搜索。

MultiPageWidgetContainerExtension 类定义#

MultiPageWidgetContainerExtension类继承自QDesignerContainerExtension,允许你在Qt Designer的多页容器插件中添加(和删除)页面。

class MultiPageWidgetContainerExtension(QObject,
                                         public QDesignerContainerExtension

    Q_OBJECT
    Q_INTERFACES(QDesignerContainerExtension)
# public
    MultiPageWidgetContainerExtension = explicit(MultiPageWidget widget, QObject parent)
    bool canAddWidget() override
    def addWidget(widget):
    int count() override
    int currentIndex() override
    def insertWidget(index, widget):
    bool canRemove(int index) override
    def remove(index):
    def setCurrentIndex(index):
    QWidget widget(int index) override
# private
    myWidget = MultiPageWidget()

值得注意的是,QDesignerContainerExtension类仅是为了提供Qt Designer访问你自定义的多页小部件的功能;你的自定义多页小部件必须实现与之对应的扩展功能的相应功能。

另外,我们实现了一个构造函数,它接受两个参数:父小部件和请求任务菜单的MultiPageWidget对象。

QDesignerContainerExtension默认在Qt Designer的任务菜单中提供几个菜单项,使用户能够添加或删除关联的自定义多页小部件在Qt Designer的工作空间中的页面。

MultiPageWidgetContainerExtension 类实现#

在构造函数中,我们保存了作为参数发送的MultiPageWidget对象的引用,即与扩展相关联的小部件。我们将需要此引用来稍后执行所需的操作。

MultiPageWidgetContainerExtension.MultiPageWidgetContainerExtension(MultiPageWidget widget,
                                                                     QObject parent)
    super().__init__(parent)
    , myWidget(widget)

为了使Qt Designer能够完全管理和操作你的自定义多页小部件,你必须重新实现QDesignerContainerExtension的所有函数。

def canAddWidget(self):

    return True

def addWidget(self, widget):

    myWidget.addPage(widget)

def count(self):

    return myWidget.count()

def currentIndex(self):

    return myWidget.currentIndex()

你必须重新实现canAddWidgetaddWidget,向容器添加给定页面,count返回容器中的页面数量,以及currentIndex返回当前选中页面的索引。

def insertWidget(self, index, widget):

    myWidget.insertPage(index, widget)

def canRemove(self, int index):

    Q_UNUSED(index)
    return True

def remove(self, index):

    myWidget.removePage(index)

def setCurrentIndex(self, index):

    myWidget.setCurrentIndex(index)

QWidget* MultiPageWidgetContainerExtension.widget(int index)

    return myWidget.widget(index)

您必须重新实现 insertWidget(),在指定的索引处将给定页面添加到容器中,canRemove()remove() 删除指定索引处的页面,setCurrentIndex() 设置当前选中页面的索引,最后通过 widget() 返回指定索引处的页面。

MultiPageWidget 类定义#

MultiPageWidget 类是一个自定义容器小部件,允许用户操作和填充其页面,并使用组合框在这些页面间导航。

class MultiPageWidget(QWidget):

    Q_OBJECT
    Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex)
    Q_PROPERTY(QString pageTitle READ pageTitle WRITE setPageTitle STORED False)
# public
    MultiPageWidget = explicit(QWidget parent = None)
    QSize sizeHint() override
    count = int()
    currentIndex = int()
    widget = QWidget(int index)
    pageTitle = QString()
# public slots
    def addPage(page):
    def insertPage(index, page):
    def removePage(index):
    def setPageTitle(newTitle):
    def setCurrentIndex(index):
# private slots
    def pageWindowTitleChanged():
# signals
    def currentIndexChanged(index):
    def pageTitleChanged(title):
# private
    stackWidget = QStackedWidget()
    comboBox = QComboBox()

需要注意的主要细节是,由于 QDesignerContainerExtension 类仅旨在为 Qt Designer 提供访问自定义多页面小部件的功能,因此您的自定义多页面小部件必须实现与 QDesignerContainerExtension 成员函数相对应的功能。

此外,我们声明了 currentIndexpageTitle 属性及其关联的设置和获取函数。通过将这些属性声明为属性,我们允许 Qt Designer 以相同的方式管理它们,正如它管理从 QWidget 和 QObject 继承的属性一样,例如功能性的属性编辑器。

注意 pageTitle 属性声明中的 STORED 属性:STORED 属性表示持久性,即在保存对象状态时声明是否必须记住属性值。如上所述,我们选择使用 QWidget::windowTitle 属性来存储页面标题,以便给每个页面分配自己的标题。因此,pageTitle 属性是一个“虚假”属性,仅提供编辑目的,不需要存储。

我们还必须实现并发射 currentIndexChanged() 和 pageTitleChanged() 信号,以确保在用户查看另一个页面或更改其中一个页面标题时,Qt Designer 的属性编辑器会更新。

有关 MultiPageWidget 类实现的详细信息,请参阅 containerextension/multipagewidget.cpp

示例项目 @ code.qt.io