一、创建Qt Designer Widget插件

Qt提供两种设计插件的API,可以用于扩展Qt的功能

  • 高级API:用于设计插件以扩展Qt的功能,例如定制数据库驱动、图像格式、文本编码、定制样式等。Qt有大量采用了插件,单击Qt Creator的主菜单栏的“Help” ==>“About Plugins”菜单项,会显示Qt Creator里已经安装的各种插件

Qt:54---自定义Qt Designer插件_应用程序

  • 低级API:用于创建插件以扩展自己编写应用程序的功能,最常见的就是将自定义Widget组件安装到UI设计器里,用于窗口界面设计
二、UI设计器插件的设计

第一步

  • 单击Qt Creator的“File”→“New File or Project”菜单,然后选择“Qt Custom Designer Widget”

Qt:54---自定义Qt Designer插件_ico_02

第二步

  • 设置项目名称为“QwBatteryPlugin”

Qt:54---自定义Qt Designer插件_应用程序_03

第三步

  • 选择编译器的版本
  • 使用Qt创建的Widget插件,若要在Qt Creator的UI设计器里正常显示,编译插件的编译器版本必须和编译Qt Creator的版本一致
  • 可以通过点击Qt Creator的“Help”→“About Qt Creator”菜单查看Qt Creator的版本

Qt:54---自定义Qt Designer插件_自定义_04

  • 由于版本为msvc 2015 32bit,所以下面也要选择MSVC2015 32bit

Qt:54---自定义Qt Designer插件_应用程序_05

第四步

  • 设置自定义QWidget类的名称,此处设为“QwBattery”。在Icon file处选择一个图片,作为自定义组件在UI设计器组件面板里的显示图标

Qt:54---自定义Qt Designer插件_自定义Qt Designer插件_06

  • 在Description页还可以设置Group、Tooltip、What's this等信息,Group是自定义组件在UI面板里的分组名称,此处我们设置为“My Widget”

Qt:54---自定义Qt Designer插件_ico_07

第五步

  • 此处设置插件、资源文件名称。我们采用默认值

Qt:54---自定义Qt Designer插件_自定义Qt Designer插件_08

第六步

之后生成这些文件

  • QwBatteryPlugin.pro:插件项目的项目文件,用于实现插件接口
  • qwbatteryplugin.h和qwbatteryplugin.cpp:分别是插件的头文件和实现文件
  • icons.qrc:插件项目的资源文件,存储了图标
  • qwbattery.pri:是包含在QwBatteryPlugin.pro项目中的一个项目文件,是第四步图中勾选“Include project”产生的,用于管理自定义组件类
  • qwbattery.h和qwbattery.cpp:分别是自定义类QwBattery的头文件和实现文件

Qt:54---自定义Qt Designer插件_应用程序_09

第七步

  • qwbatteryplugin.h的内容如下,这些成员都是系统自定义的(是对插件类QwBatteryPlugin的定义)
  • QwBatteryPlugin类实现了QDesignerCustomWidgetInterface结构,这是专门为Qt Designer设计自定义Widget组件的接口
  • Q_INTERFACES宏声明了实现的接口
  • Q_PLUGIN_METADATA声明了元数据名称
  • public部分:有关插件信息或功能的一些函数
#include <QDesignerCustomWidgetInterface>

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;
}

第八步

  • qwbatteryplugin.cpp的内容如下,这些成员也都是系统自定义的
QwBatteryPlugin::QwBatteryPlugin(QObject *parent)
    : QObject(parent)
{
    m_initialized = false;
}

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)
{   //创建并返回自定义Widget组件的实例
    return new QwBattery(parent);
}

QString QwBatteryPlugin::name() const
{   //返回自定义Widget组件类的名称
    return QLatin1String("QwBattery");
}

QString QwBatteryPlugin::group() const
{   //返回在组件面板中所属分组名称
    return QLatin1String("My Widget");
}

