a) Qt簡介
Qt是一個跨平臺的C++圖形用戶界面應用程序開發庫,使用Qt可以開發出高質量的圖形用戶接口,它是完全面向對象的、易於擴展且允許真正的組件編程。Qt獲得了很大的成功,特別是它的信號-槽機制是非常值得研究的通信機制,它也是Linux發行版標準組件KDE(K Desktop Enviroment)的基礎。
b) 風格機制
Qt的風格機制實現了不同平臺上的圖形用戶接口(GUI)的觀感(look and feel),例如Windows平臺上通常使用Windows或Windows-xp風格,而Unix平臺上通常使用Motif、CDE風格。
下圖顯示了Qt中與風格相關的類的繼承關係
QStyle是所有風格類的基類,它控制着所有的部件(widget即windows編程中的控件)的界面風格或觀感,它定義了大量的枚舉類型和十幾個函數。枚舉類型表示界面上的不同元素(如組合框中的按鈕,按鈕的邊框等);函數控制圖形用戶界面的繪製,但大多數函數基本上只是一些聲明而沒有函數實現,他們的實現在QCommonStyle、QWindowStyle、QMotifStyle及其子類中。QStyle只實現了3個函數drawItem(), itemRect(), visualRect()。
drawItem(): 負責繪製文本和象素圖。
itemRect(): 返回文本或圖像所佔的區域。
visualRect(): 返回邏輯座標,這個函數使Qt實現right-to-left風格(阿文、維文傳統是文本從右向左顯示,因此控件佈局也是從右向左)。如下圖所示:
可以看到菜單、工具條是右對齊、單選框的按鈕在右邊
c) 創建新風格的步驟
在Qt中實現一種新風格的步驟很簡單:只需選擇一個風格類(如QCommonStyle或QStyle)作爲父類,然後實現感興趣的函數即可。難點在於函數的實現。
選擇父類:可以選擇QStyle, QCommonStyle, QWindowStyle, QMotifStyle以及他們的子類的任意一個作爲父類。通常可以選擇QWindowsStyle或QMotifStyle,也可以選擇QCommonStyle甚至是QStyle,但是工作量會比較大,因爲很多界面的細節需要自己實現。
重新實現必要的函數:想修改界面風格的哪部分,就重新實現與繪製那部分相關的函數,下面解釋一下我們要重載的QStyle中的幾個函數,這幾個函數控制着圖形用戶界面上不同元素的佈局和觀感。
1)void drawPrimitive( PrimitiveElement pe, QPainter *p, const QRect & r, const QColorGroup & cg, SFlags flags = Style_Default, const QStyleOption &opt = QStyleOption::Default ) ; |
功能:繪製基本圖形元素,如QSpinBox中的帶箭頭的按鈕 等。
參數: PrimitiveElement pe: 這個枚舉型變量表示將要繪製的基本圖形界面元素(這裏說的基本圖形用戶界面元素指GUI中不可再分的一個原子元素,如組合框
中的這個繪有黑色三角形的按鈕,spinBox中的按鈕
QPainter *p:指向QPainter類的指針,Qt中的所有繪製操作不管是繪製文本、圖形還是圖像都由這個類來處理。
QRect &r: 表示一個矩形區域,Qt在這個區域中繪製基本界面元素(PrimitiveElement).
QColorGroup &cg: QColorGroup表示一個部件(widget)的顏色組(color group),color group含有部件繪製自己時使用的各種顏色,譬如前景色背景色等。下圖展示了color group中的各種顏色屬性
SFlags flags: 控制如何繪製圖形界面元素的標誌。
QStyleOption &opt: 繪製不同的部件(widget)時會需要不同的參數,如繪製面板(panel)可能需要線寬作爲額外參數而繪製焦點矩形(focus rect)可能需要背景色作爲額外參數,所以Qt專門提供了一個類QStyleOption來封裝不同的widget可能需要的不同的參數,opt指向這樣一個類的對象。
2)void drawComplexControl( ComplexControl control, QPainter *p, const QWidget *widget, const QRect &r, const QColorGroup &cg, SFlags flags = Style_Default, SCFlags controls = QStyle::SC_All, SCFlags active = QStyle::SC_None, const QStyleOption& opt = QStyleOption::Default) |
功能:繪製複雜控制部件(widget)如SpinWidget,comboBox,slider,listView等
參數:
ComplexControl control:是一個枚舉量,表示將要繪製的複雜控制部件(widget)如組合框、列表框等。
QPainter *p:指向QPainter的指針,Qt中的所有繪製操作不管是繪製文本、圖形還是圖像都由這個類來處理。
QWidget *widget:指向QWdget或其子類的指針,可以根據上面control的值轉變(cast)成合適的類型,例如如果要繪製QSpinWidget,那麼control取值爲CC_SpinWidget,而widget指向一個QSpinWidget(QWidget的子類)的實例(instance)。使用這個變量可以訪問QSpinWidget的成員函數和成員變量,譬如可以調用QSpinWidget的sizeHint函數獲得這個部件的缺省大小(一個矩形空間)。
QRect &r: 表示一個矩形區域,Qt在這個區域中繪製控件或其子部件。
QColorGroup &cg: QColorGroup表示一個部件(widget)的顏色組(color group),color group含有部件繪製自己時使用的各種顏色,譬如前景色背景色等。
SFlags flags: 控制如何繪製圖形界面元素的標誌
SCFlags controls表示繪製複雜控制部件control的哪個子部件,缺省爲SC_All,即繪製整個control而不是其某個子部件(注意control, controls是兩個不同的參數)
QStyleOption &opt: 在繪製不同的部件時可能需要不同的額外的參數,這個變量在繪製不同的widget時提供不同的信息。
3) QRect querySubControlMetrics(ComplexControl control, const QWidget* widget, SubControl sc, const QStyleOption& = QStyleOption::Default) |
功能:獲取子部件的座標和尺寸信息。這個函數控制着一個複雜控件的佈局,重載這個函數可以使的組合框的下拉按鈕繪製在左邊 而不是默認的右邊。
參數:
ComplexControl control: 枚舉量,表示將要繪製的複雜控制部件(widget)如組合框、列表框等。
QWidget *widget:指向QWidget或其子類的指針,可以根據上面control的值轉變(cast)成合適的類型,例如如果要繪製QSpinWidget,那麼control取值爲CC_SpinWidget,而widget指向一個QSpinWidget(QWidget的子類)的實例。使用這個變量可以訪問QSpinWidget的成員函數和成員變量,譬如可以調用QSpinWidget的sizeHint函數獲得這個部件的缺省大小(一個矩形空間)。
SubControl sc:枚舉量,一個複雜部件可能由多個的子部件組成,使用sc變量說明要獲取那個子部件的座標和尺寸信息。
QStyleOption &opt: 計算不同部件的尺寸時可能需要不同的額外信息,QStyleOption封裝了這些信息。
下面用一個例子來介紹一下創建新風格的整個過程,在編程之前,先看一下最終的結果是什麼樣的。(在Qt內部QSpinBox類是通過QSpinWidget實現的)
默認風格的效果: | 使用新風格的效果: |
可以看到在新風格中我們的SpinBox有了垂直顯示的效果。下面我們按上面說明的步驟來創建一種新的風格。
1)選擇基類:我們選擇QWindowsStyle類作爲我們新風格類的基類,當然也可以選擇QMotifStyle,在這個例子種也可以選擇QCommonStyle。一般不建議選擇QCommonStyle作爲基類,因爲QCommonStyle只實現了一部分界面部件,如果要實現一個完整的風格類,我們需要重新寫很多代碼。
2)重載相關的函數:在這個例程中我們只修改了spinBox的風格,實現這個部件(widget)只與QStyle類的三個函數drawPrimitive, drawComplexControl, qureySubControlMerics相關,所以我們只需重載這三個函數的相關部分代碼.下面對代碼中的關鍵部分做一下注釋,不重要的部分省略了。詳細的代碼可以從後面下載。
繪製spinbox中按鈕的函數:
void CustomStyle::drawPrimitive( PrimitiveElement pe, QPainter * p, const QRect & r, const QColorGroup & cg, SFlags flags, const QStyleOption & opt ) const { /*PE_SpinWidgetUp,PE_SpinWidgetDown表示spinBox中的下按鈕和上按鈕, 下面的代碼使得這兩個按鈕中的三角形分別向左和向右*/ if ((pe == PE_SpinWidgetUp) || (pe == PE_SpinWidgetDown)){ int fw = pixelMetric( PM_DefaultFrameWidth, 0 );//fw表示邊框寬度,默認爲2 QRect br; //spinBox上按鈕的邊界矩形不是spinBox的邊界矩形。 br.setRect( r.x() + fw, r.y() + fw, r.width() - fw*2, r.height() - fw*2 ); p->fillRect( br, cg.brush( QColorGroup::Button ) ); int x = r.x(), y = r.y(), w = r.width(), h = r.height(); int sw = w-4; int sh = sw/2 + 2; // Must have empty row at foot of arrow int sx = x + w / 2 - sw / 2 - 1; int sy = y + h / 2 - sh / 2 - 1; QPointArray a; /* 設置三角形的三個點的座標,修改這三個點可以使得QSpinBox上按鈕裏的三角型呈現任意的形狀, 下面的設置使得三角形表示的箭頭分別向左和向右。*/ if ( pe == PE_SpinWidgetDown ) a.setPoints( 3, 0, sh/2, sw-1, 1, sw-1, sh-1 ); else a.setPoints( 3, 0, 1, 0, sh-1, sw-1, sh/2 ); ........... p->drawPolygon( a ); //繪製三角形 }else if((pe == PE_ButtonBevel) || (pe == PE_ButtonCommand) || (pe == PE_ButtonTool) || (pe == PE_ButtonDropDown) || (pe == PE_HeaderSection)) { //繪製按鈕的各種效果使得看起來凸起或凹下。 qDrawShadePanel(p, r, cg, flags & (Style_Sunken | Style_Down | Style_On), 1, &cg.brush(QColorGroup::Button)); }else{ /*對於其他基本圖形元素(PrimitiveElement)的繪製我們不作處理只是簡單的調用父類的函數。*/ QWindowsStyle::drawPrimitive( pe, p, r, cg, flags, opt); } } |
繪製整個spinBox的函數:
void CustomStyle::drawComplexControl( ComplexControl control, QPainter *p, const QWidget *widget, const QRect &r, const QColorGroup &cg, SFlags flags, SCFlags controls, SCFlags active, const QStyleOption& opt ) const { //下面的代碼使得spinWidget呈現垂直顯示的風格而不是通常的水平顯示 if (control == CC_SpinWidget) { const QSpinWidget * sw = (const QSpinWidget *) widget; //繪製向上按鈕部分,controls默認爲SC_All,即繪製整個spinwidget if ( controls & SC_SpinWidgetUp ) { if ( sw->buttonSymbols() == QSpinWidget::PlusMinus ) pe = PE_SpinWidgetPlus; // 使用加減號 |
else pe = PE_SpinWidgetUp; // 使用三角形 |
QRect re = sw->upRect(); QColorGroup ucg = sw->isUpEnabled() ? cg : sw->palette().disabled(); drawPrimitive(PE_ButtonBevel, p, re, ucg, flags); //繪製按鈕的邊框 drawPrimitive(pe, p, re, ucg, flags); //繪製按鈕 } //繪製向左按鈕部分。 if ( controls & SC_SpinWidgetDown ) { /*與繪製向下按鈕相似*/ } }else{//不處理spinbox之外的其他複雜控制部件,調用父類函數處理 QWindowsStyle::drawComplexControl(control, p, widget, r, cg, flags, controls, active, opt); } } |
獲取部件(widget)中各個子部件佈局信息的函數,這個函數控制着一個widget的外觀
QRect CustomStyle::querySubControlMetrics( ComplexControl control, const QWidget *widget, SubControl sc, const QStyleOption &opt ) const { if(control == CC_SpinWidget){ int fw = pixelMetric( PM_SpinBoxFrameWidth, widget); /*QSize 定義二維對象的大小,也就是寬和高. 座標類型是QCOORD定義爲int)*/ QSize bs; //此處bs表示每個按鈕的大小,因爲有兩個按鈕所以下面除以2. bs.setWidth(widget->width()/2 -fw); if(bs.width() < 8) bs.setWidth(8); /*按鈕高度設置爲QMIN{按鈕寬度的1.6倍, 部件高度的四分之一} bs.setHeight( QMIN(bs.width()*8/5, widget->height() / 4) ); bs = bs.expandedTo( QApplication::globalStrut() ); int x = fw; int y, ly, ry; y = widget->height() - x - bs.height(); ly = fw; ry = y - fw; //下面定義了QSpinWidget的各個子部件的座標位置. switch ( sc ) { case SC_SpinWidgetUp: //返回向右按鈕的座標信息 return QRect(x + bs.width(), y, bs.width(), bs.height()); case SC_SpinWidgetDown: //返回向左按鈕的座標信息 return QRect(x, y, bs.width(), bs.height()); case SC_SpinWidgetButtonField: //返回兩個按鈕的總區域大小 return QRect(x, y, widget->width() - 2*fw, bs.height()); case SC_SpinWidgetEditField: /*返回可編輯框的座標信息*/ return QRect(fw, ly, widget->width() - 2*fw, ry); case SC_SpinWidgetFrame: //返回整個spinBox的座標信息 return widget->rect(); default: break; } }else{//其它部件的佈局信息調用父類的函數來處理。 return QWindowsStyle::querySubControlMetrics(control,widget,sc,opt ); } return QRect(); } |
有兩種方法使用新風格,一種是作爲插件,一種是在應用程序裏直接使用。作爲插件的風格可以在不用修改代碼、不用重新編譯的情況下使用新風格。由於本文着重介紹如何創建風格所以我們使用第一種方法。這種方法很簡單,只需在應用程序中包含相應風格類的頭文件,然後把main()函數第一句可執行代碼設置爲QApplication::setStyle(new MyStyle())即可。
下面我們用一個小例子來看看效果。
#include <qapplication.h> #include <qspinbox.h> #include "customstyle.h" int main( int argc, char **argv ) { QApplication::setStyle(new CustomStyle()); //使用新風格類來繪製界面。 QApplication a( argc, argv ); QSpinBox spin( 0, 15 ); spin.resize( 20, 100 ); a.setMainWidget( &spin ); spin.show(); return a.exec(); } |
然後編譯運行即可看到效果。
Ps. qt中編譯使用qmake,步驟爲
- 創建源程序
- 同一目錄下運行qmake -project
- qmake
- make
- 運行可執行程序。
1)默認大小:細心的朋友可能看到上面的代碼中有一句:spin.resize( 20, 100 ),這一句設置spinbox的長度爲20象素,寬度爲100個象素。如果沒有這一句的話,顯示的結果會一團糟,兩個按鈕幾乎看不到
,因爲qt默認的顯示是水平顯示而根本沒有考慮垂直顯示的情況。
如果想讓spinbox在默認情況下看起來長度窄而寬度高需要修改QSpinBox類中的sizeHint函數,這個函數功能是設置部件(widget)的默認尺寸。在qt中幾乎每個GUI部件類都有sizeHint這個函數來設置它自己的默認的長和寬。
文本垂直顯示:在此例中雖然控件spinbox達到了垂直顯示的效果,但是文本仍舊是水平顯示的,因此要達到真正的垂直顯示需要了解qt的文本顯示機制。這些工作是很有意義的,因爲有些民族(如蒙文)的語言傳統就是垂直顯示的,而現在沒有一個真正滿足這種需求的系統。筆者現在正在看qt-x11-free-3.2.2的源碼,目前對文本顯示機制只有初步瞭解,還沒有真正弄清,非常希望和感興趣的朋友相互交流、學習。
- 實例
源代碼下載
- Qt官方文檔 http://doc.trolltech.com/3.2/index.html
- Qt源代碼:QStyle, QCommonStyle,QSpinWidget,QSpinBox從中可以瞭解很多的細節.
http://www.ibm.com/developerworks/cn/linux/l-qtstyle/ http://www.ibm.com/developerworks/cn/linux/l-qtstyle/