QStyle/DTK重繪自定義需求控件,舉例MySwitchButton


簡 述: 使用 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


背景鋪墊:

​ 由上上一篇 的文章,已理解 QStyleQCommonStyle 和自定義的 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 控件效果,咳咳,👀 不要跟着鼠標亂動。


系列文章:

  1. QStyle設置界面的外觀和QCommonStyle繼承關係圖講解和使用
  2. QStyle/DTK重繪Qt-GUI已有控件,舉例QScrollBar
  3. QStyle/DTK重繪自定義需求控件,舉例MySwitchButton
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章