QIcon QwBatteryPlugin::icon() const
{   //返回图标文件名
    return QIcon(QLatin1String(":/battery.ico"));
}

QString QwBatteryPlugin::toolTip() const
{   //toolTip信息
    return QLatin1String("Battery charger indicator");
}

QString QwBatteryPlugin::whatsThis() const
{   //what's this的信息
    return QLatin1String("A battery charger indicator");
}

bool QwBatteryPlugin::isContainer() const
{   //是否作为容器,false表示该组件上不允许再放其他组件
    return false;
}

QString QwBatteryPlugin::domXml() const
{   //XML文件描述信息
    return QLatin1String("<widget class=\"QwBattery\" name=\"qwBattery\">\n</widget>\n");
}

QString QwBatteryPlugin::includeFile() const
{   //包含文件名
    return QLatin1String("qwbattery.h");
}
#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(qwbatteryplugin, QwBatteryPlugin)
#endif // QT_VERSION < 0x050000

第九步

  • QwBatteryPlugin.pro的内容如下
CONFIG      += plugin debug_and_release #CONFIG采用qkame编译
#plugin表示项目要作为插件,编译后只会产生.lib和.dll(或.so)文件
#debug_and_release表示项目可以用debug和release模式编译

TARGET      = $$qtLibraryTarget(qwbatteryplugin)
TEMPLATE    = lib  #TEMPLATE定义项目的类型,这里使用lib表示项目是一个库,一般的应用程序该处填写的是app

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)

第十步

  • qwbttery.h的内容如下,此处不是系统自定义的,需要我们自己手动给出(也是对组件类QwBattery的类定义,之所以与qwbatteryplugin.h的类名不同,是为了编译时不产生冲突)
  • 将从WEidget继承的子类QwBattery作为插件安装到UI设计器的组件面板里,则在设计期间就可以从属性编辑器里看到这个powerLevel属性并进行设置
  • QDESIGNER_WIDGET_EXPORT宏:用于将自定义组件类从插件导出给Qt Designer使用,必须在QwBattery类名前使用
  • Q_PROPERTY宏:用于定义属性。此处定义了一个int类型的属性powerLevel;READ宏声明了属性的读取函数时powerLevel();WRITE宏生命了设置属性值的函数时setPowerLevel();NOTIFY宏生命了其值变化时发射的信号是powerLevelChanged();DESIGNABLE宏定义属性在UI设计器里是否可见,缺省为true
  • powerLevelChanged(int)信号函数:没有实现,而是当自定义的插件被使用时,使用者调用插件的go to slot,选择powerLevelChanged(int)函数时,在里面实现相应的功能。并且这个信号在调用setPowerLevel函数中emit触发才执行
#include <QWidget>
#include <QDesignerExportWidget>
#include <QColor>
#include <QPainter>
class QDESIGNER_WIDGET_EXPORT QwBattery : public QWidget
{
    Q_OBJECT
    Q_PROPERTY(int powerLevel READ powerLevel WRITE setPowerLevel NOTIFY powerLevelChanged DESIGNABLE true)
public:
    QwBattery(QWidget *parent = 0);
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:
    void setPowerLevel(int pow); //设置当前电量
    int powerLevel(); //得到当前电量
    void setWarnLevel(int warn); //设置电量低阀值
    int warnLevel(); //得到电量低阀值
    QSize sizeHint(); //返回组件的缺省大小
signals:
    //当电量值改变时发射此信号
    void powerLevelChanged(int);
};

第十一步

  • qwbttery.cpp的相关函数定义
