天天看點

從零開始學Qt(67):高階!自定義Qt Designer插件

作者:未來奇兵

本文介紹為UI設計器設計自定義界面元件的Widget插件,直接安裝到UI設計器的元件面闆裡,如同Qt自帶的界面設計元件一樣使用,在設計時就能看到元件的實際顯示效果,隻是編譯和運作時需要使用到插件的動态連結庫(Windows平台上)。

建立 Qt Designer Widget 插件項目

Qt提供兩種設計插件的API,可以用于擴充Qt的功能。

進階(high-level) API用于設計插件以擴充Qt的功能,例如定制資料庫驅動、圖像格式、文本編碼、定制樣式等,Qt Creator裡大量采用了插件,單擊Qt Creator的主菜單欄的“Help” 一“About Plugins”菜單項,會顯示Qt Creator裡已經安裝的各種插件。

低級(low-level) API用于建立插件以擴充自己編寫應用程式的功能,最常見的就是将自定義Widget元件安裝到UI設計器裡,用于視窗界面設計。

本節建立一個與QBattery功能一樣的類,但是采用建立Qt Designer 插件的方式來建立這個類,并将其安裝到UI設計器的元件面闆裡。

要建立UI設計器插件類,單擊Qt Creator的“File” 一 “New File or Project”菜單,在出現的對話框裡選擇“Other Project”分組的“Qt Custom Designer Widget”項目,會出現一個向導對話框。按照這個向導的操作逐漸完成項目建立。

• 第1步:設定插件項目的名稱和儲存路徑

本執行個體設定項目名稱為QBatteryPlugin。

• 第2步:選擇項目編譯器

可以選擇多個編譯器,在編譯時,再選擇具體的編譯器。但是實 際上隻有MSVC2017 32bit編譯器是能用的。

注意使用Qt建立的Widget插件,若要在Qt Creator的UI設計器裡正常顯示,編譯插件的編譯器版本必須和編譯Qt Creator的版本一緻。

從零開始學Qt(67):高階!自定義Qt Designer插件

About Qt Creator對話框

Qt 5.14的 Qt Creator 是基于 MSVC2017 32bit 編譯器編譯的(單擊 Qt Creator 的“Help”一“About Qt Creator”菜單,出現的對話框裡會顯示Qt Creator的版本資訊和使用的編譯器資訊)。是以,為了在Qt Creator裡設計窗體時能夠正常顯示插件,隻能使用Qt 5.14 MSVC2017 32bit編譯器。

• 第3步:設定自定義QWidget類的名稱

隻需在左側的Widget classes清單裡設定類名,右側就會自動設定預設的檔案名,這裡添加一個類QBattery。還可以選擇一個圖示檔案作為自定義元件在UI設計器元件面闆裡的顯示圖示。

從零開始學Qt(67):高階!自定義Qt Designer插件

自定義QWidget類的名稱對話框

在Description頁還可以設定Group、Tooltip和What’s this等資訊,Group是自定義元件在元件面闆裡的分組名稱,這裡設定為“My Widget”。

• 第4步:顯示和設定插件、資源檔案名稱

本執行個體預設的插件名稱是qbatteryplugin,資源檔案名稱為icons.qrc,一般用預設的即可。

• 第5步:完成設定,生成項目

完成設定後生成的項目的檔案組織結構如圖所示,這些檔案包括以下幾個。

從零開始學Qt(67):高階!自定義Qt Designer插件

項目檔案結構

  • QBatteryPlugin.pro是插件項目的項目檔案,用于實作插件接口。
  • qbatteryplugin.h 和 qbatteryplugin.cpp 是插件的頭檔案和實作檔案。
  • icons.qrc是插件項目的資源檔案,存儲了圖示。
  • qbattery.pri是包含在QBatteryPlugin.pro項目中的一個項目檔案(圖中選擇“Include project”),用于管理自定義元件類。
  • qbattery.h和qbattery.cpp是自定義類QBattery的頭檔案和實作檔案。

插件項目各檔案的功能實作

(1)QBatteryPlugin類

