Linux下程式開發:用QT建立新風格

來源:互聯網
上載者:User

1.Qt的風格

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封裝了這些資訊。


2.建立新風格

下面用一個例子來介紹一下建立新風格的整個過程,在編程之前,先看一下最終的結果是什麼樣的。(在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();
}

3.使用新風格

有兩種方法使用新風格,一種是作為外掛程式,一種是在應用程式裡直接使用。作為外掛程式的風格可以在不用修改代碼、不用重新編譯的情況下使用新風格。由於本文著重介紹如何建立風格所以我們使用第一種方法。這種方法很簡單,只需在應用程式中包含相應風格類的標頭檔,然後把main()函數第一句可執行代碼設定為QApplication::setStyle(new MyStyle())即可。

下面我們用一個小例子來看看效果。


#i nclude <qapplication.h>
#i nclude <qspinbox.h>
#i nclude "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
  • 運行可執行程式。

4.進一步工作

1)預設大小:細心的朋友可能看到上面的代碼中有一句:spin.resize( 20, 100 ),這一句設定spinbox的長度為20象素,寬度為100個象素。如果沒有這一句的話,顯示的結果會一團糟,兩個按鈕幾乎看不到


,因為qt預設的顯示是水平顯示而根本沒有考慮垂直顯示的情況。

如果想讓spinbox在預設情況下看起來長度窄而寬度高需要修改QSpinBox類中的sizeHint函數,這個函數功能是設定組件(widget)的預設尺寸。在qt中幾乎每個GUI組件類都有sizeHint這個函數來設定它自己的預設的長和寬。

文本垂直顯示:在此例中雖然控制項spinbox達到了垂直顯示的效果,但是文本仍舊是水平顯示的,因此要達到真正的垂直顯示需要瞭解qt的文本顯示機制。這些工作是很有意義的,因為有些民族(如蒙文)的語言傳統就是垂直顯示的,而現在沒有一個真正滿足這種需求的系統。筆者現在正在看qt-x11- free-3.2.2的源碼,目前對文本顯示機制只有初步瞭解,還沒有真正弄清,非常希望和感興趣的朋友相互交流、學習。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.