Qt 自定義組件風格說明(QStyle和paintEvent)


https://www.devbean.net/2011/08/native-style-qt-3/

https://blog.csdn.net/hyongilfmmm/article/details/83238938

 

需要說明一點的是,組件的 style 是一個非常復雜的內容,僅在這里不可能全部講解清楚。如果需要自定義組件 style,還是自己仔細閱讀相關文檔。另外,這部分牽扯的類很多,函數也很復雜,步步為營才是最好的對待方法。除非非常必要,還是建議不要輕易去碰 style 這部分。

自定義 style,顧名思義,也就是自己實現樣式。這里通常有兩種實現方式:第一,重寫 widget 的paintEvent()函數;第二,使用QStyle類。兩種方式的側重點有所不同:重寫組件的paintEvent()函數,可以簡單地實現某一類組件的樣式;繼承QStyle類,則可以實現對全部組件的一致性處理,例如,將程序中所有的 text 變成紅色等。

首先我們來看看重寫paintEvent()函數。paintEvent()QWidget的一個函數,用於實現自身的繪制。一個組件顯示到屏幕上,就是通過調用paintEvent()函數把它畫出來。看看一個組件有多復雜,全部要使用QPainter提供的畫點、畫線的函數繪制出來,就知道這里的工作量了。當然也有偷懶的辦法,就是重寫paintEvent()的時候使用一張圖片代替。我們這里就不討論這種思路了,而是完全從代碼開始。

我們以QPushButton為例。這里,我們創建一個 button,這個 button 在點擊時可以凹下顯示。為了重寫paintEvent()函數,我們必須繼承QPushButton類。頭文件很簡單,暫且略去,下面只看paintEvent()這個函數:

void MyPushButton::paintEvent(QPaintEvent *)
{
    QStyleOptionButton option;
    option.initFrom(this);
    // qDebug() << option.state;
    option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
    // qDebug() << option.state;
    if (isDefault())
        option.features |= QStyleOptionButton::DefaultButton;
    option.text = text();
    option.icon = icon(); 

    QPainter painter(this);
    style()->drawControl(QStyle::CE_PushButton, &option, &painter, this);
}

 

盡管前面說過,我們需要重頭繪制整個組件,但實際上,Qt 為我們提供了一系列方便的函數,用於繪制出各個組件的組成部分。這種繪制方式在將組合進行組合的情況下非常有用。例如,一個 combo box 實際上是一個 button 加上一個向下的三角形構成。那么,我不需要將整個 combo box 用像素畫出來,而是借用 Qt 已有的組件進行繪制:畫出一個 button 和一個三角形。所以,這里我們也使用類似的思路,讓 Qt 繪制出組件的每個部分,而我們要做的就是修改參數,讓它按照我們的參數繪制。

如何調用 Qt 的組件繪制函數呢?這個繪制函數是QStyle類的成員。QWidget提供了style()函數,返回當前的QStyle對象。那么,我們就可以通過這個對象繪制。注意上面代碼中最后一行,我們從這里看起。下面給出這個函數的簽名:

 
virtual void QStyle::drawControl ( ControlElement element,
const QStyleOption * option,
QPainter * painter,
const QWidget * widget = 0 ) const = 0;
 

盡管這是一個純虛函數,但是類似於 Java 的 interface,我們可以直接使用style()返回的對象調用。這是一個很典型的 style 式的函數調用。翻看一下QStyle的聲明,QStyle類提供了很多以 draw 打頭的函數,用於繪制整個系統組件的繪制。這類 draw 函數一般會有四個參數:

  1. 一個 enum,用於指定要繪制哪個元素。這個 enum 在不同的 draw 函數中可能是不一樣的。例如,在drawControl()中是QStyle::ControlElement,指的是組件;在drawPrimitive()中則是QStyle::PrimitiveElement,指的是組件的原始組成元素,例如焦點框,check box 的小勾等;
  2. QStyleOption對象指針。這個對象保存了 painter 繪制時所需要的所有數據信息,比如繪制大小、坐標、繪制文本等。不同的 element 可能對應着不同的QStyleOption的子類,這個在文檔中可以找到;
  3. QPainter對象指針。系統即用這個 painter 進行繪制;
  4. QWidget對象指針,用於輔助繪制。

