创建 Qt Designer 插件项目
Qt 提供两种设计插件的 API,可以用于扩展 Qt 的功能。高级 API 用于设计插件以扩展 Qt 的功能,例如定制数据库驱动、图像格式、文本编码、定制样式等。Qt Designer 里大量采用了插件,点击 Qt Creator 的"Help"->"About Plugins"菜单项,会显示 Qt Creator 里已经安装的各种插件。
低级 API 用于创建插件以扩展自己编写应用程序的功能,最常见的就是将自定义 Widget 组件安装到 UI 设计器里,用于窗口界面设计。本篇文章就将上一篇中的窗口电池组件编写成 UI 组件安装到 UI 设计器中。
查看 Qt Creator 所使用的编译器
想要编译出 UI 设计器中可以使用的插件,就需要知道编译 Qt Creator 的编译器,查看方式如下:
从"关于"中可以看出,我这个版本的 Qt Creator 是使用 MSVC2019
编译器编译出来的,所以我们在创建插件项目的使用需要使用 MSVC2019
编译器。
创建插件项目
- 第一步是设置项目名称和保存路径,这里我取名叫
QwBatteryPlugin
。 - 第二步是选择项目编译器,可以选择多个编译器,在编译时,再选择具体的编译器。但是实际上我们只能选择
MSVC2019
才有用。 - 第三步是自定义 QWidget 类的名称,需要在左侧的 WIdget classes 列表里设置类名,右侧就会自动补全文件名,这里我添加一个
QwBattery
类。还可以选择一个图标作为自定义组件在 UI 设计器组件面板里的显示图形。
参考步骤如下:
插件项目各文件的功能实现
QwBatteryPlugin 类
qwbatteryplugin.h 文件中的内容是对插件类 QwBatteryPlugin 的定义,类定义完成代码如下:
cpp
#ifndef QWBATTERYPLUGIN_H
#define QWBATTERYPLUGIN_H
#include <QDesignerCustomWidgetInterface>
class QwBatteryPlugin : public QObject, public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface")
public:
explicit QwBatteryPlugin(QObject *parent = nullptr);
bool isContainer() const override;
bool isInitialized() const override;
QIcon icon() const override;
QString domXml() const override;
QString group() const override;
QString includeFile() const override;
QString name() const override;
QString toolTip() const override;
QString whatsThis() const override;
QWidget *createWidget(QWidget *parent) override;
void initialize(QDesignerFormEditorInterface *core) override;
private:
bool m_initialized = false;
};
#endif // QWBATTERYPLUGIN_H
QwBatteryPlugin 类实现了 QDesignerCustomWidgetInterface
接口,这是专门为 Qt Designer 设计自定义 WIdget 组件的接口。在这里类的定义里,除了 Q_OBJECT
宏以外,还用了 Q_INTERFACES
声明了实现的接口,用 Q_PLUGIN_METADATA
声明了元数据名称,这些都无需改动。
public 部分的函数都是观察插件信息或功能的一些函数,通过其实现代码可以看出这些函数的功能。下面是 qwbatteryplugin.cpp
文件里实现的代码:
cpp
#include "qwbatteryplugin.h"
#include "qwbattery.h"
#include <QtPlugin>
QwBatteryPlugin::QwBatteryPlugin(QObject *parent)
: QObject(parent)
{}
void QwBatteryPlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (m_initialized)
return;
// Add extension registrations, etc. here
m_initialized = true;
}
bool QwBatteryPlugin::isInitialized() const
{
return m_initialized;
}
QWidget *QwBatteryPlugin::createWidget(QWidget *parent)
{
return new QwBattery(parent);
}
QString QwBatteryPlugin::name() const
{
return QLatin1String("QwBattery");
}
QString QwBatteryPlugin::group() const
{
return QLatin1String("");
}
QIcon QwBatteryPlugin::icon() const
{
return QIcon(QLatin1String(":/batteryicon.ico"));
}
QString QwBatteryPlugin::toolTip() const
{
return QLatin1String("");
}
QString QwBatteryPlugin::whatsThis() const
{
return QLatin1String("");
}
bool QwBatteryPlugin::isContainer() const
{
return false;
}
QString QwBatteryPlugin::domXml() const
{
return QLatin1String(R"(<widget class="QwBattery" name="qwBattery">
</widget>)");
}
QString QwBatteryPlugin::includeFile() const
{
return QLatin1String("qwbattery.h");
}
这些函数的部分内容是根据创建插件向导里设置的内容自动生成的。createWidget()
函数创建一个 QwBattery 类的实例,在 UI 设计器里作为设计实例;name()
函数返回组件的类名称;group()
函数设置组件安装在面板里的分组名称;icon()
设置组件的图标;isContaoner()
设置组件是否作为容器,false 表示不作为容器,不能在这个组件上放置其他组件;domXml()
函数用 XML 设置组件的一些属性,缺省的只设置类名和实例名。
QWBatteryPlugin.pro 的内容
QwBatteryPlugon.pro
是插件项目的项目管理文件,内容如下:
cpp
# 表示项目作为插件,编译后只有lib和dll(或so)
CONFIG += plugin debug_and_release
TARGET = $$qtLibraryTarget(qwbatteryplugin)
# 项目是一个库,一般的应用程序模版类型是app
TEMPLATE = lib
HEADERS = qwbatteryplugin.h
SOURCES = qwbatteryplugin.cpp
RESOURCES = icons.qrc
LIBS += -L.
QT += designer
target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target
include(qwbattery.pri)
内置项目 qwbattery.pri
cpp
HEADERS += qwbattery.h
SOURCES += qwbattery.cpp
组件类 QWBattery 定义
QWBattery 类的定义与上一篇文章中的定义基本一样,只是在声明类的时候需要添加一个宏 QDESIGNER_WIDGET_EXPORT
,并且用 Q_PROPERTY
宏定义了一个属性 powerLevel。完整定义如下:
cpp
#ifndef QWBATTERY_H
#define QWBATTERY_H
#include <QDesignerExportWidget>
#include <QWidget>
#include <QPaintEvent>
class QDESIGNER_WIDGET_EXPORT QwBattery : public QWidget
{
Q_OBJECT
Q_PROPERTY(int powerLevel READ powerLevel WRITE setPowerLevel NOTIFY powerLevelChanged DESIGNABLE true) // designable
public:
explicit QwBattery(QWidget *parent = nullptr);
void setPowerLevel(int pow);
int powerLevel();
void setWarnLevel(int warn);
int warnLevel();
QSize sizeHint();
signals:
void powerLevelChanged(int);
private:
QColor mColorBack = Qt::white;
QColor mColorBorder = Qt::black;
QColor mColorPower = Qt::green;
QColor mColorWarning = Qt::red;
int mPowerLevel = 60;
int mWarnLevel = 20;
protected:
void paintEvent(QPaintEvent* event) override;
};
#endif // QWBATTERY_H
- QDESIGNER_WIDGET_EXPORT 宏表示将自定义的组件类从插件导出给 Qt Designer 使用,必须在类名钱使用此宏。
- Q_PROPERTY 宏定义属性,这里定义了一个 int 类型的属性 powerLevel。READ 宏声明了属性的读取函数时 powerLevel();WRITE 宏声明了设置属性值的函数是 setPowerLevel();NOTIFY 宏声明了其值变化时发射的信号是 powerLevelChanged();DWSIGNABLE 宏定义属性在 UI 设计器里是否可见,缺省为 true。
插件的编译和安装
在写完代码后,分别在 debug 和 release 模式下进行构建(注意不是运行),然后在对应的 debug 和 release 目录就会生成 dll 和 lib 文件。分别将 debug 和 release 产生的 dll 文件复制到以下两个目录:
C:\Qt6\6.7.3\msvc2019_64\plugins\designer
C:\Qt6\Tools\QtCreator\bin\plugins\designer
重启 Qt Creator,使用 UI 设计器设计窗口时,在左侧的组件面板里就会看到我们刚刚自定义的插件:
使用自定义插件
在项目源文件目录下创建一个 include 文件夹,将用到的插件的 头文件
和 lib
文件都复制进去,然后在 Qt Creator 中添加外部库。
添加后,项目配置文件会添加如下的依赖:
cpp
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/include/ -lqwbatteryplugin
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/include/ -lqwbatteryplugind
INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include
使用 MSVC 编译器输出中文的问题
在 Qt Creator 中使用 MSVC 编译器编译项目时,若处理不当容易出现中文字符串乱码的问题。这是由于 Qt Creator 保存的文件使用的是 UTF-8
编码,MSVC
编译器虽然可以正常编译 BOM 的 UTF-8 编码的源文件,但是生成的可执行文件的编码是 Windows 本地字符集,比如 GB 2312。也就是在可执行文件中,字符串"当前电量"是以 GB2312
编码的,而可执行程序执行这条语句时,是对这个字符串以 UTF-8 解码的,这样就出现了乱码。
解决这个问题的办法有两种,一种是使用 QStringLiteral()
宏封装字符串,另一种方法是强制 MSVC 编译器生成的可执行文件使用 UTF-8 编码。QStringLiteral(str)
宏在编译时将一个字符串 str 生成字符串数据,并且存储在编译后文件的只读数据段中,程序运行时使用到此字符串时,只需要读出此字符串数据即可。所以,我们可以这么写:
cpp
QString str = QStringLiteral("当前电量:") + QString::asprintf("%d%%", arg1);
程序中需要使用 QStringLiteral() 宏对每个中文字符串进行封装,并且不能再使用 tr() 函数用于翻译字符串;强制 MSVC 编译器采用 UTF-8 编码生成可执行文件,需要在每个使用到中文字符串的头文件和源文件的前部加上如下语句:
cpp
#if __MSC_VER >= 160
#pragma execution_character_set("utf-8")
#endif