qbatteryplugin.h檔案中的内容是對插件類QBatteryPlugin的定義,類定義完整代碼如下(自動生成,無需修改):

#include <QDesignerCustomWidgetInterface>
class QBatteryPlugin : 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:
    QBatteryPlugin(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;
};           

QBatteryPlugin類實作了QDesignerCustomWidgetlnterface接口,這是專門為Qt Designer 設計自定義Widget元件的接口。

在這個類定義裡,除了Q_OBJECT宏之外,還用Q_INTERFACES宏聲明了實作的接口,用Q_PLUGIN_METADATA聲明了中繼資料名稱,這些都無需改動。

public部分的函數都是有關插件資訊或功能的一些函數,通過其實作代碼可以看出這些函數 的功能。

下面是qbatteryplugin.cpp檔案裡的實作代碼(自動生成,無需修改)。

#include "qbattery.h"
#include "qbatteryplugin.h"
#include <QtPlugin>
  
QBatteryPlugin::QBatteryPlugin(QObject *parent)
: QObject(parent)
{
	m_initialized = false;
}
void QBatteryPlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
  if (m_initialized)
  	return;
  // Add extension registrations, etc. here
  m_initialized = true;
}
bool QBatteryPlugin::isInitialized() const
{//是否初始化
	return m_initialized;
}
QWidget *QBatteryPlugin::createWidget(QWidget *parent)
{//傳回自定義Widget元件的執行個體
	return new QBattery(parent);
}
QString QBatteryPlugin::name() const
{//自定義Widget元件類的名稱
	return QLatin1String("QBattery");
}
QString QBatteryPlugin::group() const
{//在元件面闆中所屬分組名稱
	return QLatin1String("My Widget");
}
QIcon QBatteryPlugin::icon() const
{//圖示檔案名
	return QIcon(QLatin1String(":/battery.jpg"));
}
QString QBatteryPlugin::toolTip() const
{//toolTip 資訊
	return QLatin1String("Battery charger indicator");
}
QString QBatteryPlugin::whatsThis() const
{//whatsThis 資訊
	return QLatin1String("A battery charger indicator");
}
bool QBatteryPlugin::isContainer() const
{//是否作為容器,false表示該元件上不允許再放其他元件
	return false;
}
QString QBatteryPlugin::domXml() const
{//XML檔案描述資訊
	return QLatin1String("<widget class=\"QBattery\" name=\"qBattery\">\n</widget>\n");
}
QString QBatteryPlugin::includeFile() const
{//包含檔案名
	return QLatin1String("qbattery.h");
}
#if QT_VERSION < 0x050000
	Q_EXPORT_PLUGIN2(qbatteryplugin, QBatteryPlugin)
#endif // QT_VERSION < 0x050000           

這些函數的部分内容是根據建立插件向導裡設定的内容自動生成的。createWidget()函數建立一個QBattery類的執行個體,在UI設計器裡作為設計執行個體;name()函數傳回元件的類名稱;group()函數設定元件安裝在面闆裡的分組名稱;icon()設定元件的圖示;isContainer()設定元件是否作為容器,false表示不作為容器,不能在這個元件上放置其他元件;domXml()函數用XML設定元件的一些屬性,預設的隻設定了類名和執行個體名。

(2)QBatteryPlugin.pro 的内容

QBatteryPlugin.pro是插件項目的項目管理檔案,其内容如下:

CONFIG += plugin debug_and_release
TARGET = $qtLibraryTarget(qbatteryplugin)
TEMPLATE = lib
HEADERS = qbatteryplugin.h
SOURCES = qbatteryplugin.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(qbattery.pri)           

CONFIG是用于qkame編譯設定的,這裡配置為:

CONFIG += plugin debug_and_release           

其中,plugin表示項目要作為插件,編譯後隻會産生lib和dll(或.so)檔案,debug_and_release 表示項目可以用debug和release模式編譯。

TEMPLATE定義項目的類型,這裡設定為:

TEMPLATE = lib           

這表示項目是一個庫,一般的應用程式模闆類型是app。

(3)内置項目 qbattery.pri

qbattery.pri是内置于QBatteryPlugin.pro中的項目,qwbattery.pri項目配置檔案隻有兩行,

