讓你的Qt桌面程式看上去更加native(三):自定義style
前面我們一再強調,Qt 使用自己的方式繪製元件。然而我們也看到,在不同的平臺上,Qt 的元件表現也不相同。這和 Swing 有些類似:Swing 使用 look and feel 表現元件的外觀,Qt 也是類似的。用來繪製元件外觀的類就是 QStyle。
需要說明一點,元件的 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 函式一般會有四個引數:
- 第一個是一個 enum,用於指定要繪製哪個元素。這個 enum 在不同的 draw 函式中可能是不一樣的。例如,在 drawControl() 中是 QStyle::ControlElement,指的是元件;在 drawPrimitive() 中則是 QStyle::PrimitiveElement,指的是元件的原始組成元素,例如焦點框,check box 的小勾等;
- 第二個是 QStyleOption 物件指標。這個物件儲存了 painter 繪製時所需要的所有資料資訊,比如繪製大小、座標、繪製文字等。不同的 element 可能對應著不同的 QStyleOption 的子類,這個在文件中可以找到;
- 第三個是 QPainter 物件指標。系統即用這個 painter 進行繪製;
- 第四個是 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);
- MainWindow w;
- w.show();
- return app.exec();
- }
這樣,我們就可以用我們自己的 style 顯示元件了。
就像前面所說,自定義 style 是一個相當複雜的話題,我們不可能在這裡完全說明。不過,也正因為 Qt 提供了這種機制,也能夠讓我們可以比較輕鬆地實現自定義 style。
本文轉自 FinderCheng 51CTO部落格,原文連結:
http://blog.51cto.com/devbean/471941
相關文章
- 讓你的Qt桌面程式看上去更加native(二):StyleQT
- 讓你的Qt桌面程式看上去更加native(四):stylesheetQT
- 讓你的Qt桌面程式看上去更加native(六):跨平臺技術QT
- 如何讓你的JavaScript程式碼更加語義化JavaScript
- 聖誕節到了!!你的桌面下雪了嗎?? - Qt趣味開發之讓你的桌面下雪QT
- 讓拖拽更加人性化?如何自定義dragover樣式Go
- DSL-讓你的 Ruby 程式碼更加優雅
- 如何讓你的Python程式碼更加pythonic ?Python
- 讓你的C++程式碼變的更加健壯C++
- 想讓你的程式碼變得更加優雅嗎?
- React Native自定義ButtonReact Native
- Qt Charts 自定義樣式QT
- Native Query的自定義轉換器
- QT常用控制元件(三)——自定義控制元件封裝QT控制元件封裝
- 如何自定義 GNOME 3 桌面?
- QT自定義精美換膚介面QT
- 如何讓自己的程式碼更加安全?
- 讓你工作變得更加有趣
- win10桌面自定義桌布的方法_win10怎麼設定桌面自定義桌布Win10
- 使用Qt Style Sheet(1)QT
- 聊一聊Java8 Optional,讓你的程式碼更加優雅Java
- Qt實現自定義控制元件QT控制元件
- Qt自定義動畫插值函式QT動畫函式
- 使用 FVWM 自定義 Linux 桌面Linux
- WM自定義桌面"今日外掛"
- 使用防火牆讓你的 Linux 更加強大防火牆Linux
- 讓你的linux作業系統更加安全Linux作業系統
- android studio和 Eclipse中程式碼的註釋,讓你的程式碼更加AndroidEclipse
- Android Annotation-讓你的程式碼和設計更加優雅(一)Android
- Qt的皮膚設計(Style Sheet)QT
- 微信小程式使用自定義字型的三種方法微信小程式自定義字型
- win10桌面佈局設定成自定義_windows10桌面圖示如何自定義Win10Windows
- Qt自定義外掛plugin的開發和呼叫QTPlugin
- Qt繪製自定義箭頭圖元QT
- C/C++ Qt TableDelegate 自定義代理元件C++QT元件
- Qt入門(19)——自定義視窗部件QT
- Qt QMessageBox::information 自定義按鈕QTORM
- 如何自定義Mac電腦桌面桌布?Mac