回到代碼,我們可以看到,在drawControl()函數的四個參數中,只有最后一個有默認值。也就是說,如果要調用這個函數,我們必須准備好參數數據。這就是在paintEvent()中,前面幾行代碼做在的工作。

通過文檔我們查到,QPushButton需要的是QStyleOptionButton作為第二個參數。於是,我們新建一個QStyleOptionButton對象。初始化調用initFrom(),也就是使用本對象設置一個初始值。QStyleOption有很多屬性。比如QStyleOption::state指的是當前狀態。例如,如果 button 被按下,也就是isDown()返回true的時候,我們將 state 設置為QStyle::State_Sunken,也就是凹下,否則則是QStyle::State_Raised。這樣,我們就完成了設置。另外,還要根據需要設置別的屬性,例如,如果isDefault()返回true時,我們需要設置option.features,這樣才能繪制出默認的效果。text 和 icon 屬性則是通過 button 自身函數獲得。這樣,我們完成對繪制數據的設置,就可以調用QStyle::drawControl()函數,將這個 button 繪制出來。

這里注意一點是,對於QFlags對象,使用 = 賦值很可能不是你所期望的結果。QFlags實現的是 bitmap 位圖,如果簡單的使用 = 賦值,在賦值的同時會清除原有位的值。你可以將上面的option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised;修改為option.state = isDown() ? QStyle::State_Sunken : QStyle::State_Raised;,注意比較下前后兩個 debug 輸出的不同。

調用QStyle::drawControl()函數時,第一個參數可以通過文檔查到。這里的 CE_ 前綴實際就是 ControlElement 的意思。

這樣,我們就完成了一個簡單的自定義 button。代碼雖然簡單,但是大體流程已經表現出來,剩下的就是去翻閱大量文檔,仔細了解各個 draw 函數的使用,才能夠做出滿意的自定義組件效果。

前面說的第一種自定義組件實現就簡單說到這里。然后看看第二種,QStyle的實現。其實在上面,我們已經使用了QStyle。想必也能夠想到,這里我們依舊要用到QStyle的各個 draw 函數,只不過這里我們不是簡單的去調用它們,而是通過繼承,將這些 draw 函數替換成我們自己的版本,達到自定義樣式的目的。

雖然我們可以直接繼承QStyle來實現,但是這並不是一個好主意。因為QStyle這個類很復雜,幾乎所有的函數都是純虛函數,這要求我們必須一個個實現它們。有時候,我們並不需要自己實現所有功能,僅僅是做簡單的修改。於是,從 4.6 版本開始,Qt 提供了一個專門的類,QProxyStyle。我們要做的就是繼承QProxyStyle,覆蓋我們感興趣的函數即可。看下面一個簡單的實例: 

 
class MyProxyStyle : public QProxyStyle
{
public:
    void drawControl(ControlElement element,
                     const QStyleOption *option,
                     QPainter *painter,
                     const QWidget *widget) const;
};

void MyProxyStyle::drawControl(ControlElement element,
                               const QStyleOption *option,
                               QPainter *painter,
                               const QWidget *widget) const
{
    if(element == QStyle::CE_PushButtonLabel) {
        painter->drawText(option->rect, "fixed");
    } else {
        QProxyStyle::drawControl(element, option, painter, widget);
    }
}

 

 

MyProxyStyle覆蓋了drawControl()函數,然后判斷,如果是 button label 的話,繪制文本 “fixed”。可想而知,我們的QPushButton::setText()函數已經沒有作用了,因為我們在繪制時沒有使用這個屬性,也就不會顯示出來了。不管你設不設置,所有 button 的 text 都會是 fixed。如果要使用這個 style ,需要在運行前設置,例如:

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    app.setStyle(new MyProxyStyle);
    QPushButton button;
    button.setText("Hello, world!");
    button.show();
    return app.exec();
}

 

這樣,我們就可以用我們自己的 style 顯示組件了。

就像前面所說,自定義 style 是一個相當復雜的話題,我們不可能在這里完全說明。不過,也正因為 Qt 提供了這種機制,也能夠讓我們可以比較輕松地實現自定義 style。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM