天天看點

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

目錄

  • 前言
  • 一、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的無奈用過的人都知道。

二、工程結構

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
項目 說明
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去掉。
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

2 cutewidgets.pri

整個工程的公共配置檔案,内容如下:

################################################################################
# CONFIG配置檔案
################################################################################

include($$PWD/cutewidgetsconfig.pri)

################################################################################
# 函數定義
################################################################################

include($$PWD/cutewidgetsfunctions.pri)

################################################################################
# 建構配置
################################################################################

include($$PWD/cutewidgetsbuild.pri)
           

它其實就是三個配置檔案的包,友善引用,所有引用該檔案的地方都會有這麼一個包出現

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

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
           
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

再延伸下,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++可識别的變量。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

其實主要是這兩行起到的作用

#define STR(R) #R
#define STRVALUE(R) STR(R)
           

第一行用 # 擷取宏的名稱,第二行擷取宏的值。這樣就擷取了pro檔案的變量,這樣做的目的是為了統一,install的路徑和自定義控件的頭檔案定義就一緻了,其實QT的自定義控件模闆工程的做法是直接把自定義控件的頭檔案放在QT的include檔案下,我們是為了和人家檔案結構保持一緻,才加了這麼個變量,沒辦法,誰叫咱有潔癖呢 (^ _ ^)。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

後半部分為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三個子項目工程是否添加該自定義控件。這裡編寫時可以參考自帶的測試控件例程。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

DEFINE_CUTEWIDGETS_TESTEDIT不用想也肯定和cutewidgetsconfig.pri裡的CuteWidgetsTestEdit有關,在CuteWidgetsTestEdit決定的testedit.pri檔案裡定義如下:

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

總之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

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
  • 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

這個就是我編寫的測試自定義控件的源碼了。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

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是顧名思義是為了測試編寫的自定義控件。其檔案結構如下:

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
  • 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的測試源碼

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

testedit.pro檔案内容如下:

TEMPLATE = subdirs

SUBDIRS += \
    simple

           

這裡不用要求是子項目模闆,若目前測試隻有一個工程,也可以直接上app模闆,若是多個測試工程那應該考慮使用子項目模闆。

simple例程,是一個再簡單不過的小工程了,

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

主要說下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。運作結果如下:

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

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子產品。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

一共包含四個檔案和一個資源檔案夾:

  • 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值,會觸發信号,彈窗。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

但是在運作程式裡不會出現該彈窗。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

其實這很好了解,隻要理清了目标對象,就不會混淆了,

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的信号,是以不會觸發彈窗,如果在程式中同樣連接配接了該信号,那觸發信号時,也必然彈窗。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

這是兩個概念。不了解也沒關系,記住就行了。

3.3 資源檔案

很好了解,給你的自定義控件插件添加一個圖示,這個圖示會出現在designer中。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
這個資源檔案僅是圖示用,如果自定義控件本身需要資源檔案,請在自定義控件源碼處添加自己的資源檔案,不要混淆了。

至此,架構就介紹完了。

五、如何使用自定義控件

1 确認QT環境下的cutewidgets部署情況

将cutewidgets編譯好後,去QT安裝目錄下查詢,你應該看到:

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

2 在Qt Creator中使用

建立一個app程式

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

切換到designer界面,拖拽一個自定義控件到界面中。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

編譯程式,你會看到如下錯誤:

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

很明顯是沒有添加動态庫引用,在pro檔案中添加,再次編譯。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

點選調試或運作,如果提示缺少cutewidgets.dll無法運作,需要将cutewidgets.dll拷貝到測試例程的運作環境中。

QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件
QT自定義控件工程結構架構前言一、cutewidgets是什麼?二、工程結構三、架構的工程配置四、源碼五、如何使用自定義控件

繼續閱讀