上篇使用提升法(promotion)来创建自定义组件,本篇为 UI 设计器设计自定义界面组件的 Widget 插件,直接安装到 UI设计器的组件面板里,如同 Qt 自带的界面设计组件一样使用,在设计时就能看到组件的实际显示效果,只是编译和运行时需要使用到插件的动态链接库 (Windows 平台上)
,在需要使用到的时候再细读此文。
Qt提供两种设计插件的 API,可以用于扩展 Qt 的功能。
高级 (high-level) API用于设计插件以扩展 Qt 的功能,例如定制数据库驱动、图像格式、文本编码、定制样式等,Qt Creator 里大量采用了插件,单击 Qt Creator 的主菜单栏的“Help”-“About Plugins”菜单项,会显示Qt Creator里已经安装的各种插件。
低级 (low-level)API 用于创建插件以扩展自己编写应用程序的功能,最常见的就是将自定义Widget 组件安装到UI 设计器里,用于窗口界面设计。
本节创建一个与 12.1节的QmyBattery 功能一样的类 QwBattery,但是采用创建Qt Designer插件的方式来创建这个类,并将其安装到UI设计器的组件面板里。
要创建 UI设计器插件类,单击 Qt Creator 的“File”-“New File or Project”菜单,在出现的对话框里选择“Other Project”分组的“Qt Custom Designer Widget”项目,会出现一个向导对话框。按照这个向导的操作逐步完成项目创建。
第1步是设置插件项目的名称和保存路径,本实例设置项目名称为 QwBatteryPlugin。
第2步是选择项目编译器,可以选择多个编译器,在编译时,再选择具体的编译器。但是实际上只有 MSVC2015 32bit 编译器是能用的。
注意: 使用 Qt创建的 Widget 插件,若要在 Qt Creator 的 Ul设计器里正常显示,编译插件的编译器版本必须
和编译Qt Creator 的版本一致。
Qt 5.9的 Qt Creator 是基于 MSVC2015 32bit 编译器编译的(单击 Qt Creator 的“Help”-“AboutQt Creator”菜单,出现的对话框里会显示 Qt Creator 的版本信息和使用的编译器信息)。所以,为了在Qt Creator 里设计窗体时能够正常显示插件,只能使用 Qt 5.9 MSVC2015 32bit 编译器。
第 3 步是设置自定义 QWidget 类的名称 (见下图),只需在左侧的 Widget classes 列表里设置类名,右侧就会自动设置缺省的文件名,这里添加一个类QwBatery。还可以选择一个图标文件作为自定义组件在UI设计器组件面板里的显示图标。
在图 12-3 的 Description 页还可以设置 Group、Tooltip 和 What’s this 等信息,Group 是自定义组件在组件面板里的分组名称,这里设置为“My Widget”
第 4 步是显示和设置插件、资源文件名称。本实例缺省的插件名称是 qwbatteryplugin,资源文件名称为icons.qrc,一般用缺省的即可。
第5步,完成设置,生成项目。
完成设置后生成的项目的文件组织结构如下图所示,这些文件包括以下几个。
qwbatteryplugin.h 文件中的内容是对插件类QwBatteryPlugin 的定义,类定义完整代码如下:
#ifndef QWBATTERYPLUGIN_H
#define QWBATTERYPLUGIN_H
#include
class QwBatteryPlugin : public QObject, public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
#if QT_VERSION >= 0x050000
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface")
#endif // QT_VERSION >= 0x050000
public:
QwBatteryPlugin(QObject *parent = 0);
bool isContainer() const;
bool isInitialized() const;
QIcon icon() const;
QString domXml() const;
QString group() const;
QString includeFile() const;
QString name() const;
QString toolTip() const;
QString whatsThis() const;
QWidget *createWidget(QWidget *parent);
void initialize(QDesignerFormEditorInterface *core);
private:
bool m_initialized;
};
#endif
QwBatteryPlugin 类实现了 QDesignerCustomWidgetInterface 接口,这是专门为Qt Designer设计自定义 Widget 组件的接口。
在这个类定义里,除了Q_OBJECT 宏之外,还用了Q_INTERFACES 宏声明了实现的接口用Q_PLUGIN_METADATA 声明了元数据名称,这些都无需改动。
public 部分的函数都是有关插件信息或功能的一些函数,通过其实现代码可以看出这些函数的功能。
下面是 qwbatteryplugin.cpp 文件里的实现代码。
#include "qwbattery.h"
#include "qwbatteryplugin.h"
#include
QwBatteryPlugin::QwBatteryPlugin(QObject *parent)
: QObject(parent)
{
m_initialized = false;
}
void QwBatteryPlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (m_initialized)
return;
m_initialized = true;
}
bool QwBatteryPlugin::isInitialized() const
{//是否初始化
return m_initialized;
}
QWidget *QwBatteryPlugin::createWidget(QWidget *parent)
{//返回自定义Widget组件的实例
return new QwBattery(parent);
}
QString QwBatteryPlugin::name() const
{//自定义Widget组件类的名称
return QLatin1String("QwBattery");
}
QString QwBatteryPlugin::group() const
{//在组件面板中所属分组名称
return QLatin1String("MyWidget");
}
QIcon QwBatteryPlugin::icon() const
{//图标文件名
return QIcon(QLatin1String(":/44.ico"));
}
QString QwBatteryPlugin::toolTip() const
{//toolTip信息
return QLatin1String("Battery charger indicator");
}
QString QwBatteryPlugin::whatsThis() const
{//whatsThis 信息
return QLatin1String("A battery charger indicator");
}
bool QwBatteryPlugin::isContainer() const
{ //是否作为容器, false表示该组件上不允许再放其他组件
return false;
}
QString QwBatteryPlugin::domXml() const
{//XML文件描述信息
return QLatin1String("\n \n");
}
QString QwBatteryPlugin::includeFile() const
{//包含文件名
return QLatin1String("qwbattery.h");
}
#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(qwbatteryplugin, QwBatteryPlugin)
#endif // QT_VERSION < 0x050000
这些函数的部分内容是根据创建插件向导里设置的内容自动生成的。createWidget()函数创建一个QwBattery 类的实例,在 UI设计器里作为设计实例;name()函数返回组件的类名称; group()函数设置组件安装在面板里的分组名称; icon()设置组件的图标; isContainer()设置组件是否作为容器,false 表示不作为容器,不能在这个组件上放置其他组件;domXml()函数用XML 设置组件的一些属性,缺省的只设置了类名和实例名。
QwBatteryPlugin.pro 是插件项目的项目管理文件,其内容如下:
CONFIG += plugin debug_and_release
TARGET = $$qtLibraryTarget(qwbatteryplugin)
TEMPLATE = lib
HEADERS = qwbatteryplugin.h
SOURCES = qwbatteryplugin.cpp
RESOURCES = icons.qrc
LIBS += -L.
greaterThan(QT_MAJOR_VERSION, 4) {
QT += designer
} else {
CONFIG += designer
}
target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target
include(qwbattery.pri)
CONFIG是用于qkame 编译设置的,这里配置为:
CONFIG+= plugin debug_and_release
其中,plugin 表示项目要作为插件,编译后只会产生 lib 和 dIl(或.so)文件,debug_and_release表示项目可以用 debug和release 模式编译。
TEMPLATE定义项目的类型,这里设置为:
TEMPLATE = 1ib
这表示项目是一个库,一般的应用程序模板类型是 app。
qwbattery.pri 是内置于QwBatteryPlugin.pro 中的项目,qwbattery.pri项目配置文件只有两行,也就是这个内置项目中包含的头文件和源文件名称。
HEADERS += qwbattery.h
SOURCES += qwbattery.cpp
qwbattery.h 里的内容是对组件类QwBattery 的类定义,其功能与12.1节中的QmyBattery 类完全一样。这两个类的名称之所以不同,是为了在编译两个实例时不产生冲突。
QwBattery 类的定义与QmyBattery 的定义基本一样,只是在声明类的时候需要加一个宏ODESIGNER_WIDGET_EXPORT,并且用Q_PROPERTY宏定义了一个属性 powerLevel。OwBattery类的完整定义如下:
#ifndef WBATTERY_H
#define WBATTERY_H
#include
//#include
#include
#include
class QDESIGNER_WIDGET_EXPORT QwBattery : public QWidget
{
Q_OBJECT
//自定义属性
Q_PROPERTY(int powerLevel READ powerLevel WRITE setPowerLevel NOTIFY powerLevelChanged DESIGNABLE true)
private:
QColor mColorBack=Qt::white;//背景颜色
QColor mColorBorder=Qt::black;//电池边框颜色
QColor mColorPower=Qt::green;//电量柱颜色
QColor mColorWarning=Qt::red;//电量短缺时的颜色
int mPowerLevel=60;//电量0-100
int mWarnLevel=20;//电量低警示阈值
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
public:
explicit QwBattery(QWidget *parent = 0);
void setPowerLevel(int pow);//设置当前电量
int powerLevel();
void setWarnLevel(int warn);//设置电量低阈值
int warnLevel();
QSize sizeHint();//报告缺省大小
signals:
void powerLevelChanged(int );
public slots:
};
#endif // WBATTERY_H
QDESIGNER_WIDGET_EXPORT 宏用于将自定义组件类从插件导出给Qt Designer 使用,必须在类名称前使用此宏。
Q_PROPERTY 宏用于定义属性,这里定义了一个int 类型的属性 powerLevel。READ 宏声明了属性的读取函数是 powerLevel(); WRITE 宏声明了设置属性值的函数是 setPowerLevel();NOTIFY 宏声明了其值变化时发射的信号是 powerLevelChanged(); DESIGNABLE 宏定义属性在UI设计器里是否可见,缺省为 true。
将从QWidget 继承的子类 QwBattery 作为插件安装到 UI设计器的组件面板里,则在设计期间就可以从属性编辑器里看到这个 powerLevel 属性并进行设置。
QwBattery 类的实现代码与QmyBattery 的实现代码完全相同,不再列出。
使用MSVC2015 32bit 编译器,将插件项目在release 模式下编译,编译后会生成qwbatteryplugin.dll和qwbatteryplugin.lib 两个文件。
qwbatteryplugin.dll是插件的动态链接库文件,需要将此文件复制到 Ot Creator 的插件目录和Qt 的插件目录下。例如,要把 Qt 安装到 D:\Qt\Qt5.9.1 目录下,就需要将 qwbatteryplugin.dil 复制到如下两个目录下:
D:\Qt\Qt5.9.1\Tools\QtCreator\bin\plugins\designer
D:\Qt\Qt5.9.1\5.9.1\msvc2015\plugins\designer
重启Qt Creator,使用UI设计器设计窗口时,在左侧的组件面板里会看到增加了一个“MyWidget’分组,里面有一个组件 QwBattery。
编译和安装Widget插件必须注意以下事项:
要让插件在 Qt creator 的 UI 设计器里正常显示,编译插件项目的编译器必须与编译 QtCreator 的编译器一致,否则,即使将编译后生成的 DLL 文件复制到 Qt 的目录下,Qt Creator的 UI设计器的组件面板里也不会出现自定义的组件。例如,Qt Creator 4.3.1 是基于 Qt 5.9.1和MSVC2015 32 bit 编译器 (单击Qt Creator 的“Hep”->“About Qt Creator”菜单项可以看到这些信息),那么编译插件就必须使用 Qt 5.9.1 MSVC2015 32 bit 编译器。
用debug和release 模式编译的插件也分别只适用于 debug 和release 模式编译的应用程序。在 debug 模式下编译的插件项目生成的 Lib 和 DLL 文件会在文件名最后自动增加一个字母“d”,例如,本项目在 debug 模式下编译生成的是qwbatteryplugind.dll和qwbatteryplugind.lib,这两个文件应用于使用此插件的应用程序的 debug 模式。
在Qt Creator 的 UI 设计器的组件面板里能正常显示自定义的 QwBattery 组件后,就可以在窗体设计时使用QwBattery 组件了。
创建一个基于 QWidget 的实例应用程序 BatteryUser。设计窗体时,从组件面板上拖放一个QwBattery 到窗体上,窗体的功能与实例 samp12_1的窗体相同,但是在设计窗体时,就能直接看到QwBattery 绘制的电池图形,在属性编辑器里可以编辑 QwBattery 组件的 powerLevel属性 (见图12-5),在其“Go to slot”对话框里会出现自定义的信号 powerLevelChanged(int),可以为此信号设计槽函数。
下面的代码实现的是利用滑动条设置 battery 的当前电量值,在 battery 的 powerLevelChanged()信号的槽函数里,将当前电量值显示在标签里,程序运行后就可以实现与图 12-1 相同的功能。
void Widget::on_horizontalSlider_valueChanged(int value)
{
ui->battery->setPowerLevel(value);
}
void Widget::on_battery_powerLevelChanged(int arg1)
{
QString str=QStringLiteral("当前电量:")+QString::asprintf("%d %%",arg1);
ui->LabInfo->setText(str);
}
注意:项目 BatteryUser 只能用 MSVC2015 32bit 编译器进行编译,因为使用的 Widget 插件类 QwBattery 是
用MSVC2015 32bit 编译的。
要正常编译项目 BatteryUser,还需要做以下设置。
完成“Add Library”对话框的设置后,Qt Creator 会自动修改项目文件 BatteryUser.pro 的内容,在其中添加了以下几行:
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/include/ -lqwbatteryplugin
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/include/ -lqwbatteryplugind
LIBS 用于设置添加的库文件,会判断当前项目是以 debug 还是 release 模式编译,自动加入qwbatteryplugin.lib或qwbatteryplugind.lib 库文件。
INCLUDEPATH 和 DEPENDPATH 用于设置头文件目录和项目依赖项目录,都指向项目路径下的 include目录。
这样设置后,项目就可以在 release 或 debug 模式下编译了,同样只能使用 MSVC2015 32bit编译器。
注意: 要运行应用程序,还需要将插件的 DLL 文件复制到编译后的 relcase 或 debug 版本的可执行文件目录下,在本例中就是qwbatteryplugin.dll 文件或qwbatteryplugind.dll 文件,因为应用程序运行需要相应的DLI文件。在应用程序发布时,也需要将 DLL 文件随同应用程序发布。
自定义 Widget 插件的功能使得我们可以扩展 QtCreator 的组件种类,设计自己需要的组件。也有许多第三方 Widget 插件可供直接使用,减少自己编程的工作量,例如 QWT 就是一套非常好的开源 Widget 插件。
在 Qt Creator 中使用MSVC 编译器编译项目时,若处理不当容易出现中文字符串乱码问题。例如 BatteryUser 项目中,如果槽函数 on_battery_powerLevelChanged()的代码改写为如下的形式,程序运行时,LabInfo 显示的汉字就会出现乱码。
void Widget::on_battery_powerLevelChanged(int arg1)
{
QString str="当前电量:"+QString::asprintf("%d %%",arg1);
ui->LabInfo->setText(str);
}
这是因为 Qt Creator 保存的文件使用的是 UTF-8 编码(是任何平台、任何语言都可以使用的跨平台的字符集),MSVC 编译器虽然可以正常编译带 BOM 的UTF-8 编码的源文件,但是生成的可执行文件的编码是 Windows 本地字符集,比如 GB2312。也就是在可执行文件中,字符串“前电量:”是以GB2312 编码的,而可执行程序执行到这条语句时,对这个字符串却是以UTF-8 解码的,这样就会出现乱码。
解决这个问题有两种方法,一种方法是使用 QStringLiteral0宏封装字符串,另一种方法是强制MSVC编译器生成的可执行文件使用UTF-8 编码。
QStringLiteral(str)宏在编译时将一个字符串 str 生成字符串数据,并且存储在编译后文件的只读数据段中,程序运行时使用到此字符串时,只需读出此字符串数据即可。所以,BatteryUser 项目中槽函数on_battery_powerLevelChanged()中使用的一行代码如下:
QString str=QStringLiteral("当前电量:")+QString::asprintf("%d %%",arg1);
程序中需要使用 QStringLiteral()宏对每个中文字符串进行封装,并且不能再使用 tr()函数用于翻译字符串
强制 MSVC 编译器采用 UTF-8 编码生成可执行文件,需要在每个使用到中文字符的头文件和源程序文件的前部加入如下的语句:
// 若MSVC 编译版本错误,修改 msvc-version.conf 文件
// 解决MSVC编译时,界面汉字乱码的问题
#if _MSC_VER >= 1600 //MSVC2015>1899, MSVC_VER= 14.0
#pragma execution_character_set("utf-8")
#endif
MSVC2010 以后的编译器可以使用此方案,这是强制编译后的执行文件采用 UTF-8 编码。这样,即使不再使用 QStringLiteral()宏,程序运行时也不会再出现汉字乱码的问题了。而且,也可以采用 tr()函数用于翻译字符串。
此处涉及代码量过多,使用到时参考附带的源文件