任务菜单扩展

Qt Designer 创建自定义小部件工具插件,并提供与插件关联的自定义任务菜单条目。

任务菜单扩展示例展示了如何为 Qt Designer 创建一个自定义小部件工具插件,以及如何使用 QDesignerTaskMenuExtension 类来提供与插件关联的自定义任务菜单条目。

要提供可与 Qt Designer 一起使用自定义小部件,我们需要提供一个自包含的实现。在此示例中,我们使用一个自定义小部件,该小部件旨在展示任务菜单扩展功能:井字棋小部件。

扩展是在修改 Qt Designer 行为的对象。当选择具有这种扩展的小部件时,QDesignerTaskMenuExtension 可以提供自定义任务菜单条目。

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

你可以使用与示例相同的模式使用所有扩展,只需替换相应的扩展基类。有关更多信息,请参阅 Qt Designer C++ 类

任务菜单扩展示例由五个类组成

  • TicTacToe 是一个自定义小部件,允许用户玩井字棋游戏。
  • TicTacToePluginTicTacToe 类暴露给 Qt Designer
  • TicTacToeTaskMenuFactory 创建一个 TicTacToeTaskMenu 对象。
  • TicTacToeTaskMenu 提供任务菜单扩展,即插件相关的任务菜单条目。
  • TicTacToeDialog 允许用户修改加载到 Qt Designer 中的井字棋插件的设置。

自定义小部件工具插件的工程文件需要一些附加信息以确保它们在 Qt Designer 中运行。例如,自定义小部件插件依赖于 Qt Designer 提供的组件,必须在我们在的项目文件中指定这一点。我们将首先看看插件的工程文件。

然后我们将继续通过回顾 TicTacToePlugin 类,然后查看 TicTacToeTaskMenuFactoryTicTacToeTaskMenu 类。最后,在快速查看 TicTacToe 小部件的类定义之前,我们将回顾 TicTacToeDialog 类。

项目文件

CMake

项目文件需要声明需要构建链接到 Qt Designer 库的插件

find_package(Qt6 REQUIRED COMPONENTS Core Designer Gui Widgets)

qt_add_plugin(taskmenuextension)

target_link_libraries(taskmenuextension PUBLIC
    Qt::Core
    Qt::Designer
    Qt::Gui
    Qt::Widgets
)

以下示例显示了如何添加小部件的头文件和源文件

target_sources(taskmenuextension PRIVATE
    tictactoe.cpp tictactoe.h
    tictactoedialog.cpp tictactoedialog.h
    tictactoeplugin.cpp tictactoeplugin.h
    tictactoetaskmenu.cpp tictactoetaskmenu.h
)

我们提供了一个插件接口的实现,以便 Qt Designer 可以使用自定义小部件。在本例中,我们还提供了任务菜单扩展和扩展工厂的实现,以及对话框实现。

确保插件安装在一个被 Qt Designer 搜索的位置非常重要。我们通过指定项目目标路径并将其添加到要安装的项目项列表中来实现这一点

   set(INSTALL_EXAMPLEDIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_PLUGINS}/designer")
