程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 讓你的 Qt 桌面程序看上去更加 native(三):自定義 style

讓你的 Qt 桌面程序看上去更加 native(三):自定義 style

編輯:關於C語言

前面我們一再強調,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() 這個函數:

  1. void MyPushButton::paintEvent(QPaintEvent *) 
  2.     QStyleOptionButton option; 
  3.     option.initFrom(this); 
  4.     qDebug() << option.state; 
  5.     option.state |= isDown() ? QStyle::State_Sunken : QStyle::State_Raised; 
  6.     qDebug() << option.state; 
  7.     if (isDefault()) 
  8.         option.features |= QStyleOptionButton::DefaultButton; 
  9.     option.text = text(); 
  10.     option.icon = icon(); 
  11.  
  12.     QPainter painter(this); 
  13.     style()->drawControl(QStyle::CE_PushButton, &option, &painter, this); 
盡管前面說過,我們需要重頭繪制整個組件,但實際上,Qt 為我們提供了一系列方便的函數,用於繪制出各個組件。這種在將組建組合的時候非常有用。例如,一個 combo box 實際上是一個 button 加上一個向下的三角形構成。那麼,我不需要將整個 combo box 用像素畫出來,而是借用 Qt 已有的組建繪制,畫出一個 button 和一個三角形就可以了。所以,這裡我們也使用類似的思路,讓 Qt 繪制出組件,我們要做的就是修改參數,讓它按照我們的參數繪制。   如果調用 Qt 的組件繪制函數呢?這個繪制函數是 QStyle 類的成員。QWidget 提供了 style() 函數,返回當前的 QStyle 對象。那麼,我們就可以通過這個對象繪制。注意上面代碼中最後一行,我們從這裡看起。下面給出這個函數的簽名:
  1. 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,覆蓋我們感興趣的函數即可。看下面一個簡單的實例:
  1. class MyProxyStyle : public QProxyStyle 
  2.   public: 
  3.     void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const; 
  4. }; 
  5.  
  6. void MyProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const 
  7.     if(element == QStyle::CE_PushButtonLabel) { 
  8.         painter->drawText(option->rect, "fixed"); 
  9.     } else { 
  10.         QProxyStyle::drawControl(element, option, painter, widget); 
  11.     } 
MyProxyStyle 覆蓋了 drawControl() 函數,然後判斷,如果是 button label 的話,繪制文本 “fixed”。可想而知,我們的 QPushButton::setText() 函數已經沒有作用了,因為我們在繪制時沒有使用這個屬性,也就不會顯示出來了。不管你設不設置,所有 button 的 text 都會是 fixed。如果要使用這個 style ,需要在運行前設置,例如:
  1. int main(int argc, char **argv) 
  2.     QApplication app(argc, argv); 
  3.     app.setStyle(new MyProxyStyle); 
  4.     MainWindow w; 
  5.     w.show(); 
  6.     return app.exec(); 
這樣,我們就可以用我們自己的 style 顯示組件了。   就像前面所說,自定義 style 是一個相當復雜的話題,我們不可能在這裡完全說明。不過,也正因為 Qt 提供了這種機制,也能夠讓我們可以比較輕松地實現自定義 style。

本文出自 “豆子空間” 博客,請務必保留此出處http://devbean.blog.51cto.com/448512/471941

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved