文章目錄
簡 述: 使用 QStyle / DTK 來實現重繪自定義需求控件(Qt-GUI 沒有的),此處以重繪 ios 的控件 MySwitchButton 舉例。
編程環境:
💻: uos 20 amd64
📎 Qt 5.11.3
💻: MacOS 10.14.6
📎 Qt 5.12.6
💻: win10 x64
📎 Qt 5.9.8
背景鋪墊:
由上上一篇 的文章,已理解 QStyle
、 QCommonStyle
和自定義的 MyStyle
三者的關係;對基礎的風格控件也有所學習了;
和上一篇 文章中,已理解如何使用 MyStyle
來繪畫 Qt 自帶的控件的風格,也有一次的實戰重繪 QCommonStyle
裏面的虛函數的項目;
在此,本文收尾,分析開源社區精神小夥們,它們是怎麼對 DTK 的庫進行重繪控件的。想到的新學習的框架,良心說,開源,真的能夠學習到很多知識。 本篇重點介紹,如何創建一個新的自定義的控件,並且對其進行繪畫,寫一個簡單工程,進行示範。
需求分析:
因 DTk/QStyle
繪畫的這個系列的完整性,得自己想一個比較好的控件啊,撓了撓頭,iPhone 給了我感覺。SwitchButton
控件是 ios / Andiroad 📱的控件,不屬於 PC 的控件,更非是 Qt 自帶的一個控件;用電腦進行繪畫,正好是妙哉。其 UI 原型圖如下;功能就是點擊一下,就會在 開/關 狀態之間來回切換,且展示不同的顏色背景。
工程文件分析:
這裏分析下,整個項目的構成組成;這裏還是再次簡單梳理一番:
- QtStyleEx:
- main.cpp: 程序的總入口
- widget.h: 顯示 GUI 控件的背景窗口,繼承於 QWidget 的類的聲明
- widget.cpp: 顯示 GUI 控件的背景窗口,繼承於 QWidget 的類定義
- mystyle.h: 自定義風格(不屬於 OS 自帶風格)類的聲明
- mystyle.cpp: 自定義風格(不屬於 OS 自帶風格)的類定義
- myswitchbutton.h: 自定義需求的控件類的聲明
- myswitchbutton_p.h: 自定義需求的控件類的數據類 Private 的聲明
- myswitchbutton.cpp: 自定義需求的控件類和變量 Private 類的實現
其中主要涉及的幾個類如下:
整理所有類的思維導圖:
這上面的這些問題,順其自然的找到它們對應的類。按照各自的功能劃分,可以得到如下的思維導圖。提綱挈領,寫代碼不會迷失在細節👩💻;
實現流程圖:
有了 UI 設計圖,和熟悉的功能,已經進行重繪控件;通過以上的代碼邏輯流程,覆盤一下 DTK 繪畫自定義需求的源碼思路。將自己的邏輯插入到 Qt 原本的繪畫架構中,利用已經有的 QStyle 架構來進行繪畫。梳理出來的流程圖如下。
代碼實現:
其中代碼實現的地方,有幾處是的設計很精妙,可能我是才眼界初看開,很有必要的🌶出來單獨講;看着代碼來實現復現一個功能很容易,屬於借鑑技巧與學習,但容易讓自己上鉤思想懶惰,不去思考原理。有一些細節是含糊過去的地方;在正式實現之前,主要解決的問題有如下難點:
- 如何創建 MySwitchButton 控件? 需要繼承哪些類❓
- 控件矩形大小❓
- 控件的 UI 樣式❓
- 控件如何拆分部件 MyStyle::CE_SwitchButton ❓
- 如何進入到繪畫步驟中❓
- QPainter 如何繪畫該小部件?進入到控件的 paintEvent()函數❓
- MyStyle 如何區分 Qt 原生控件和自定義的控件❓
- 如何增加自定義枚舉 CE_ , SE_ , PM_ , PE_ 等❓
- 如何區分重繪畫的控件是 Qt 自帶的還是自定義枚舉元素❓
- 以及 MyStyle 如何調用這些重載的函數❓
上面👆這些想的清楚了,那麼這個架構也清楚了很多。也知曉了繪畫的流程。
添加自定義的枚舉:
這裏新增加的枚舉,是屬於 MyStyle:: , 而非 QStyle:: 範圍。
且要從 CE_CustomBase
= QStyle::CE_CustomBase + 0xf00000
開始,新的枚舉按照 int 類型依次加一。
class MyStyle : public QCommonStyle
{
public:
//這裏新增加的枚舉,是屬於 MyStyle:: , 而非 QStyle:: 範圍
enum ControlElement {
CE_SwitchButton = QStyle::CE_CustomBase + 1, //switchButton 控件
CE_CustomBase = QStyle::CE_CustomBase + 0xf00000
};
...
}
重寫 QCommonStyle 的虛函數:
這裏的快捷方式創建的枚舉,都是不帶QStyle::, 但是快捷方式的定義是帶是QStyle:: , 此處聲明的地方必須加上 QStyle::,後面改寫更復雜的得寫上MyStyle:: 因添加自定義的枚舉。
這裏 override 的虛函數,只能夠調用舊有的 QStyle:: 的函數。主要用來繪畫 Qt 、 自定義新增 的控件枚舉。
後面改寫更復雜的得寫上MyStyle:: 因添加自定義的枚舉
class MyStyle : public QCommonStyle
{
public:
virtual void drawControl(QStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const override;
virtual QRect subElementRect(QStyle::SubElement subElement, const QStyleOption *option, const QWidget *widget) const override;
virtual void drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *widget) const override;
virtual QSize sizeFromContents(QStyle::ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w) const override;
virtual void polish(QWidget *widget) override;
virtual void unpolish(QWidget *widget) override;
...
}
內斂函數調用 MyStyle:: 強制轉換爲 QStyle:: 調用:
新增加的枚舉屬 MyStyle:: , 之能夠在此內斂函數裏面調用;主要用來繪畫 自定義新增 的控件枚舉 --> 實際調用在 下面的 virtual 裏面繪畫。
class MyStyle : public QCommonStyle
{
public: //聲明如下:
inline void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr) const;
inline void drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
...
}
//定義如下:
void MyStyle::drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
proxy()->drawPrimitive(static_cast<QStyle::PrimitiveElement>(pe), opt, p, w);
}
void MyStyle::drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
proxy()->drawControl(static_cast<QStyle::ControlElement>(element), opt, p, w);
}
設計靜態函數同名接口讓 MyStylrHelp 調用:
static 函數,供 MyStylrHelp 調用 [用來繪畫自增加的控件枚舉]。
class MyStyle : public QCommonStyle
{
public: //聲明如下:
static void drawPrimitive(const QStyle *style, MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr);
static void drawControl(const QStyle *style, MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w);
...
}
//定義如下:
void MyStyle::drawPrimitive(const QStyle *style, MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w)
{
//沒有使用到,因爲具體的設計因爲每個人的思路設計不同,決定的具體的繪畫地方也不一樣;這裏我是放到 重寫的虛函數裏面,和繪畫 Qt 的虛函數的框架裏面一起繪畫
}
輔助類 MyStyleHelp , 區分繪畫控件:
輔助類 MyStyleHelp , 幫助區分繪畫到底是繪畫 MyStyle::PrimitiveElement 還是,它的類的實現如下:
class MyStyleHelp
{
public:
inline MyStyleHelp (const QStyle* style = nullptr);
inline void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = nullptr) const;
inline void drawControl(MyStyle::ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const;
...
private:
const QStyle* m_qStyle;
const MyStyle* m_myStyle;
};
其中放一個函數的實現,會簡單發現這個 三目表達式,很是精華,屬於一段點睛之筆;設計上面,既可以專門繪畫自定義的新增加的控件,也可以繪畫 Qt 的控件;有着很好的擴展性和代碼的健壯性。
void MyStyleHelp::drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const
{
m_myStyle ? m_myStyle->drawPrimitive(pe, opt, p, w)
: MyStyle::drawPrimitive(m_qStyle, pe, opt, p, w);
}
MyStylePainter 畫家,在指定的“畫板”上繪畫:
其直接繼承於 class MyStylePainter : public QPainter;本也可以不這樣再單獨設計一個畫家類,爲了展現此架構的可擴展性,此處會再封裝一下,是的分風格更爲統一。其類的設計如下:
class MyStylePainter : public QPainter
{
public:
MyStylePainter(QWidget* w);
void drawPrimitive(MyStyle::PrimitiveElement pe, const QStyleOption *opt);
void drawPrimitive(QStyle::PrimitiveElement pe, const QStyleOption *opt);
...
private:
QWidget* m_widget;
QStyle* m_qStyle;
MyStyleHelp m_myStyleHelp;
};
這裏面,相反,我覺得最重要的除了三個成員變量之外,就是這個構造函數,裏面調用父類的函數 QPainter::begin(w);
。
MyStylePainter::MyStylePainter(QWidget* w)
{
m_widget = w;
m_qStyle = w->style();
m_myStyleHelp.setStyle(m_qStyle);
QPainter::begin(w); //是調用父類的 begin(), 調試半天才發現
}
控件 MySwitchButton 設計:
作爲新的控件,使用 xxx 和它的數據類 xxxPrivate 進行構建,兩個類之間依靠宏 Q_D 和 Q_Q 來進行互相的調用;
- MySwitchButton && MySwitchButtonPrivate
- Q_D(MySwitchButton) && Q_Q(MySwitchButton)
class MySwitchButtonPrivate;
class MySwitchButton : public QAbstractButton
{
Q_OBJECT
public:
explicit MySwitchButton(QWidget* parent = nullptr);
~MySwitchButton();
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent *event) override;
private:
void initStyleOption(QStyleOptionButton *opt) const;
Q_DECLARE_PRIVATE(MySwitchButton)
};
這裏的自定義控件, 在 paintEvent 函數裏面,通過畫筆進行繪畫,與 MyStyle::CE_SwitchButton 關聯上。
void MySwitchButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QStyleOptionButton opt;
initStyleOption(&opt);
MyStylePainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.drawControl(MyStyle::CE_SwitchButton, &opt); //重點
}
運行效果:
附上一張完整的 gif 的動圖:當然是重點看的 switchButton 控件效果,咳咳,👀 不要跟着鼠標亂動。