也就是這個内置項目中包含的頭檔案和源檔案名稱。

HEADERS += qbattery.h
SOURCES += qbattery.cpp           

(4)元件類QBattery的定義

qbattery.h裡的内容是對元件類QBattery的類定義,其功能與前一篇文章中的QBattery類完全一樣。這兩個類的名稱之是以不同,是為了在編譯兩個執行個體時不産生沖突。

QBattery類的定義在聲明類的時候需要加一個宏QDESIGNER_WIDGET_EXPORT,并且用 Q_PROPERTY宏定義了一個屬性powerLevel。

QBattery類的完整定義如下:

#include <QWidget>
#include <QDesignerExportWidget>
class QDESIGNER_WIDGET_EXPORT QBattery : 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 QBattery(QWidget *parent = 0);
    void setPowerLevel (int pow); //設定目前電量
    int powerLevel();
    void setWarnLevel(int warn);//設定電量低門檻值
    int warnLevel();
    QSize sizeHint();//報告預設大小
  signals:
  	void powerLevelChanged(int );
  public slots:
};           

QDESIGNER_WIDGET_EXPORT宏用于将自定義元件類從插件導出給Qt Designer使用,必須在類名稱前使用此宏。

Q_PROPERTY宏用于定義屬性,這裡定義了一個int類型的屬性powerLevel。READ宏聲明 了屬性的讀取函數是powerLevel(); WRITE宏聲明了設定屬性值的函數是setPowerLevel(); NOTIFY宏聲明了其值變化時發射的信号是powerLevelChanged(); DESIGNABLE宏定義屬性在 UI設計器裡是否可見,預設為true。

将從QWidget繼承的子類QBattery作為插件安裝到UI設計器的元件面闆裡,則在設計期間就可以從屬性編輯器裡看到這個powerLevel屬性并進行設定。

QBattery類的實作代碼與之前的實作代碼完全相同,不再列出。

插件的編譯與安裝

使用MSVC2017 32bit編譯器,将插件項目在release模式下編譯,編譯後會生成qbatteryplugin.dll 和 qbatteryplugin.lib 兩個檔案。

qbatteryplugin.dll是插件的動态連結庫檔案,需要将此檔案複制到Qt Creator的插件目錄和 Qt的插件目錄下。例如,要是把Qt安裝到了C:\Qt\Qt5.14.2目錄下,就需要将qbatteryplugin.dll複制到如下兩個目錄下:

C:\Qt\Qt5.14.2\Tools\QtCreator\bin\plugins\designer
C:\Qt\Qt5.14.2\5.14.2\msvc2017\plugins\designer           

重新開機Qt Creator,使用UI設計器設計視窗時,在左側的元件面闆裡會看到增加了一個“My Widget” 分組,裡面有一個元件QBattery。

編譯和安裝Widget插件必須注意以下事項。

  • 要讓插件在Qt creator的UI設計器裡正常顯示,編譯插件項目的編譯器必須與編譯Qt Creator的編譯器一緻,否則,即使将編譯後生成的DLL檔案複制到Qt的目錄下,Qt Creator 的UI設計器的元件面闆裡也不會出現自定義的元件。例如,Qt Creator 4.11.1是基于Qt 5.14.2和MSVC2017 32 bit編譯器(單擊 Qt Creator 的 “Help” 一 “About Qt Creator” 菜單項可以看到這些資訊),那麼編譯插件就必須使用Qt 5.12.2 MSVC2017 32 bit編譯器。
  • 用debug和release模式編譯的插件也分别隻适用于debug和release模式編譯的應用程式。在debug模式下編譯的插件項目生成的Lib和DLL檔案會在檔案名最後自動增加一個字母“d”,例如,本項目在debug模式下編譯生成的是 qbatteryplugind.dll和 qbatteryplugind.lib,這兩個檔案應用于使用此插件的應用程式的debug模式。

使用自定義插件

在Qt Creator的UI設計器的元件面闆裡能正常顯示自定義的QBattery元件後,就可以在窗 體設計時使用QBattery元件了。

從零開始學Qt(67):高階!自定義Qt Designer插件