install(TARGETS taskmenuextension
    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 += tictactoe.h \
           tictactoedialog.h \
           tictactoeplugin.h \
           tictactoetaskmenu.h
SOURCES += tictactoe.cpp \
           tictactoedialog.cpp \
           tictactoeplugin.cpp \
           tictactoetaskmenu.cpp
OTHER_FILES += tictactoe.json

以下示例显示了如何将插件安装到 Qt Designer 的插件路径

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

TicTacToePlugin 类定义

TicTacToePlugin 类将 TicTacToe 类暴露给 Qt Designer。其定义与 自定义小部件插件 示例的插件类相当,该插件类有详细的解释。此特定自定义小部件类定义中特有的部分是类名。

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

#ifndef TICTACTOEPLUGIN_H
#define TICTACTOEPLUGIN_H

#include <QtUiPlugin/QDesignerCustomWidgetInterface>

class QIcon;
class QWidget;

class TicTacToePlugin : public QObject, public QDesignerCustomWidgetInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface")
    Q_INTERFACES(QDesignerCustomWidgetInterface)

public:
    explicit TicTacToePlugin(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:
    bool initialized = false;
};

#endif

插件类为 Qt Designer 提供了有关我们的插件的基本信息,例如其类名和其包含文件。此外,它知道如何创建 TicTacToe 小部件的实例。TicTacToePlugin 还定义了在插件加载到 Qt Designer 后调用的 initialize() 函数。该函数的 QDesignerFormEditorInterface 参数为插件提供了一个通向 Qt Designer 所有 API 的接口。

TicTacToePlugin 类继承自 QObjectQDesignerCustomWidgetInterface。当使用多重继承时,需要记住,使用 Q_INTERFACES() 宏确保所有接口(即不继承 Q_OBJECT 的类)都向元对象系统公开。这使 Qt Designer 能够仅使用 QObject 指针来查询支持接口。

TicTacToePlugin 类实现

TicTacToePlugin 类实现的大部分与 自定义小部件插件 示例的插件类相当

TicTacToePlugin::TicTacToePlugin(QObject *parent)
    : QObject(parent)
{
}

QString TicTacToePlugin::name() const
{
    return u"TicTacToe"_s;
}

QString TicTacToePlugin::group() const
{
    return u"Display Widgets [Examples]"_s;
}

QString TicTacToePlugin::toolTip() const
{
    return u"Tic Tac Toe Example, demonstrating class QDesignerTaskMenuExtension (C++)"_s;
}

QString TicTacToePlugin::whatsThis() const
{
    return {};
}

QString TicTacToePlugin::includeFile() const
{
    return u"tictactoe.h"_s;
}

QIcon TicTacToePlugin::icon() const
{
    return {};
}

bool TicTacToePlugin::isContainer() const
{
    return false;
}

QWidget *TicTacToePlugin::createWidget(QWidget *parent)
{
    auto *ticTacToe = new TicTacToe(parent);
    ticTacToe->setState(u"-X-XO----"_s);
    return ticTacToe;
}

bool TicTacToePlugin::isInitialized() const
{
    return initialized;
}

只有一个函数与该函数有显著差异:initialize() 函数

void TicTacToePlugin::initialize(QDesignerFormEditorInterface *formEditor)
{

函数initialize()需要一个QDesignerFormEditorInterface对象作为参数。类QDesignerFormEditorInterface提供了对Qt Designer组件的访问。

Qt Designer中,您可以创建两种类型的插件:自定义小部件插件和工具插件。QDesignerFormEditorInterface提供了访问所有创建工具插件所必需的Qt Designer组件的接口:扩展管理器、对象检查器、属性编辑器和小部件箱。自定义小部件插件也有访问到相同的组件。

    if (initialized)
        return;

    auto *manager = formEditor->extensionManager();
    Q_ASSERT(manager != nullptr);

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

Qt DesignerQDesignerFormEditorInterface保存了关于所有Qt Designer组件的信息:动作编辑器、对象检查器、属性编辑器、小部件箱以及扩展和表单窗口管理器。

QExtensionManagerQt Designer提供了扩展管理功能。使用Qt Designer的当前扩展管理器,您可以检索给定对象扩展。您还可以为给定对象注册和注销扩展。请记住,扩展是一个修改Qt Designer行为的对象。

在注册扩展时,实际上是注册了相关的扩展工厂。在Qt Designer中,扩展工厂在需要时用于查找和创建命名扩展。因此,在这个例子中,任务菜单扩展本身是在用户请求任务菜单时才被创建的。

    manager->registerExtensions(new TicTacToeTaskMenuFactory(manager),
                                Q_TYPEID(QDesignerTaskMenuExtension));

    initialized = true;
}

QString TicTacToePlugin::domXml() const
{
    return uR"(
<ui language="c++">
    <widget class="TicTacToe" name="ticTacToe"/>
    <customwidgets>
        <customwidget>
            <class>TicTacToe</class>
            <propertyspecifications>
            <tooltip name="state">Tic Tac Toe state</tooltip>
            <stringpropertyspecification name="state" notr="true" type="singleline"/>
            </propertyspecifications>
        </customwidget>
    </customwidgets>
</ui>
)"_s;
}

我们创建一个TicTacToeTaskMenuFactory对象,并使用从QDesignerFormEditorInterface参数中获取的Qt Designer当前的扩展管理器进行注册。第一个参数是新建的工厂,第二个参数是一个字符串,表示扩展标识符。宏Q_TYPEID()将字符串转换为QLatin1String

TicTacToeTaskMenuFactory类继承自QExtensionFactory。当用户点击具有指定任务菜单扩展的小部件的右键按钮时,请求任务菜单,Qt Designer的扩展管理器将会遍历所有注册的工厂,调用第一个能够为所选小部件创建任务菜单扩展的工厂。然后这个工厂将创建一个TicTacToeTaskMenu对象(扩展)。

我们没有重写函数QDesignerCustomWidgetInterface::domXml(它包含了用于Qt Designer中标准XML格式的自定义小部件的默认设置),因为在这种情况下没有必要。

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

最后,我们使用宏Q_PLUGIN_METADATA导出TicTacToePlugin类,以便与Qt的插件处理类一起使用。此宏确保Qt Designer可以访问并构建自定义小部件。没有这个宏,Qt Designer就无法使用这个小部件。

