黃艷芳
(電子科技大學(xué) 通信與信息工程學(xué)院,四川 成都 611731)
圖形用戶界面是用戶與計(jì)算機(jī)進(jìn)行交互的操作方式,即用戶與計(jì)算機(jī)之間互相傳遞信息的方式,它因美觀、大方、簡單易用而深受廣大用戶的喜愛。由挪威TrollTech公司開發(fā)的Qt是一個用于跨平臺的圖形界面程序開發(fā)的C++工具包,提供給應(yīng)用程序開發(fā)者建立圖形用戶界面所需的所有功能。Qt是使用源代碼級“一次編寫,隨處編譯”的方式用于構(gòu)建多平臺圖形用戶界面程序,它完全面向?qū)ο笄液苋菀讛U(kuò)展,提供給應(yīng)用程序開發(fā)者建立藝術(shù)級圖形用戶界面所需的功能,提供了信號與槽的機(jī)制替代回調(diào)函數(shù),使組件間信號傳遞更安全、簡單,因此,它已經(jīng)成為全世界范圍內(nèi)數(shù)千種成功的應(yīng)用程序的基礎(chǔ),為世界上數(shù)千個最大的公司,包括IBM、摩托羅拉和夏普等提供開發(fā)軟件[1]。游戲需要給玩家提供藝術(shù)級圖形用戶界面,讓玩家不僅使用方便,更要達(dá)到視覺上的愉悅。因此,使用Qt開發(fā)游戲是一個不錯的選擇。
信號和槽機(jī)制是Qt編程的基礎(chǔ)。這個機(jī)制可以讓編程人員把這些互不了解的對象綁定在一起[2]。信號是一個特定的標(biāo)識;一個槽就是一個函數(shù),槽和普通的C++成員函數(shù)幾乎是一樣的——可以是虛函數(shù);可以被重載;可以是公有的、保護(hù)的或者私有的,并且也可以被其他C++成員函數(shù)直接調(diào)用;還有,它們的參數(shù)可以是任意類型。唯一不同的是,槽還可以和信號連接在一起。當(dāng)某個事件出現(xiàn)時,通過發(fā)送信號,可以將與之相關(guān)的槽函數(shù)激活,即執(zhí)行槽函數(shù)代碼。在程序中,使用 QObject::connect()函數(shù)來將某個信號和某個槽進(jìn)行關(guān)聯(lián),而信號和槽之間的真正關(guān)聯(lián)是由Qt的信號和槽機(jī)制來實(shí)現(xiàn)的。 connect函數(shù)語法如下:connect(sender,SIGNAL(signal),receiver,SLOT(slot))。 sender和 receiver是 QObject對象指針,signal和slot是不帶參數(shù)的函數(shù)原型。
信號和槽的關(guān)聯(lián)關(guān)系可以有幾種模式:1)一個信號和一個槽關(guān)聯(lián);2)一個信號和多個槽關(guān)聯(lián),當(dāng)發(fā)射這個信號的時候,會以不確定的順序一個接一個地調(diào)用這些槽;3)多個信號和一個槽關(guān)聯(lián),無論發(fā)射的是哪一個信號,都會調(diào)用這個槽。另外,一個信號還可以與另外一個信號相連接,當(dāng)發(fā)射第一個信號時,也會發(fā)射第二個信號,例如:connect(lineEdit,SIGNAL(textChanged(constQString &)),this,SIGNAL(update-Record(constQString&)))。信號與信號之間的連接和信號與槽之間的連接有時是難以區(qū)分的。
信號與槽除了可以在程序中用connect函數(shù)手動關(guān)聯(lián)外,Qt的元對象還提供了信號與槽的自動關(guān)聯(lián)。對于Qt窗口部件已經(jīng)提供的信號,如果能按下面的規(guī)則命名槽函數(shù),那么Qt就能夠自動進(jìn)行關(guān)聯(lián):
void on_<窗口部件名>_<信號名稱>_(<信號參數(shù)>)
本文設(shè)計(jì)的單機(jī)小游戲Lines中,對所有控件pushButton采用的都是自動關(guān)聯(lián)信號與槽的方式 void_on_pushButton_clicked(),這樣更加簡單快捷。
“事件”功能,簡單來說就是當(dāng)一個事件產(chǎn)生后,相關(guān)控件作出回應(yīng)。Qt事件的處理過程是,首先QApplication的事件循環(huán)體從事件隊(duì)列中拾取本地窗口系統(tǒng)事件或者其他事件 ,譯成 QEvent;然 后 ,把 QEvent送 給 QObject:event();最后,再送給 QWidget:event(),對事件進(jìn)行處理[2]。 Qt已經(jīng)將上述那些繁瑣的調(diào)用步驟封裝,極大減輕了編程人員的負(fù)擔(dān)。
Qt的2D圖形系統(tǒng)的基礎(chǔ)是類QPainter,QPainter能夠繪制各種幾何圖形(點(diǎn),線,矩形,橢圓,圓弧,弦,扇形,多線段,貝賽爾曲線),還能繪制位圖,圖像和文字[3]。在控件上繪圖時,先創(chuàng)建一個QPainter,把繪圖設(shè)備指針傳給QPainter對象,然后在繪圖函數(shù) paintEvent(QPaintEvent*event)中,通過函數(shù)painter.setPen()設(shè)置畫筆的顏色、大小和所繪曲線類型;通過函數(shù)painter.draw函數(shù)繪制需要的圖形;通過函數(shù)QBrush brush(QColor())設(shè)置畫刷顏色,用畫刷給圖形填充顏色。
paintEvent()函數(shù)是一個事件處理函數(shù),在控件需要重新繪制的時候調(diào)用。Qt中很多情況下都會產(chǎn)生繪制事件,調(diào)用paintEvent()函數(shù):
1)當(dāng)控件第一次顯示時,Qt自動產(chǎn)生繪制事件使控件繪制自身;
2)當(dāng)控件尺寸發(fā)生變化時,系統(tǒng)產(chǎn)生繪制事件;
3)如果控件被其他的窗口遮住,窗口移走時,產(chǎn)生繪制被遮住部分的事件。
4)如 果 調(diào) 用 了 QWidget:update 和 QWidget:repaint()函數(shù),產(chǎn)生繪制事件。
update()函數(shù)和 repaint()函數(shù)有所不同。 repaint()立刻產(chǎn)生繪制事件,重新繪制控件;而調(diào)用update()后,只是交給Qt一個產(chǎn)生繪制事件的計(jì)劃。如果控件在屏幕上不可見,那么這兩個函數(shù)什么都不做。如果update()被調(diào)用了多次之后,Qt就把這幾個連續(xù)的繪制事件合為一個事件避免閃爍。
本文設(shè)計(jì)的單機(jī)小游戲Lines,就是通過this->update()函數(shù)來重繪游戲區(qū)的。
Qt提供了在大多數(shù)GUI應(yīng)用程序中通常都需要的操作:異步播放聲音文件。播放聲音有兩種方式[4]:
1)QSound:play("debug/sound/click.wav");2)QSound:bells("debug/sound/click.wav");bells.play();第二種方式播放聲音會消耗更多內(nèi)存,但依靠底層平臺的音頻設(shè)備,比起第一種方式播放聲音更直接。在微軟windows下使用的底層多媒體系統(tǒng),僅支持WAVE格式的聲音文件。
Qt中的QTimer類提供了定時器信號和單觸發(fā)定時器[3]。它在內(nèi)部使用定時器事件來提供更通用的定時器。QTimer的使用比較簡單:創(chuàng)建一個QTimer,使用start()來開始并且把它的timeout()連接到適當(dāng)?shù)牟?。?dāng)這段時間過去了,它將會發(fā)射 timeout()信號。
QTimer的精確度依賴于底下的操作系統(tǒng)和硬件。絕大多數(shù)平臺支持20ms的精確度,一些平臺可以提供更高的。如果Qt不能傳送定時器觸發(fā)所要求的數(shù)量,它將會默默地拋棄一些。一些操作系統(tǒng)限制可能用到的定時器的數(shù)量,Qt會盡力在限制范圍內(nèi)工作。當(dāng)QTimer的父對象被銷毀時,它也會被自動銷毀。
QPushButton窗口部件提供了命令按鈕,主要用來提供點(diǎn)擊動作。。
控件pushButton用自動關(guān)聯(lián)信號與槽的方式void_on_pushButton_clicked()關(guān)聯(lián)信號與槽。當(dāng)按鈕被鼠標(biāo)、空格鍵或者鍵盤快捷鍵激活,它發(fā)射clicked()信號,連接這個信號來執(zhí)行按鈕的操作。
如圖1所示,游戲中有7種顏色的小球,81個可放置小球的位置。每移動一個小球,隨機(jī)發(fā)射3個小球,每個小球的顏色是七種顏色中的任意一種。這3個小球隨機(jī)放置在81個位置中沒有小球的地方。游戲目標(biāo)是將同一顏色的球排在同一直線上(橫、豎、斜著排都可以)。當(dāng)5個或5個以上顏色相同的小球排在同一直線上時,小球會被移除,并得分。同一直線上相同顏色小球個數(shù)越多,得分越高。當(dāng)81個位置都布滿小球時,游戲結(jié)束。
圖1 游戲主界面Fig.1 Main interface of the game
目前,在Windows平臺下使用Qt非常方便,不再像以前一樣,需要再經(jīng)過幾個小時的自己編譯。目前比較流行的有把Qt的開發(fā)環(huán)境集成到Visual Studio 2008環(huán)境中[5]和直接使用Qt Creator而僅借用Visual Studio 2008的編譯器。本文中的界面構(gòu)建使用的是免費(fèi)的 “QtCreator+Visual Studio 2008編譯器”:
1)下載并安裝好Visual Studio 2008。
2)下載開源版的 qt-sdk-win-opensource-2010.04.exe,安裝好。
3)設(shè)置環(huán)境變量: 右鍵“我的電腦”->“屬性”->“高級”->“環(huán)境變量”,在PATH,INCLUDE和LIB中分別填入環(huán)境變量:
至此,開發(fā)環(huán)境搭建完成。
通過子類化QMainWindow創(chuàng)建游戲應(yīng)用程序的用戶界面。
游戲具體代碼設(shè)計(jì)與實(shí)現(xiàn)過程大致分為兩步:1)用Qt建立GUI界面的主框架;2)調(diào)用信號與槽機(jī)制和事件功能以完善游戲的詳細(xì)功能。
2.3.1 用Qt建立GUI界面的主框架
游戲的主框架如圖2所示。主要有菜單欄、工具欄、圖標(biāo)、用以計(jì)分的標(biāo)簽和各種按鈕。
圖2 游戲主框架Fig.2 Main frame of the game
2.3.2 完善游戲的詳細(xì)功能
通過信號與槽機(jī)制和事件功能,借用QTimer定時刷新程序完成菜單欄、工具欄、圖標(biāo)、計(jì)分、各種按鈕及繪圖的功能。
1)創(chuàng)建菜單欄和工具欄
本游戲需要一個菜單欄和一個工具欄,菜單欄提供游戲的聲音、重新開始、退出及幫助等按鈕;工具欄把菜單欄里的功能用圖標(biāo)表示出來,方便用戶快速使用那些功能。菜單欄和工具欄使用了action(動作)的概念。一個action就是一個可以添加到任意菜單欄和工具欄上的項(xiàng)。創(chuàng)建菜單欄和工具欄時,按照如下步驟進(jìn)行:創(chuàng)建并設(shè)置action->創(chuàng)建菜單并把a(bǔ)ction添加到菜單上->創(chuàng)建工具欄并把a(bǔ)ction添加到工具欄上。每選擇一個菜單項(xiàng),都會觸發(fā)相應(yīng)的槽函數(shù)。在此,對于每一個action,信號與槽的關(guān)聯(lián)我們不用connect函數(shù)手動關(guān)聯(lián),而是采用自動關(guān)聯(lián)的方式,即,對于“重新開始”菜單項(xiàng),我們在創(chuàng)建該菜單項(xiàng)的時候,就命名為action_R:
private slots:void on_action_R_triggered();
則當(dāng)選擇“重新開始”菜單項(xiàng)的時候,此動作觸發(fā)了槽函數(shù) restart()函數(shù):
restart()函數(shù)定義在私有類中,該函數(shù)主要作用是使游戲重新開始。
工具欄中的圖標(biāo)通過Qt Resource File添加到程序中。
2)鼠標(biāo)點(diǎn)擊按鈕事件
界面中,先調(diào)用paintEvent()函數(shù)畫出圖1中藍(lán)色方框里的81個位置;在每個位置上方分別布上一個和81個藍(lán)色方框一樣大小的pushButton按鈕,按鈕名稱為pushButton00~80,如圖2所示。小球在按鈕下方的窗口上畫出。用ui->pushButton->setFlat(true)把按鈕設(shè)置成透明的,這樣在按鈕下方畫圖時,按鈕就不會覆蓋圖形了。
游戲過程中,當(dāng)玩家用鼠標(biāo)點(diǎn)擊相應(yīng)的方塊,程序就會做出相應(yīng)的響應(yīng)事件,程序處理這些事件來完成圖形的繪制[6]。
MainWindow類的定義如下:
下面以 void on_pushButton00_clicked()為例,簡要說明代碼如何為繪圖做準(zhǔn)備:
void Lines :on_pushButton00_clicked () //按鈕被鼠標(biāo)左鍵點(diǎn)擊
void Lines:on_pushButton00_clicked() 函數(shù)中,有很多變量隨著鼠標(biāo)的按下而改變。是否有5個或者以上相同顏色連在一起以及游戲得分可能也隨著鼠標(biāo)按下而改變。這些改變需要通過定時器定時去檢測。檢測到有變量的改變則需要重繪圖形,重繪圖形的指令update放在paint_show()函數(shù)中,系統(tǒng)通過定時器定時調(diào)用paint_show()函數(shù)[7]。
創(chuàng)建一個 QTimer:m_timer=new QTimer (this);
使用start()來開始并且把它的 timeout()連接到適當(dāng)?shù)牟?:connect (m_timer, SIGNAL (timeout ()),this, SLOT(paint_show())); m_timer->start(10);
槽函數(shù):
繪圖函數(shù)paintEvent()主要動態(tài)繪制3部分內(nèi)容:
1)下一次出現(xiàn)的小球,根據(jù)m=qrand()%8得到的隨機(jī)數(shù)的值繪制小球的顏色,不同的數(shù)字代表小球不同的顏色;
2)被移動后重新放置的小球和重新出現(xiàn)的小球,及5個或以上相同顏色小球相連時被移除的小球。這些小球是否被移除或者被繪制根據(jù)paint[i][j]的值,繪制什么顏色根據(jù)j[i][j]的值;
3)被點(diǎn)擊小球外面的紅框,是否有紅框框根據(jù)p_flag[i][j]的值。
關(guān)鍵代碼如下:
//以上繪畫重新放置和重新出現(xiàn)的小球,及5個或以上相同顏色小球相連時被移除的小球
}
本文著重分析了Qt的信號與槽機(jī)制和事件功能,并詳細(xì)描述了繪圖函數(shù)paintEvent()、添加音頻、按鈕QpushButton和定時器QTimer的用法。通過設(shè)計(jì)及實(shí)現(xiàn)單機(jī)小游戲Lines,來表現(xiàn)出Qt在構(gòu)建圖形界面、實(shí)現(xiàn)事件響應(yīng)、繪圖、播放聲音等方面的卓越特性。隨著越來越多的產(chǎn)品需要有完美的操作界面以滿足人機(jī)交互的需求,使用Qt來開發(fā)圖形用戶界面程序?qū)兊迷絹碓綇V泛。
[1]張春艷.基于Qt的嵌入式圖形用戶界面研究與實(shí)現(xiàn)[D].大連:大連海事大學(xué).2008
[2]Blanchette J,Summerfield M.C++GUIProgrammingwith Qt4[M].Prentice HallPTR.2006.
[3]蔡志明.精通Qt4編程[M].北京:電子工業(yè)出版社,2008.
[4]Trolltech.Qt-Cross-Platform C++Development-Trolltech[EB/OL].(2007).http://www.trolltech.com/products/qt/features/index.
[5]李繼平,畢淑娥,易立瓊,等.基于QT4與windows CE的機(jī)器人示教盒界面設(shè)計(jì)[J].現(xiàn)代計(jì)算機(jī), 2010(5):175-177、187.
LI Ji-ping, BI Shu-e, YI Li-qiong,et al.Design of user interface of teaching box based on QT4 and Windows CE[J].Modern Computer,2010(5):175-177、187.
[6]Lippman B, Lajoie, Barbara E.Moo.C++Primer[M].北京人民郵電出版社,2006.
[7]譚浩強(qiáng).C程序設(shè)計(jì)[M].2版.北京清華大學(xué)出版社,2002.