使用自定義插件設計界面

建立一個基于QWidget的執行個體應用程式BatteryUser。設計窗體時,從元件面闆上拖放一個 QBattery到窗體上。在設計窗體時,就能直接看到QBattery繪制的電池圖形,在屬性編輯器裡可以編輯QBattery元件的powerLevel屬性,在其“Go to slot”對話框裡會出現自定義的信号powerLevelChanged(int),可以為此信号設計槽函數。

下面的代碼實作的是利用滑動條設定battery的目前電量值,在battery的powerLevelChanged()信号的槽函數裡,将目前電量值顯示在标簽裡,程式運作後就可以實作上一篇文章示例中相同的功能。

void Widget::on_horizontalSlider_valueChanged(int value)
{ // 拖動slider改變battery的電量
	ui->battery->setPowerLevel(value);
}
void Widget::on_battery_powerLevelChanged(int arg1)
{ //電量值改變時,在标簽中顯示
	QString str=QStringLiteral("目前電量:")+QString::asprintf ("%d %%", arg1);
	ui->labInfo->setText(str);
}           

注意項目BatteryUser隻能用MSVC2017 32bit編譯器進行編譯,因為使用的Widget插件類QBattery是用MSVC2017 32bit 編譯的。

要正常編譯項目BatteryUser還需要做以下設定。

  • 在項目的源檔案目錄下建立一個include子目錄(名稱随個人喜好設定),将QBattery類定義的頭檔案qbattery.h、插件的debug和release兩種模式編譯生成的庫檔案qbatteryplugin.lib 以及qbatteryplugind.lib複制到此目錄下,項目在編譯連結時需要使用此頭檔案和庫檔案。
  • 在項目管理器中,選中BatteryUser項目節點并單擊右鍵,在快捷菜單中單擊“Add Library…”,在出現的向導對話框第一步中,選擇庫類型時,将外部庫“External Library” 選中,因為本項目需要使用的是己經編譯好的庫檔案。
從零開始學Qt(67):高階!自定義Qt Designer插件

Add Library對話框

  • 在向導的第二步,單擊“Library file”編輯框後面的按鈕,選擇include目錄 下的庫檔案qwbatteryplugin.lib,會自動填充“Include path”編輯框。在平台選擇中可以隻選擇一個 windows平台,連接配接方式選擇Dynamic,下方的 Add “d” suffix for debug version 表示在debug版本的庫名稱後面添加一個字母“d”,以便編譯器自動區分release和debug 版本的庫檔案。

完成“Add Library”對話框的設定後,QtCreator會自動修改項目檔案BatteryUser.pro的内容,在其中添加了以下幾行:

win32:CONFIG(release, debug|release): LIBS += -L$PWD/include/ -lqbatteryplugin
else:win32:CONFIG(debug, debug|release): LIBS += -L$PWD/include/ -lqbatteryplugind
INCLUDEPATH += $PWD/include
DEPENDPATH += $PWD/include           

LIBS用于設定添加的庫檔案,會判斷目前項目是以debug還是release模式編譯,自動加入 qbatteryplugin.lib 或 qbatteryplugind.lib 庫檔案。

INCLUDEPATH和DEPENDPATH用于設定頭檔案目錄和項目依賴項目錄,都指向項目路徑 下的include目錄。

這樣設定後,項目就可以在release或debug模式下編譯了,同樣隻能使用MSVC2017 32bit編譯器。 注意要運作應用程式,還需要将插件的DLL檔案複制到編譯後的release或debug版本的可執行檔案目錄下,在本例中就是qbatteryplugin.dll檔案或qbatteryplugind.dll檔案,因為應用程式運作需要相應的DLL檔案。在應用程式釋出時,也需要将DLL檔案随同應用程式釋出。

從零開始學Qt(67):高階!自定義Qt Designer插件

運作示意圖

自定義Widget插件的功能使得我們可以擴充Qt Creator的元件種類,設計自己需要的元件。 也有許多第三方Widget插件可供直接使用,減少自己程式設計的工作量,例如QWT就是一套非常好的開源Widget插件。

繼續閱讀