TicTacToeTaskMenuFactory 类定义

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

class TicTacToeTaskMenuFactory : public QExtensionFactory
{
    Q_OBJECT

public:
    explicit TicTacToeTaskMenuFactory(QExtensionManager *parent = nullptr);

protected:
    QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override;
};

子类的目的是重写函数QExtensionFactory::createExtension,使其能够创建一个TicTacToe任务菜单扩展。

TicTacToeTaskMenuFactory 类实现

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

TicTacToeTaskMenuFactory::TicTacToeTaskMenuFactory(QExtensionManager *parent)
    : QExtensionFactory(parent)
{
}

如上所述,当用户通过点击Qt Designer中具有指定任务菜单扩展的部件,并右击请求任务菜单时,会调用工厂。

Qt Designer的行为在请求的扩展与容器、成员表单、属性表或任务菜单相关联时相同:其扩展管理器遍历其注册的所有扩展工厂,直到其中一个通过创建请求的扩展来响应。

QObject *TicTacToeTaskMenuFactory::createExtension(QObject *object,
                                                   const QString &iid,
                                                   QObject *parent) const
{
    if (iid != Q_TYPEID(QDesignerTaskMenuExtension))
        return nullptr;

    if (auto *tic = qobject_cast<TicTacToe*>(object))
        return new TicTacToeTaskMenu(tic, parent);

    return nullptr;
}

因此,我们在TicTacToeTaskMenuFactory::createExtension()中做的第一件事就是检查请求的扩展是否是任务菜单扩展。如果是,并且请求该扩展的部件是一个TicTacToe部件,我们就创建并返回一个TicTacToeTaskMenu对象。否则,我们简单地返回空指针,允许Qt Designer的扩展管理器继续在其注册的工厂中搜索。

TicTacToeTaskMenu类定义

TicTacToeTaskMenu类继承自QDesignerTaskMenuExtension,允许你在Qt Designer的任务菜单中添加自定义条目(以QAction形式)。

class TicTacToeTaskMenu : public QObject, public QDesignerTaskMenuExtension
{
    Q_OBJECT
    Q_INTERFACES(QDesignerTaskMenuExtension)

public:
    explicit TicTacToeTaskMenu(TicTacToe *tic, QObject *parent);

    QAction *preferredEditAction() const override;
    QList<QAction *> taskActions() const override;

private slots:
    void editState();

private:
    QAction *editStateAction;
    TicTacToe *ticTacToe;
};

我们重写了preferredEditAction()taskActions()函数。注意,我们实现了一个接受两个参数的构造函数:父部件和请求的任务菜单的TicTacToe部件。

此外,我们声明了私有槽editState(),我们的自定义editStateAction以及指向我们想要修改状态的TicTacToe部件的私有指针。

TicTacToeTaskMenu类实现

TicTacToeTaskMenu::TicTacToeTaskMenu(TicTacToe *tic, QObject *parent)
    : QObject(parent)
    , editStateAction(new QAction(tr("Edit State..."), this))
    , ticTacToe(tic)
{
    connect(editStateAction, &QAction::triggered, this, &TicTacToeTaskMenu::editState);
}

在构造函数中,我们首先保存了作为参数发送的TicTacToe部件的引用,即我们想要修改状态的部件。我们稍后需要这来调用我们的自定义动作。我们还创建了我们自定义的editStateAction并将其连接到editState()槽。

void TicTacToeTaskMenu::editState()
{
    TicTacToeDialog dialog(ticTacToe);
    dialog.exec();
}

editState()槽在用户选择TicTacToe部件任务菜单中的编辑状态...选项时被调用。该槽创建一个TicTacToeDialog,显示部件的当前状态,并允许用户通过玩游戏来编辑其状态。

QAction *TicTacToeTaskMenu::preferredEditAction() const
{
    return editStateAction;
}

我们重写了preferredEditAction()函数,以返回我们的自定义editStateAction,作为在选中TicTacToe部件并按下F2时应调用的动作。

QList<QAction *> TicTacToeTaskMenu::taskActions() const
{
    return QList<QAction *>{editStateAction};
}

我们重写了taskActions()函数,以返回一个包含我们自定义动作的列表,使这些动作在TicTacToe部件任务菜单的默认菜单条目之上显示。

TicTacToeDialog类定义

TicTacToeDialog类继承自QDialog。对话框允许用户修改当前选定的Tic-Tac-Toe插件的状态。

class TicTacToeDialog : public QDialog
{
    Q_OBJECT

public:
    explicit TicTacToeDialog(TicTacToe *plugin = nullptr, QWidget *parent = nullptr);

    QSize sizeHint() const override;

private slots:
    void resetState();
    void saveState();

private:
    TicTacToe *editor;
    TicTacToe *ticTacToe;
    QDialogButtonBox *buttonBox;
};

我们重写了sizeHint()函数。此外,我们还声明了两个私有槽:resetState()saveState()。除了对话框的按钮和布局外,我们声明了两个TicTacToe指针,一个用于用户可以与之交互的小部件,另一个用于用户想要编辑原始自定义小部件插件的状态。

TicTacToeDialog类实现

TicTacToeDialog::TicTacToeDialog(TicTacToe *tic, QWidget *parent)
    : QDialog(parent)
    , editor(new TicTacToe)
    , ticTacToe(tic)
    , buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok
                                     | QDialogButtonBox::Cancel
                                     | QDialogButtonBox::Reset))
{
    editor->setState(ticTacToe->state());

    connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked,
            this, &TicTacToeDialog::resetState);
    connect(buttonBox, &QDialogButtonBox::accepted, this, &TicTacToeDialog::saveState);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);

    auto *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(editor);
    mainLayout->addWidget(buttonBox);

    setWindowTitle(tr("Edit State"));
}

在构造函数中,我们首先保存了作为参数传入的TicTacToe界面的引用,即用户想要修改的状态界面。然后我们创建一个新的 TicTacToe 界面,并将它的状态设置为与参数界面相同。

最后,我们创建对话框的按钮和布局。

QSize TicTacToeDialog::sizeHint() const
{
    return {250, 250};
}

我们重新实现了 sizeHint() 函数,以确保对话框有一个合理的大小。

void TicTacToeDialog::resetState()
{
    editor->clearBoard();
}

resetState() 空位在用户按下 重置 按钮时被调用。我们做的唯一一件事是调用编辑器界面(即我们在对话框构造函数中创建的 TicTacToe 界面)的 clearBoard() 函数。

void TicTacToeDialog::saveState()
{

saveState() 空位在用户按下 确定 按钮时被调用,并将编辑器界面的状态传输到我们想要修改的状态界面。为了使状态的变更对 Qt Designer 可见,我们需要使用 QDesignerFormWindowInterface 类来设置后者界面的状态属性。

QDesignerFormWindowInterface 为您提供了有关相关表单窗口的信息,并允许您更改其属性。该接口的目的不是直接实例化,而是提供对由 Qt Designer 的表单窗口管理器控制的 Qt Designer 的当前表单窗口的访问权。

如果您正在寻找包含特定界面的表单窗口,您可以使用静态的 QDesignerFormWindowInterface::findFormWindow() 函数

    if (auto *formWindow = QDesignerFormWindowInterface::findFormWindow(ticTacToe))
        formWindow->cursor()->setProperty("state", editor->state());

在获取界面(我们想要修改的状态)的表单窗口后,我们使用 QDesignerFormWindowInterface::cursor() 函数来获取表单窗口的光标。

QDesignerFormWindowCursorInterface 类提供了一个到表单窗口文本光标的接口。一旦我们有了光标,我们就可以使用 QDesignerFormWindowCursorInterface::setProperty() 函数来设置状态属性。

    accept();
}

最后,我们调用 QEvent::accept() 函数,它设置了事件对象的接受标志。设置 accept 参数表示事件接收者想要该事件。不想要的的事件可能会传播到父界面。

TicTacToe 类定义

TicTacToe 类是一个自定义的界面,它允许用户玩井字棋游戏。

class TicTacToe : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(QString state READ state WRITE setState)

public:
    explicit TicTacToe(QWidget *parent = nullptr);

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;
    void setState(const QString &newState);
    QString state() const;
    void clearBoard();

protected:
    void mousePressEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;

private:
    static constexpr char16_t Empty = '-';
    static constexpr char16_t Cross = 'X';
    static constexpr char16_t Nought = 'O';

    QRect cellRect(int position) const;
    int cellWidth() const { return width() / 3; }
    int cellHeight() const { return height() / 3; }

    QString myState;
    int turnNumber = 0;
};

TicTacToe 类的定义中需要关注的主要细节是 state 属性的声明及其 state()setState() 函数。

我们需要将 TicTacToe 界面的状态声明为一个属性,以便在 Qt Designer 中可见;允许 Qt Designer 以与它管理从 QWidgetQObject 继承的属性相同的方式管理它,例如属性编辑器。

示例项目 @ code.qt.io

© 2024 Qt 公司有限公司。包含在此的文档贡献是各自所有者的版权。此处提供的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 的条款授权的。Qt 及其相关标志是芬兰和/或世界各地的 The Qt Company Ltd. 的 商标。所有其他商标均为其各自所有者的财产。