void QwBattery::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    
    //设置QPainter的绘图区
    QRect rect(0,0,width(),height());
    painter.setViewport(rect);
    painter.setWindow(0,0,120,50);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);
    
    //设置画笔
    QPen pen;
    pen.setWidth(2);
    pen.setColor(mColorBorder);
    pen.setStyle(Qt::SolidLine);
    pen.setCapStyle(Qt::FlatCap);
    pen.setJoinStyle(Qt::BevelJoin);
    //设置画刷
    QBrush brush;
    brush.setColor(mColorBack);
    brush.setStyle(Qt::SolidPattern);
    
    painter.setPen(pen);
    painter.setBrush(brush);
    
    //改变rect的区域,绘制电池边框
    rect.setRect(1,1,109,48);
    painter.drawRect(rect);
    
    //改变画刷颜色,改变rect的区域,绘制电池的正极头
    brush.setColor(mColorBorder);
    painter.setBrush(brush);
    rect.setRect(110,15,10,20);
    painter.drawRect(rect);
    
    //画电池柱
    if(mPowerLevel>mWarnLevel) //正常颜色电量柱
    {
        brush.setColor(mColorPower);
        pen.setColor(mColorPower);
    }
    else//电量低时电量柱
    {
        brush.setColor(mColorWarning);
        pen.setColor(mColorWarning);
    }
    painter.setBrush(brush);
    painter.setPen(pen);
    if(mPowerLevel>0)//如果当前有电量,绘制电量柱
    {
        rect.setRect(5,5,mPowerLevel,40);
        painter.drawRect(rect);
    }
    
    //绘制电量百分比文字
    QFontMetrics textSize(this->font());
    QString powerStr=QString::asprintf("%d%%",mPowerLevel);
    QRect textRect=textSize.boundingRect(powerStr); //得到字符串的rect
    painter.setFont(this->font());
    pen.setColor(mColorBorder);
    painter.setPen(pen);
    painter.drawText(55-textRect.width()/2,23+textRect.height()/2,powerStr);;
    
}
//设置当前电量
void QwBattery::setPowerLevel(int pow)
{
    mPowerLevel=pow;
    emit powerLevelChanged(pow);
    repaint();
}
 
//得到当前电量
int QwBattery::powerLevel()
{
    return mPowerLevel;
}

//设置电量低阀值
void QwBattery::setWarnLevel(int warn)
{
    mWarnLevel=warn;
    repaint();
}
 
//得到电量低阀值
int QwBattery::warnLevel()
{
    return mWarnLevel;
}

//返回组件尺寸大小
QSize QwBattery::sizeHint()
{
    int H=this->height();
    int W=H*12/5;
    QSize size(W,H);
    return size;
}
三、插件的编译

二中的代码设计好之后,就可以开始编译了

第一步:

  • 用debug和release模式编译的插件分别只适用于debug和release模式编译的应用程序。在debug模式下编译的插件项目生成的lib和dll文件会在文件名最后自动增加一个字母d
  • 此处我们将项目在release模式下、debug模式下都编译一遍

Qt:54---自定义Qt Designer插件_自定义Qt Designer插件_10

  • release编译后会生成qwbatteryplugin.dll和qwbatteryplugin.lib两个文件,debug编译后会生成qwbatteryplugind.dll和qwbatteryplugind.lib两个文件。这两个文件是在bulid的文件夹下,而不是在当前项目的文件夹下

Qt:54---自定义Qt Designer插件_自定义_11Qt:54---自定义Qt Designer插件_自定义_12

第二步:

  • qwbatteryplugin.dll是插件的动态链接库文件,需要将此文件复制到Qt Creator的插件目录和Qt的插件目录下
  • 例如Qt Creator安装到D:\Qt\Qt5.9.1目录下,就需要将qwbatteryplugin.dll复制到如下的两个目录下

Qt:54---自定义Qt Designer插件_应用程序_13

第三步:

  • 重启Qt Creator,随意打开一个项目文件,就可以看到在UI界面生成了我们自定义的组件,并且可以使用

Qt:54---自定义Qt Designer插件_自定义Qt Designer插件_14

四、使用自定义插件

第一步:

  • 创建一个基于QWidget的实例应用程序BatteryUser
  • 然后从UI中拖动我们自定义的组件和一个滚动条和Label标签到界面中

Qt:54---自定义Qt Designer插件_自定义_15

第二步:

想要使用这个插件,还需要做一些设置

  • ①在项目的源文件目录下创建一个include目录(这个目录的名称随意设置)

Qt:54---自定义Qt Designer插件_自定义_16

  • ②将插件的QwBattery类定义的头文件qwbattery.h、插件的debug和release两种模式编译生成的库文件qwbatteryplugin.lib、qwbatteryplugind.lib复制到include目录下,项目在编译链接时需要使用到此头文件和库文件

Qt:54---自定义Qt Designer插件_编译器_17

  • ③右击“BatteryUser”项目,选择“Add Library...”,在出现的向导对话框第一步中,选择库类型时,将外部库“External Libary”选中,因为本项目需要使用的是已经编译好的库文件

Qt:54---自定义Qt Designer插件_ico_18

  • ④在Library file中选择我们刚才在项目的include目录下复制的qwbatteryplugin.lib文件,选择之后会自动填充Include path,平台只选择Windows;连接方式选择Dynamic;下方的Add “d”......表示在debug版本的库名称后面添加一个字符“d”,以便编译器自动区分release和debug版本的库文件

Qt:54---自定义Qt Designer插件_编译器_19

  • ⑤之后会在项目文件中自动添加这几行内容(LIBS用于设置添加的库文件,会判断当前项目是以debug还是release模式编译,自动加入qwbatteryplugin.lib或qwbatteryplugind.lib库文件;INCLUDEPATH和DEPENDPATH用于设置头文件目录和项目依赖项目录,都指向项目路径下的include目录)

Qt:54---自定义Qt Designer插件_应用程序_20

这样设置之后,项目就可以在release或者debug模式下编译了,只能使用MSVC2015 32bit编译器(因为插件使用32bit编译器生成的)

第三步:

  • 要运行应用程序,还需要将插件的在release和debug版本下编译产生的.dll文件复制到项目的release或debug版本的可执行文件(.exe)目录下,因为应用程序运行需要相应的dll文件,在应用程序发布时,也需要将dll文件随同应用程序发布

Qt:54---自定义Qt Designer插件_编译器_21Qt:54---自定义Qt Designer插件_编译器_22

第四步:

  • 项目在编译的时候,我们插件的头文件会显示找不到#include <QDesignerExportWidget>头文件,也就找不到QDESIGNER_WIDGET_EXPORT宏了,因此将这两个删除或注释

Qt:54---自定义Qt Designer插件_自定义Qt Designer插件_23

第五步:

  • 实现滚动条的valueChanged信号,当滚动条滚动时,调用qwbattery插件的setPowerLevel函数(该函数会重绘qwbattery插件,并且emit发射powerLevelChanged信号)

Qt:54---自定义Qt Designer插件_编译器_24

void Widget::on_horizontalSlider_valueChanged(int value)
{   //拖动slider改变battery的电量值
    ui->qwBattery->setPowerLevel(value);
}

第五步:

  • 实现qwbattery插件的powerLevelChanged()信号函数

Qt:54---自定义Qt Designer插件_自定义_25

void Widget::on_qwBattery_powerLevelChanged(int arg1)
{   //电量改变时,在label标签中显示
    QString str=QStringLiteral("当前电量:")+QString::asprintf("%d %%",arg1);
    ui->label->setText(str);
}

第六步

  • 在32位编译器的debug模式下编译演示一下,可以看到功能实现(其他版本的编译器编译会报错,因为插件是在MSVC2015 32bit版本下编译生成的)

Qt:54---自定义Qt Designer插件_应用程序_26Qt:54---自定义Qt Designer插件_自定义_27

五、自定义插件的第2方法
  • 将在UI界面设计好的窗口部件,直接拖拽到“组件箱”,会自动生成“Scratchpad”栏,自定义的组件会放置在这个栏下, 下次直接使用即可
  • 例如将一个TextEdit的组件拖拽到“组件箱”,下次可以直接使用

Qt:54---自定义Qt Designer插件_ico_28