目錄
- 前言
- 一、cutewidgets是什麼?
- 二、工程結構
- 三、架構的工程配置
-
- 1 cutewidgets.pro
- 2 cutewidgets.pri
-
- 2.1 cutewidgetsconfig.pri
- 2.2 cutewidgetsfunctions.pri
- 2.3 cutewidgetsbuild.pri
- 四、源碼
-
- 1 src
-
- 1.1 src.pro
- 1.2 cutewidgets_global.h
- 1.3 testedit
- 1.4 擴充
- 2 examples
-
- 2.1 examples.pro
- 2.2 examples.pri
- 2.3 testedit
- 2.4 拓展
- 3 designer
-
- 3.1 designer.pro
- 3.2 designer_plugin.h和designer_plugin.cpp
- 3.3 資源檔案
- 五、如何使用自定義控件
-
- 1 确認QT環境下的cutewidgets部署情況
- 2 在Qt Creator中使用
前言
網上關于QT自定義控件的介紹很多,本文不做具體自定義控件編寫的詳細介紹,本文的主要目的是介紹一個自己在用的一個工程架構cutewidgets。
一、cutewidgets是什麼?
cutewidgets是一個編寫Qt Designer自定義控件的架構工程,主要作用的友善自動化部署到本地QT環境,其中也包含測試例程的編寫,友善測試。内含一個簡單的自定義控件和測試例程。
友情提示:新手在看下面内容時,最好先下載下傳源碼,對照閱讀,效果會更好。老手就不用了,看個思路就行,當然若是老手能使用這個架構,那也不勝榮幸。
源碼放在GitHub上了,可自行下載下傳。本來是想放在gitee上的,無奈因為一些認證原因,就沒折騰。GitHub的無奈用過的人都知道。
二、工程結構

項目 | 說明 |
---|---|
Git相關 | .git,.gitattributes,.gitignore,README.md為git版本控制相關檔案,不做解釋 |
bin | 測試例程運作目錄 |
designer | 自定義控件插件源碼 |
examples | 測試例程 |
include | 拷貝源碼相關頭檔案的目标路徑,作為普通動态庫提供的頭檔案 |
lib | 動态庫或靜态庫輸出目錄 |
plugin | 自定義控件插件輸出目錄 |
src | 自定義控件源碼 |
配置檔案 | cutewidgets.pri,cutewidgets.pro,cutewidgetsbuild.pri,cutewidgetsconfig.pri,cutewidgetsfunctions.pri工程及編譯相關配置檔案 |
三、架構的工程配置
本部分内容着重介紹cutewidgets架構的工程配置,主要涉及pro,pri等檔案的編寫。
1 cutewidgets.pro
工程的總入口,内容如下:
TEMPLATE = subdirs
CONFIG += ordered
include(cutewidgets.pri)
SUBDIRS += src
contains(CUTEWIDGETS_CONFIG, CuteWidgetsExamples) {
SUBDIRS += examples
}
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDesigner) {
SUBDIRS += designer
}
模闆為子項目subdirs,比較簡單,src預設加載,examples和designer項目按工程配置選擇性加載。
這裡有個小問題,不知是不是Qt Creator的bug,在配置檔案去除examples和designer的配置後,雖然這兩個子項目不參與編譯,但它們還在整個工程結構裡,但可以看到加載的檔案都變灰了。這就引入了一個問題。這些灰掉的檔案在還在使用它們的子項目裡變成了有錯誤的代碼,而QT6版本的Creator有個尿性就是一旦你的檔案有錯誤提示,它的檢視或者補全等便捷的編碼工具都失效了。這樣寫起代碼來就太痛苦了。是以在編碼階段最好不要把examples和designer去掉。
2 cutewidgets.pri
整個工程的公共配置檔案,内容如下:
################################################################################
# CONFIG配置檔案
################################################################################
include($$PWD/cutewidgetsconfig.pri)
################################################################################
# 函數定義
################################################################################
include($$PWD/cutewidgetsfunctions.pri)
################################################################################
# 建構配置
################################################################################
include($$PWD/cutewidgetsbuild.pri)
它其實就是三個配置檔案的包,友善引用,所有引用該檔案的地方都會有這麼一個包出現
2.1 cutewidgetsconfig.pri
顧名思義,這個檔案為配置檔案,内容如下:
################################################################################
# Source paths
################################################################################
CUTEWIDGETS_ROOT = $$PWD
CUTEWIDGETS_OUTPUT_LIB = $$CUTEWIDGETS_ROOT/lib
CUTEWIDGETS_OUTPUT_PLUGIN = $$CUTEWIDGETS_ROOT/plugin
######################################################################
# Install paths
######################################################################
CUTEWIDGETS_INSTALL_PREFIX = $$[QT_INSTALL_PREFIX]
CUTEWIDGETS_INSTALL_DIR_NAME = QtCuteWidgets
CUTEWIDGETS_INSTALL_HEADERS = $${CUTEWIDGETS_INSTALL_PREFIX}/include/$$CUTEWIDGETS_INSTALL_DIR_NAME
CUTEWIDGETS_INSTALL_LIB = $${CUTEWIDGETS_INSTALL_PREFIX}/lib
######################################################################
# Designer plugin
# creator/designer load designer plugins from certain default
# directories ( f.e the path below QT_INSTALL_PREFIX ) and the
# directories listed in the QT_PLUGIN_PATH environment variable.
# When using the path below CUTEWIDGETS_INSTALL_PREFIX you need to
# add $${CUTEWIDGETS_INSTALL_PREFIX}/plugins to QT_PLUGIN_PATH in the
# runtime environment of designer/creator.
######################################################################
CUTEWIDGETS_INSTALL_PLUGINS_FOR_DESIGNER = $${CUTEWIDGETS_INSTALL_PREFIX}/plugins/designer
CUTEWIDGETS_INSTALL_PLUGINS_FOR_CREATOR = $${CUTEWIDGETS_INSTALL_PREFIX}/../../Tools/QtCreator/bin/plugins/designer
######################################################################
# Build the static/shared libraries.
# If CuteWidgetsDll is enabled, a shared library is built, otherwise
# it will be a static library.
######################################################################
CUTEWIDGETS_CONFIG += CuteWidgetsDll
######################################################################
# If you want to build the CuteWidgets designer plugin,
# enable the line below.
# Otherwise you have to build it from the designer directory.
######################################################################
CUTEWIDGETS_CONFIG += CuteWidgetsDesigner
######################################################################
# If you want to auto build the examples, enable the line below
# Otherwise you have to build them from the examples directory.
######################################################################
CUTEWIDGETS_CONFIG += CuteWidgetsExamples
######################################################################
### custom widget below ###
######################################################################
######################################################################
# CuteWidgetsTestEdit enables all classes, that are needed to use the
# CuteWidgetsTestEdit classes.
######################################################################
CUTEWIDGETS_CONFIG += CuteWidgetsTestEdit
前半部分是一些變量的定義,包括編譯輸出路徑和部署的QT環境下的一些路徑定義。
CUTEWIDGETS_INSTALL_DIR_NAME變量做下說明,QT下include檔案夾下都會增加一層子產品的檔案夾,然後才是相關頭檔案定義,為了保持統一,我們也定義一個。
CUTEWIDGETS_INSTALL_DIR_NAME = QtCuteWidgets
CUTEWIDGETS_INSTALL_HEADERS = $${CUTEWIDGETS_INSTALL_PREFIX}/include/$$CUTEWIDGETS_INSTALL_DIR_NAME
再延伸下,pro檔案定義的變量如何在cpp中使用,就拿我們的測試控件裡的内容說明。
我們在designer.pro檔案中定義了CUTEWIDGETS_STR變量,并且指派為CUTEWIDGETS_INSTALL_DIR_NAME
#########################################################################
# 定義QT路徑下include下檔案名,所有install的頭檔案放于
# QT_INSTALL_PRFIX/include/CUTEWIDGETS_STR中
#########################################################################
DEFINES += CUTEWIDGETS_STR=$$CUTEWIDGETS_INSTALL_DIR_NAME
下面我們看看如何在designer_plugin.h中把這個值轉換成c++可識别的變量。
其實主要是這兩行起到的作用
#define STR(R) #R
#define STRVALUE(R) STR(R)
第一行用 # 擷取宏的名稱,第二行擷取宏的值。這樣就擷取了pro檔案的變量,這樣做的目的是為了統一,install的路徑和自定義控件的頭檔案定義就一緻了,其實QT的自定義控件模闆工程的做法是直接把自定義控件的頭檔案放在QT的include檔案下,我們是為了和人家檔案結構保持一緻,才加了這麼個變量,沒辦法,誰叫咱有潔癖呢 (^ _ ^)。
後半部分為CUTEWIDGETS_CONFIG變量的配置,前面提到的配置examples和designer是否加載即在這裡,這裡簡單說下qmake函數contains,熟悉qmake函數的請自行跳過。
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDesigner) {
...
} else {
...
}
就是判斷第一個變量裡是否包含第二個變量,qmake的函數的左大括号必須和函數在一行,不能另一起一行
#錯誤用法
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDesigner)
{
...
} else
{
...
}
最後一段開始是你可以自由發揮的地方,注釋說的也很清楚了,再添加其他自定義控件時,依次添加就好了。
######################################################################
### custom widget below ###
######################################################################
######################################################################
# CuteWidgetsTestEdit enables all classes, that are needed to use the
# CuteWidgetsTestEdit classes.
######################################################################
CUTEWIDGETS_CONFIG += CuteWidgetsTestEdit
CUTEWIDGETS_CONFIG += CuteWidgetsXXX1
CUTEWIDGETS_CONFIG += CuteWidgetsXXX2
類似CuteWidgetsTestEdit這種配置會同時決定src,examples和designer三個子項目工程是否添加該自定義控件。這裡編寫時可以參考自帶的測試控件例程。
DEFINE_CUTEWIDGETS_TESTEDIT不用想也肯定和cutewidgetsconfig.pri裡的CuteWidgetsTestEdit有關,在CuteWidgetsTestEdit決定的testedit.pri檔案裡定義如下:
總之CuteWidgetsTestEdit的目的就是添加或抹除所有和該變量相關的工程檔案,後續再添加的自定義控件的寫法要參照這個測試控件。
2.2 cutewidgetsfunctions.pri
這個檔案比較簡單,就是參照QT的寫法定義了兩個函數
defineReplace(cuteWidgetsLibraryTarget) {
unset(LIBRARY_NAME)
LIBRARY_NAME = $$1
contains(TEMPLATE, .*lib):CONFIG(debug, debug|release) {
!debug_and_release|build_pass {
win32:RET = $$member(LIBRARY_NAME, 0)d
}
}
isEmpty(RET):RET = $$LIBRARY_NAME
return($$RET)
}
defineTest(cuteWidgetsAddLibrary) {
LIB_PATH = $$1
LIB_NAME = $$2
LIBS *= -L$${LIB_PATH}
unset(LINKAGE)
isEmpty(LINKAGE) {
if(!debug_and_release|build_pass):CONFIG(debug, debug|release) {
win32:LINKAGE = -l$${LIB_NAME}d
}
}
isEmpty(LINKAGE) {
LINKAGE = -l$${LIB_NAME}
}
!isEmpty(QMAKE_LSB) {
QMAKE_LFLAGS *= --lsb-shared-libs=$${LIB_NAME}
}
LIBS += $$LINKAGE
export(LIBS)
export(QMAKE_LFLAGS)
export(QMAKE_LIBDIR_FLAGS)
return(true)
}
雖然隻有兩個函數還把defineTest和defineReplace都用上了,沒見過的可以去百度一下,漲漲知識。
cuteWidgetsLibraryTarget這個函數的功能就是如果是debug模式那就在原來的名字上加一個d,這在QT的庫裡太常見了。
cuteWidgetsAddLibrary的功能就是添加依賴庫,第一個變量是路徑,第二個變量是庫名稱,其實就是實作了
LIBS += -Ldir -llib
我想看這個部落格的人不會不知道LIBS的用法的。
2.3 cutewidgetsbuild.pri
這個檔案顧名思義是配置編譯的,内容如下:
######################################################################
# qmake internal options
######################################################################
CONFIG += qt
CONFIG += warn_on
CONFIG += no_keywords
CONFIG += silent
CONFIG -= depend_includepath
CONFIG += c++17
######################################################################
# release/debug mode
######################################################################
win32 {
# On Windows you can't mix release and debug libraries.
# The designer is built in release mode. If you like to use it
# you need a release version. For your own application development you
# might need a debug version.
# Enable debug_and_release + build_all if you want to build both.
#CONFIG += debug_and_release
#CONFIG += build_all
CONFIG += release
}
這裡我沒寫太多内容,因為本人隻在Windows系統上做開發,是以這個架構也沒那麼QT,可以多平台使用,主要是本人沒有其他系統的使用需求,故而未作深入研究。
四、源碼
下面進入源碼的介紹環節。該部分還是主要介紹幾個子項目的工程配置檔案,捎帶說說源碼内容,因為自定義控件源碼的編寫網上的内容太多了,我就不做詳細介紹了。
1 src
- src.pro
- cutewidgets_global.h
- testedit
1.1 src.pro
也是一個子項目,内容如下:
include($$PWD/../cutewidgets.pri)
TEMPLATE = lib
TARGET = $$cuteWidgetsLibraryTarget(cutewidgets)
DESTDIR = $$CUTEWIDGETS_OUTPUT_LIB
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDll) {
CONFIG += dll
DEFINES += CUTEWIDGETS_DLL CUTEWIDGETS_LIBRARY
DLLDESTDIR = $$CUTEWIDGETS_ROOT/bin
}
else {
CONFIG += staticlib
}
INCLUDEPATH += $$CUTEWIDGETS_ROOT/src
unset(INSTALL_INCLUDE_FILES)
INSTALL_INCLUDE_FILES += $$CUTEWIDGETS_ROOT/src/cutewidgets_global.h
#包含指定源碼
contains(CUTEWIDGETS_CONFIG, CuteWidgetsTestEdit) {
include($$CUTEWIDGETS_ROOT/src/testedit/testedit.pri)
}
########################################################################
# 在此新增自定義控件源碼 #
########################################################################
#new pri here
########################################################################
#頭檔案拷貝
########################################################################
target_headers.path = $$CUTEWIDGETS_ROOT/include
target_headers.files = $$INSTALL_INCLUDE_FILES
INSTALLS += target_headers
撿有用的說幾個吧。
項目 | 說明 |
---|---|
include($$PWD/…/cutewidgets.pri) | 不必多說,定義這玩意就是讓引用的。 |
DESTDIR = $$CUTEWIDGETS_OUTPUT_LIB | 定義動态庫或靜态庫輸出位置 |
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDll) | 決定是編譯動态庫還是靜态庫,動态庫的話,輸出dll到bin |
INCLUDEPATH += $$CUTEWIDGETS_ROOT/src | 這個特殊說明下,這個路徑下有cutewidgets_global.h檔案,為了可以在所有檔案中直接包含#include “cutewidgets_global.h”,這樣做的用意是編譯designer插件時會把所有相關頭檔案都拷貝到QT的include/QtCuteWidgets下,和代碼的檔案結構不同了,避免頭檔案引用錯誤 |
contains(CUTEWIDGETS_CONFIG, CuteWidgetsTestEdit) | 前文提到了,根據配置選擇是否加載該部分源碼 |
INSTALLS += target_headers | 頭檔案拷貝,具體配置請參考QT INSTALLS使用 |
1.2 cutewidgets_global.h
内容如下:
#ifndef CUTEWIDGETS_GLOBAL_H
#define CUTEWIDGETS_GLOBAL_H
#include <QtCore/qglobal.h>
#ifdef CUTEWIDGETS_DLL
#ifdef CUTEWIDGETS_LIBRARY
# define CUTEWIDGETS_EXPORT Q_DECL_EXPORT
#else
# define CUTEWIDGETS_EXPORT Q_DECL_IMPORT
#endif
#else
#define CUTEWIDGETS_EXPORT
#endif
#endif // CUTEWIDGETS_GLOBAL_H
主要是倒騰CUTEWIDGETS_EXPORT這個變量的,在動态庫、靜态庫或直接引用源碼下的使用情況。
1.3 testedit
這個就是我編寫的測試自定義控件的源碼了。
testedit.pri:
!contains(DEFINES, DEFINE_CUTEWIDGETS_TESTEDIT) {
DEFINES += DEFINE_CUTEWIDGETS_TESTEDIT
}
QT += widgets
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/testedit.h
SOURCES += \
$$PWD/testedit.cpp
########################################################
#添加需要install的頭檔案
########################################################
INSTALL_INCLUDE_FILES += $$PWD/testedit.h
DEFINE_CUTEWIDGETS_TESTEDIT前面已經提到了。主要說下最後一段,給install用的,編譯源碼主要生成三個檔案,bin,include,lib,形成動态庫三件套,這裡會拷貝相關頭檔案到工程根目錄下的include。
testedit.h和testedit.cpp
純源碼了,内容如下:
#ifndef TESTEDIT_H
#define TESTEDIT_H
#include "cutewidgets_global.h"
#include <QWidget>
class TestEditPrivate;
class CUTEWIDGETS_EXPORT TestEdit : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString title READ getName WRITE setName RESET resetName NOTIFY nameChanged)
Q_PROPERTY(QString value READ getValue WRITE setValue RESET resetValue NOTIFY valueChanged)
public:
explicit TestEdit(QWidget *parent = nullptr);
QString getName();
void setName(const QString &text);
void resetName();
QString getValue();
void setValue(const QString &text);
void resetValue();
Q_SIGNALS:
void nameChanged(QString);
void valueChanged(QString);
private:
Q_DECLARE_PRIVATE(TestEdit)
TestEditPrivate *d_ptr;
};
#endif // TESTEDIT_H
#include "testedit.h"
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
class TestEditPrivate
{
Q_DECLARE_PUBLIC(TestEdit)
TestEdit *q_ptr;
public:
QLabel *name;
QLineEdit *input;
TestEditPrivate(TestEdit *q);
};
TestEdit::TestEdit(QWidget *parent)
: QWidget{parent}, d_ptr(new TestEditPrivate(this))
{
Q_D(TestEdit);
auto vLayout = new QVBoxLayout;
setLayout(vLayout);
d->name = new QLabel;
resetName();
d->input = new QLineEdit;
d->input->setPlaceholderText("please input");
resetValue();
vLayout->addWidget(d->name);
vLayout->addWidget(d->input);
}
QString TestEdit::getName()
{
Q_D(TestEdit);
return d->name->text();
}
void TestEdit::setName(const QString &text)
{
Q_D(TestEdit);
if(d->name->text() == text)
return;
d->name->setText(text);
Q_EMIT nameChanged(text);
}
void TestEdit::resetName()
{
Q_D(TestEdit);
d->name->setText("untitled");
}
QString TestEdit::getValue()
{
Q_D(TestEdit);
return d->input->text();
}
void TestEdit::setValue(const QString &text)
{
Q_D(TestEdit);
if(d->input->text() == text)
return;
d->input->setText(text);
Q_EMIT valueChanged(text);
}
void TestEdit::resetValue()
{
Q_D(TestEdit);
d->input->setText("");
}
TestEditPrivate::TestEditPrivate(TestEdit *q) : q_ptr(q)
{
}
很簡單,就是把QLabel和QLineEdit放一起了。
TestEdit::TestEdit(QWidget *parent)
: QWidget{parent}, d_ptr(new TestEditPrivate(this))
{
Q_D(TestEdit);
auto vLayout = new QVBoxLayout;
setLayout(vLayout);
d->name = new QLabel;
resetName();
d->input = new QLineEdit;
d->input->setPlaceholderText("please input");
resetValue();
vLayout->addWidget(d->name);
vLayout->addWidget(d->input);
}
Q_DECLARE_PRIVATE、Q_DECLARE_PUBLIC、Q_D、Q_Q,沒見過的去查,又長知識了啊。
Q_PROPERTY 也是知識點,不過既然要編寫自定義控件了,自然應該知道的。![]()
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
1.4 擴充
看檔案結構也應該清楚,後續再添加其他自定義控件,那無非就是添加一個一個的類似testedit的檔案夾了。pri檔案參見testedit.pri的寫法,其他正常,主要注意install需要的頭檔案,src.pro檔案添加新源碼的位置也已标注。
2 examples
例程examples是顧名思義是為了測試編寫的自定義控件。其檔案結構如下:
- examples.pro
- examples.pri
- testedit
2.1 examples.pro
沒什麼說的,簡單的一個子項目,内容如下:
include($$PWD/../cutewidgets.pri)
TEMPLATE = subdirs
contains(CUTEWIDGETS_CONFIG, CuteWidgetsTestEdit) {
SUBDIRS += testedit
}
########################################################################
# 在此新增測試源碼 #
########################################################################
在尾部新增測試源碼。
2.2 examples.pri
測試例程的公共配置,内如如下:
CUTEWIDGETS_ROOT = $$PWD/..
CUTEWIDGETS_OUTPUT_LIB = $$CUTEWIDGETS_ROOT/lib
include($$CUTEWIDGETS_ROOT/cutewidgetsfunctions.pri)
TEMPLATE = app
DESTDIR = $$CUTEWIDGETS_ROOT/bin
INCLUDEPATH += $$CUTEWIDGETS_ROOT/include
DEPENDPATH += $$CUTEWIDGETS_OUTPUT_LIB
cuteWidgetsAddLibrary($$CUTEWIDGETS_OUTPUT_LIB, cutewidgets)
模闆為app,輸出到根目錄bin,後三行将源碼作為正常的動态庫或靜态庫引用。
2.3 testedit
對應源碼testedit的測試源碼
testedit.pro檔案内容如下:
TEMPLATE = subdirs
SUBDIRS += \
simple
這裡不用要求是子項目模闆,若目前測試隻有一個工程,也可以直接上app模闆,若是多個測試工程那應該考慮使用子項目模闆。
simple例程,是一個再簡單不過的小工程了,
主要說下pro檔案,
include($${PWD}/../../examples.pri)
QT += widgets
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
include($${PWD}/…/…/examples.pri),所有測試工程的pro檔案都要包含這句,配置三方動态庫依賴。
mainwindow.cpp檔案内容如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "testedit.h"
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto v = new QVBoxLayout;
v->addWidget(new TestEdit(this));
ui->centralwidget->setLayout(v);
}
MainWindow::~MainWindow()
{
delete ui;
}
這裡就簡單添加了一個自定義控件TestEdit。運作結果如下:
2.4 拓展
同源碼src一樣,src那邊新增源碼子產品,這邊就相應添加測試子產品,參見examples.pro尾部。
3 designer
前面提到的src和examples分别是自定義控件的編寫和其測試例程的編寫,完成後,其實作為第三方動态庫的工作已經完成,已經可以供其他項目調用了,那為什麼還要再設計一個designer子產品呢?
我們都知道QT提供了Qt Designer和Qt Creator兩個工具,其中Qt Designer是單獨設計UI界面的,很友善,妥妥拽拽就把複雜的界面搭出來了,Qt Creator把Qt Designer做了內建,UI設計也很友善,還有一些Qt Designer沒有的功能,例如可以直接右鍵控件轉到源碼槽函數等等,Qt Designer内部內建了很多控件,提供了拖拽的基礎。
我們編寫的自定義控件本質上和原生控件是一樣的,如果也內建到Qt Designer中,采用拖拽的方式就把自定義控件加到我們的UI界面中了,那現在的問題就是我們編寫的自定義控件它隻是普通的UI動态庫,無法被Qt Designer識别,自然也就不能像原生控件那樣被友善的使用了,這就是為什麼還要再添加一個designer子產品的原因,将自定義控件包裝一下,讓它可以被Designer識别。
其實對于我個人而言,添加到designer裡的實用意義并不大,因為到一定階段,你會更鐘情于在代碼中設計UI,而不是通過Designer工具。可能有人會反駁,說Designer更友善,更高效,這裡我們不做争論,在不同階段使用自己習慣的方式就好。但是将自定義控件內建到Designer的工作還是要做的,畢竟有人是更習慣于Designer的。
下面開始介紹designer子產品。
一共包含四個檔案和一個資源檔案夾:
- designer.pro
- designer_plugin.h
- designer_plugin.cpp
- designer_plugin.qrc
- resource
3.1 designer.pro
内容如下:
include($$PWD/../cutewidgets.pri)
################################################################################
# 若qtcreator路徑下已生成cutewidgets_plugin.dll,編譯前需要關閉qtcreator并删除qtcreator
# 路徑下的cutewidgets_plugin.dll,然後再編譯,因為qtcreator打開情況下,該dll是被加載狀态,
# 無法覆寫拷貝;也可以将INSTALL_TO_CREATOR置0,編譯完後關閉qtcreator,手動拷貝;
# 運作例程時需要置0,避免因為編譯錯誤,無法運作或調試例程
################################################################################
INSTALL_TO_CREATOR = 0
CONFIG(debug_and_release) {
# When building debug_and_release the designer plugin is built
# for release only. If you want to have a debug version it has to be
# done with "CONFIG += debug" only.
message("debug_and_release: building the cutewidgets designer plugin in release mode only")
CONFIG -= debug_and_release
CONFIG += release
}
contains(CUTEWIDGETS_CONFIG, CuteWidgetsDesigner) {
CONFIG += qt plugin
greaterThan(QT_MAJOR_VERSION, 4) {
QT += designer
}
else {
CONFIG += designer
}
TEMPLATE = lib
TARGET = cutewidgets_plugin
DESTDIR = $$CUTEWIDGETS_OUTPUT_PLUGIN
#########################################################################
# 定義QT路徑下include下檔案名,所有install的頭檔案放于
# QT_INSTALL_PRFIX/include/CUTEWIDGETS_STR中
#########################################################################
DEFINES += CUTEWIDGETS_STR=$$CUTEWIDGETS_INSTALL_DIR_NAME
INCLUDEPATH += $$CUTEWIDGETS_ROOT/src
#包含指定源碼
contains(CUTEWIDGETS_CONFIG, CuteWidgetsTestEdit) {
include($$CUTEWIDGETS_ROOT/src/testedit/testedit.pri)
}
########################################################################
# 在此新增自定義控件源碼 #
########################################################################
#new pri here
#designer 插件檔案
HEADERS += designer_plugin.h
SOURCES += designer_plugin.cpp
RESOURCES += designer_plugin.qrc
target_designer.path = $$CUTEWIDGETS_INSTALL_PLUGINS_FOR_DESIGNER
target_designer.files = $$CUTEWIDGETS_OUTPUT_PLUGIN/$${TARGET}.dll $$CUTEWIDGETS_OUTPUT_PLUGIN/$${TARGET}.lib
INSTALLS += target_designer
equals(INSTALL_TO_CREATOR, 1) {
target_creator.path = $$CUTEWIDGETS_INSTALL_PLUGINS_FOR_CREATOR
target_creator.files = $$CUTEWIDGETS_OUTPUT_PLUGIN/$${TARGET}.dll
INSTALLS += target_creator
}
target_headers.path = $$CUTEWIDGETS_INSTALL_HEADERS
target_headers.files = $$$$CUTEWIDGETS_ROOT/include/*.h
INSTALLS += target_headers
target_lib.path = $$CUTEWIDGETS_INSTALL_LIB
target_lib.files = $$CUTEWIDGETS_OUTPUT_LIB/cutewidgets.dll $$CUTEWIDGETS_OUTPUT_LIB/cutewidgets.lib
INSTALLS += target_lib
}
else {
TEMPLATE = subdirs # do nothing
}
-
INSTALL_TO_CREATOR
開關變量,功能注釋的很清楚
-
CONFIG(debug_and_release)
Designer隻使用release插件
下面沒什麼可說的,可以動态添加包含自定義控件源碼,尾部是install。
3.2 designer_plugin.h和designer_plugin.cpp
designer_plugin.h:
#ifndef DESIGNER_PLUGIN_H
#define DESIGNER_PLUGIN_H
#include <QtGlobal>
#include <QtPlugin>
#if QT_VERSION >= 0x050600
#include <QtUiPlugin/QDesignerCustomWidgetInterface>
#else
#include <QDesignerCustomWidgetInterface>
#endif
#define STR(R) #R
#define STRVALUE(R) STR(R)
#ifdef CUTEWIDGETS_STR
const QString strQTInclude = QString("%1/").arg(STRVALUE(CUTEWIDGETS_STR));
#elif
const QString strQTInclude = "";
#endif
class CuteWidgetInterface : public QDesignerCustomWidgetInterface
{
public:
virtual QString name() const override;
virtual QString group() const override;
virtual QString toolTip() const override;
virtual QString whatsThis() const override;
virtual QString includeFile() const override;
virtual QIcon icon() const override;
virtual bool isContainer() const override;
virtual bool isInitialized() const override;
virtual void initialize(QDesignerFormEditorInterface *core) override;
virtual QString domXml() const override;
virtual QString codeTemplate() const override;
protected:
QString m_name;
QString m_group;
QString m_toolTip;
QString m_whatsThis;
QString m_include;
QIcon m_icon;
bool m_isContainer;
bool m_initialized;
QString m_domXml;
QString m_codeTemplate;
};
#ifdef DEFINE_CUTEWIDGETS_TESTEDIT
class TestEditInterface : public CuteWidgetInterface
{
public:
TestEditInterface();
virtual QWidget *createWidget(QWidget* parent) override;
};
#endif
/*******************************************************************************************************************/
/* 在此以上填寫自定義接口類 */
/*******************************************************************************************************************/
class CuteWidgetsCollectionInterface
: public QObject
, public QDesignerCustomWidgetCollectionInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetCollectionInterface)
#if QT_VERSION >= 0x050000
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface" )
#endif
public:
CuteWidgetsCollectionInterface() : QObject()
{
#ifdef DEFINE_CUTEWIDGETS_TESTEDIT
m_plugins += new TestEditInterface();
#endif
}
virtual ~CuteWidgetsCollectionInterface() override
{
qDeleteAll(m_plugins);
}
QList<QDesignerCustomWidgetInterface*> customWidgets() const override
{
return m_plugins;
}
private:
QList<QDesignerCustomWidgetInterface*> m_plugins;
};
#endif // DESIGNER_PLUGIN_H
designer_plugin.cpp:
#include "designer_plugin.h"
#ifdef DEFINE_CUTEWIDGETS_TESTEDIT
#include "testedit.h"
#include <QMessageBox>
#endif
QString CuteWidgetInterface::name() const
{
return m_name;
}
QString CuteWidgetInterface::group() const
{
return m_group;
}
QString CuteWidgetInterface::toolTip() const
{
return m_toolTip;
}
QString CuteWidgetInterface::whatsThis() const
{
return m_whatsThis;
}
QString CuteWidgetInterface::includeFile() const
{
return m_include;
}
QIcon CuteWidgetInterface::icon() const
{
return m_icon;
}
bool CuteWidgetInterface::isContainer() const
{
return m_isContainer;
}
bool CuteWidgetInterface::isInitialized() const
{
return m_initialized;
}
void CuteWidgetInterface::initialize(QDesignerFormEditorInterface *core)
{
if (m_initialized)
return;
// Add extension registrations, etc. here
m_initialized = true;
}
QString CuteWidgetInterface::domXml() const
{
return m_domXml;
}
QString CuteWidgetInterface::codeTemplate() const
{
return m_codeTemplate;
}
#ifdef DEFINE_CUTEWIDGETS_TESTEDIT
TestEditInterface::TestEditInterface()
{
m_name = "TestEdit";
m_group = "Cute Widgets";
m_toolTip = "TestEdit";
m_whatsThis = "TestEdit";
m_include = strQTInclude + "testedit.h";
m_icon = QPixmap(":/resource/testedit.png");
m_isContainer = false;
m_domXml = QDesignerCustomWidgetInterface::domXml();
m_codeTemplate = QDesignerCustomWidgetInterface::codeTemplate();
}
QWidget *TestEditInterface::createWidget(QWidget *parent)
{
auto w = new TestEdit(parent);
QObject::connect(w, &TestEdit::nameChanged, [](QString name){
QMessageBox::information(nullptr, "info", QString("name changed : %1").arg(name));
});
QObject::connect(w, &TestEdit::valueChanged, [](QString value){
QMessageBox::information(nullptr, "info", QString("value changed : %1").arg(value));
});
return w;
}
#endif
解讀下:
QDesignerCustomWidgetCollectionInterface,因為我們這個架構本身就是自定義控件的集合,是以最好用QDesignerCustomWidgetCollectionInterface。
QDesignerCustomWidgetInterface,關于這個類,主要說一下容易莫名入坑的地方,
class QDesignerCustomWidgetInterface
{
public:
virtual ~QDesignerCustomWidgetInterface() {}
virtual QString name() const = 0;
virtual QString group() const = 0;
virtual QString toolTip() const = 0;
virtual QString whatsThis() const = 0;
virtual QString includeFile() const = 0;
virtual QIcon icon() const = 0;
virtual bool isContainer() const = 0;
virtual QWidget *createWidget(QWidget *parent) = 0;
virtual bool isInitialized() const { return false; }
virtual void initialize(QDesignerFormEditorInterface *core) { Q_UNUSED(core); }
virtual QString domXml() const
{
return QString::fromUtf8("<widget class=\"%1\" name=\"%2\"/>")
.arg(name()).arg(name().toLower());
}
virtual QString codeTemplate() const { return QString(); }
};
QDesignerCustomWidgetInterface::domXml(),按基類這個去寫肯定沒問題,如果有些人想重寫這個函數,那一定注意"<widget class=“%1” name=“%2”/>“,%1和%2位置一定不能一樣,比如都寫成"TestEdit”,編譯不會報錯,但在使用該自定義控件時會報錯,有興趣的可以試試,看看會報什麼錯。
TestEditInterface類的
前面已經提到過,這裡再提一次,需要加上strQTInclude 這個常量。
TestEditInterface::createWidget函數
QWidget *TestEditInterface::createWidget(QWidget *parent)
{
auto w = new TestEdit(parent);
QObject::connect(w, &TestEdit::nameChanged, [](QString name){
QMessageBox::information(nullptr, "info", QString("name changed : %1").arg(name));
});
QObject::connect(w, &TestEdit::valueChanged, [](QString value){
QMessageBox::information(nullptr, "info", QString("value changed : %1").arg(value));
});
return w;
}
正常情況下就是new一個對象然後再傳回就行,如果想增加一些邏輯,可以添加一些編碼,比如測試代碼中就添加了兩個信号顯示QMessageBox::information。但這裡請注意,這裡的邏輯是對于designer的,而不是對于運作程式的。
在designer中拖拽一個TestEdit控件,改變value值,會觸發信号,彈窗。
但是在運作程式裡不會出現該彈窗。
其實這很好了解,隻要理清了目标對象,就不會混淆了,
QWidget *TestEditInterface::createWidget(QWidget *parent)
{
auto w = new TestEdit(parent);
QObject::connect(w, &TestEdit::nameChanged, [](QString name){
QMessageBox::information(nullptr, "info", QString("name changed : %1").arg(name));
});
QObject::connect(w, &TestEdit::valueChanged, [](QString value){
QMessageBox::information(nullptr, "info", QString("value changed : %1").arg(value));
});
return w;
}
TestEditInterface這個類是對應designer的,是以designer即為它的運作程式,那在designer裡一旦觸發的信号,必然會彈窗。但使用該自定義控件的程式隻是借由designer把TestEdit添加到自己的程式裡,而那裡隻是單純的使用TestEdit而已,并沒有連接配接上述的兩個TestEdit的信号,是以不會觸發彈窗,如果在程式中同樣連接配接了該信号,那觸發信号時,也必然彈窗。
這是兩個概念。不了解也沒關系,記住就行了。
3.3 資源檔案
很好了解,給你的自定義控件插件添加一個圖示,這個圖示會出現在designer中。
這個資源檔案僅是圖示用,如果自定義控件本身需要資源檔案,請在自定義控件源碼處添加自己的資源檔案,不要混淆了。
至此,架構就介紹完了。
五、如何使用自定義控件
1 确認QT環境下的cutewidgets部署情況
将cutewidgets編譯好後,去QT安裝目錄下查詢,你應該看到:
2 在Qt Creator中使用
建立一個app程式
切換到designer界面,拖拽一個自定義控件到界面中。
編譯程式,你會看到如下錯誤:
很明顯是沒有添加動态庫引用,在pro檔案中添加,再次編譯。
點選調試或運作,如果提示缺少cutewidgets.dll無法運作,需要将cutewidgets.dll拷貝到測試例程的運作環境中。