黃艷芳
(電子科技大學(xué) 通信與信息工程學(xué)院,四川 成都 611731)
圖形用戶界面是用戶與計(jì)算機(jī)進(jìn)行交互的操作方式,即用戶與計(jì)算機(jī)之間互相傳遞信息的方式,它因美觀、大方、簡(jiǎn)單易用而深受廣大用戶的喜愛(ài)。由挪威TrollTech公司開(kāi)發(fā)的Qt是一個(gè)用于跨平臺(tái)的圖形界面程序開(kāi)發(fā)的C++工具包,提供給應(yīng)用程序開(kāi)發(fā)者建立圖形用戶界面所需的所有功能。Qt是使用源代碼級(jí)“一次編寫(xiě),隨處編譯”的方式用于構(gòu)建多平臺(tái)圖形用戶界面程序,它完全面向?qū)ο笄液苋菀讛U(kuò)展,提供給應(yīng)用程序開(kāi)發(fā)者建立藝術(shù)級(jí)圖形用戶界面所需的功能,提供了信號(hào)與槽的機(jī)制替代回調(diào)函數(shù),使組件間信號(hào)傳遞更安全、簡(jiǎn)單,因此,它已經(jīng)成為全世界范圍內(nèi)數(shù)千種成功的應(yīng)用程序的基礎(chǔ),為世界上數(shù)千個(gè)最大的公司,包括IBM、摩托羅拉和夏普等提供開(kāi)發(fā)軟件[1]。游戲需要給玩家提供藝術(shù)級(jí)圖形用戶界面,讓玩家不僅使用方便,更要達(dá)到視覺(jué)上的愉悅。因此,使用Qt開(kāi)發(fā)游戲是一個(gè)不錯(cuò)的選擇。
信號(hào)和槽機(jī)制是Qt編程的基礎(chǔ)。這個(gè)機(jī)制可以讓編程人員把這些互不了解的對(duì)象綁定在一起[2]。信號(hào)是一個(gè)特定的標(biāo)識(shí);一個(gè)槽就是一個(gè)函數(shù),槽和普通的C++成員函數(shù)幾乎是一樣的——可以是虛函數(shù);可以被重載;可以是公有的、保護(hù)的或者私有的,并且也可以被其他C++成員函數(shù)直接調(diào)用;還有,它們的參數(shù)可以是任意類(lèi)型。唯一不同的是,槽還可以和信號(hào)連接在一起。當(dāng)某個(gè)事件出現(xiàn)時(shí),通過(guò)發(fā)送信號(hào),可以將與之相關(guān)的槽函數(shù)激活,即執(zhí)行槽函數(shù)代碼。在程序中,使用 QObject::connect()函數(shù)來(lái)將某個(gè)信號(hào)和某個(gè)槽進(jìn)行關(guān)聯(lián),而信號(hào)和槽之間的真正關(guān)聯(lián)是由Qt的信號(hào)和槽機(jī)制來(lái)實(shí)現(xiàn)的。 connect函數(shù)語(yǔ)法如下:connect(sender,SIGNAL(signal),receiver,SLOT(slot))。 sender和 receiver是 QObject對(duì)象指針,signal和slot是不帶參數(shù)的函數(shù)原型。
信號(hào)和槽的關(guān)聯(lián)關(guān)系可以有幾種模式:1)一個(gè)信號(hào)和一個(gè)槽關(guān)聯(lián);2)一個(gè)信號(hào)和多個(gè)槽關(guān)聯(lián),當(dāng)發(fā)射這個(gè)信號(hào)的時(shí)候,會(huì)以不確定的順序一個(gè)接一個(gè)地調(diào)用這些槽;3)多個(gè)信號(hào)和一個(gè)槽關(guān)聯(lián),無(wú)論發(fā)射的是哪一個(gè)信號(hào),都會(huì)調(diào)用這個(gè)槽。另外,一個(gè)信號(hào)還可以與另外一個(gè)信號(hào)相連接,當(dāng)發(fā)射第一個(gè)信號(hào)時(shí),也會(huì)發(fā)射第二個(gè)信號(hào),例如:connect(lineEdit,SIGNAL(textChanged(constQString &)),this,SIGNAL(update-Record(constQString&)))。信號(hào)與信號(hào)之間的連接和信號(hào)與槽之間的連接有時(shí)是難以區(qū)分的。
信號(hào)與槽除了可以在程序中用connect函數(shù)手動(dòng)關(guān)聯(lián)外,Qt的元對(duì)象還提供了信號(hào)與槽的自動(dòng)關(guān)聯(lián)。對(duì)于Qt窗口部件已經(jīng)提供的信號(hào),如果能按下面的規(guī)則命名槽函數(shù),那么Qt就能夠自動(dòng)進(jìn)行關(guān)聯(lián):
void on_<窗口部件名>_<信號(hào)名稱(chēng)>_(<信號(hào)參數(shù)>)
本文設(shè)計(jì)的單機(jī)小游戲Lines中,對(duì)所有控件pushButton采用的都是自動(dòng)關(guān)聯(lián)信號(hào)與槽的方式 void_on_pushButton_clicked(),這樣更加簡(jiǎn)單快捷。
“事件”功能,簡(jiǎn)單來(lái)說(shuō)就是當(dāng)一個(gè)事件產(chǎn)生后,相關(guān)控件作出回應(yīng)。Qt事件的處理過(guò)程是,首先QApplication的事件循環(huán)體從事件隊(duì)列中拾取本地窗口系統(tǒng)事件或者其他事件 ,譯成 QEvent;然 后 ,把 QEvent送 給 QObject:event();最后,再送給 QWidget:event(),對(duì)事件進(jìn)行處理[2]。 Qt已經(jīng)將上述那些繁瑣的調(diào)用步驟封裝,極大減輕了編程人員的負(fù)擔(dān)。
Qt的2D圖形系統(tǒng)的基礎(chǔ)是類(lèi)QPainter,QPainter能夠繪制各種幾何圖形(點(diǎn),線,矩形,橢圓,圓弧,弦,扇形,多線段,貝賽爾曲線),還能繪制位圖,圖像和文字[3]。在控件上繪圖時(shí),先創(chuàng)建一個(gè)QPainter,把繪圖設(shè)備指針傳給QPainter對(duì)象,然后在繪圖函數(shù) paintEvent(QPaintEvent*event)中,通過(guò)函數(shù)painter.setPen()設(shè)置畫(huà)筆的顏色、大小和所繪曲線類(lèi)型;通過(guò)函數(shù)painter.draw函數(shù)繪制需要的圖形;通過(guò)函數(shù)QBrush brush(QColor())設(shè)置畫(huà)刷顏色,用畫(huà)刷給圖形填充顏色。
paintEvent()函數(shù)是一個(gè)事件處理函數(shù),在控件需要重新繪制的時(shí)候調(diào)用。Qt中很多情況下都會(huì)產(chǎn)生繪制事件,調(diào)用paintEvent()函數(shù):
1)當(dāng)控件第一次顯示時(shí),Qt自動(dòng)產(chǎn)生繪制事件使控件繪制自身;
2)當(dāng)控件尺寸發(fā)生變化時(shí),系統(tǒng)產(chǎn)生繪制事件;
3)如果控件被其他的窗口遮住,窗口移走時(shí),產(chǎn)生繪制被遮住部分的事件。
4)如 果 調(diào) 用 了 QWidget:update 和 QWidget:repaint()函數(shù),產(chǎn)生繪制事件。
update()函數(shù)和 repaint()函數(shù)有所不同。 repaint()立刻產(chǎn)生繪制事件,重新繪制控件;而調(diào)用update()后,只是交給Qt一個(gè)產(chǎn)生繪制事件的計(jì)劃。如果控件在屏幕上不可見(jiàn),那么這兩個(gè)函數(shù)什么都不做。如果update()被調(diào)用了多次之后,Qt就把這幾個(gè)連續(xù)的繪制事件合為一個(gè)事件避免閃爍。
本文設(shè)計(jì)的單機(jī)小游戲Lines,就是通過(guò)this->update()函數(shù)來(lái)重繪游戲區(qū)的。
Qt提供了在大多數(shù)GUI應(yīng)用程序中通常都需要的操作:異步播放聲音文件。播放聲音有兩種方式[4]:
1)QSound:play("debug/sound/click.wav");2)QSound:bells("debug/sound/click.wav");bells.play();第二種方式播放聲音會(huì)消耗更多內(nèi)存,但依靠底層平臺(tái)的音頻設(shè)備,比起第一種方式播放聲音更直接。在微軟windows下使用的底層多媒體系統(tǒng),僅支持WAVE格式的聲音文件。
Qt中的QTimer類(lèi)提供了定時(shí)器信號(hào)和單觸發(fā)定時(shí)器[3]。它在內(nèi)部使用定時(shí)器事件來(lái)提供更通用的定時(shí)器。QTimer的使用比較簡(jiǎn)單:創(chuàng)建一個(gè)QTimer,使用start()來(lái)開(kāi)始并且把它的timeout()連接到適當(dāng)?shù)牟邸.?dāng)這段時(shí)間過(guò)去了,它將會(huì)發(fā)射 timeout()信號(hào)。
QTimer的精確度依賴(lài)于底下的操作系統(tǒng)和硬件。絕大多數(shù)平臺(tái)支持20ms的精確度,一些平臺(tái)可以提供更高的。如果Qt不能傳送定時(shí)器觸發(fā)所要求的數(shù)量,它將會(huì)默默地拋棄一些。一些操作系統(tǒng)限制可能用到的定時(shí)器的數(shù)量,Qt會(huì)盡力在限制范圍內(nèi)工作。當(dāng)QTimer的父對(duì)象被銷(xiāo)毀時(shí),它也會(huì)被自動(dòng)銷(xiāo)毀。
QPushButton窗口部件提供了命令按鈕,主要用來(lái)提供點(diǎn)擊動(dòng)作。。
控件pushButton用自動(dòng)關(guān)聯(lián)信號(hào)與槽的方式void_on_pushButton_clicked()關(guān)聯(lián)信號(hào)與槽。當(dāng)按鈕被鼠標(biāo)、空格鍵或者鍵盤(pán)快捷鍵激活,它發(fā)射clicked()信號(hào),連接這個(gè)信號(hào)來(lái)執(zhí)行按鈕的操作。
如圖1所示,游戲中有7種顏色的小球,81個(gè)可放置小球的位置。每移動(dòng)一個(gè)小球,隨機(jī)發(fā)射3個(gè)小球,每個(gè)小球的顏色是七種顏色中的任意一種。這3個(gè)小球隨機(jī)放置在81個(gè)位置中沒(méi)有小球的地方。游戲目標(biāo)是將同一顏色的球排在同一直線上(橫、豎、斜著排都可以)。當(dāng)5個(gè)或5個(gè)以上顏色相同的小球排在同一直線上時(shí),小球會(huì)被移除,并得分。同一直線上相同顏色小球個(gè)數(shù)越多,得分越高。當(dāng)81個(gè)位置都布滿小球時(shí),游戲結(jié)束。

圖1 游戲主界面Fig.1 Main interface of the game
目前,在Windows平臺(tái)下使用Qt非常方便,不再像以前一樣,需要再經(jīng)過(guò)幾個(gè)小時(shí)的自己編譯。目前比較流行的有把Qt的開(kāi)發(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)下載開(kāi)源版的 qt-sdk-win-opensource-2010.04.exe,安裝好。
3)設(shè)置環(huán)境變量: 右鍵“我的電腦”->“屬性”->“高級(jí)”->“環(huán)境變量”,在PATH,INCLUDE和LIB中分別填入環(huán)境變量:

至此,開(kāi)發(fā)環(huán)境搭建完成。
通過(guò)子類(lèi)化QMainWindow創(chuàng)建游戲應(yīng)用程序的用戶界面。
游戲具體代碼設(shè)計(jì)與實(shí)現(xiàn)過(guò)程大致分為兩步:1)用Qt建立GUI界面的主框架;2)調(diào)用信號(hà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ì)功能
通過(guò)信號(hào)與槽機(jī)制和事件功能,借用QTimer定時(shí)刷新程序完成菜單欄、工具欄、圖標(biāo)、計(jì)分、各種按鈕及繪圖的功能。
1)創(chuàng)建菜單欄和工具欄
本游戲需要一個(gè)菜單欄和一個(gè)工具欄,菜單欄提供游戲的聲音、重新開(kāi)始、退出及幫助等按鈕;工具欄把菜單欄里的功能用圖標(biāo)表示出來(lái),方便用戶快速使用那些功能。菜單欄和工具欄使用了action(動(dòng)作)的概念。一個(gè)action就是一個(gè)可以添加到任意菜單欄和工具欄上的項(xiàng)。創(chuàng)建菜單欄和工具欄時(shí),按照如下步驟進(jìn)行:創(chuàng)建并設(shè)置action->創(chuàng)建菜單并把a(bǔ)ction添加到菜單上->創(chuàng)建工具欄并把a(bǔ)ction添加到工具欄上。每選擇一個(gè)菜單項(xiàng),都會(huì)觸發(fā)相應(yīng)的槽函數(shù)。在此,對(duì)于每一個(gè)action,信號(hào)與槽的關(guān)聯(lián)我們不用connect函數(shù)手動(dòng)關(guān)聯(lián),而是采用自動(dòng)關(guān)聯(lián)的方式,即,對(duì)于“重新開(kāi)始”菜單項(xiàng),我們?cè)趧?chuàng)建該菜單項(xiàng)的時(shí)候,就命名為action_R:
private slots:void on_action_R_triggered();
則當(dāng)選擇“重新開(kāi)始”菜單項(xiàng)的時(shí)候,此動(dòng)作觸發(fā)了槽函數(shù) restart()函數(shù):

restart()函數(shù)定義在私有類(lèi)中,該函數(shù)主要作用是使游戲重新開(kāi)始。
工具欄中的圖標(biāo)通過(guò)Qt Resource File添加到程序中。
2)鼠標(biāo)點(diǎn)擊按鈕事件
界面中,先調(diào)用paintEvent()函數(shù)畫(huà)出圖1中藍(lán)色方框里的81個(gè)位置;在每個(gè)位置上方分別布上一個(gè)和81個(gè)藍(lán)色方框一樣大小的pushButton按鈕,按鈕名稱(chēng)為pushButton00~80,如圖2所示。小球在按鈕下方的窗口上畫(huà)出。用ui->pushButton->setFlat(true)把按鈕設(shè)置成透明的,這樣在按鈕下方畫(huà)圖時(shí),按鈕就不會(huì)覆蓋圖形了。
游戲過(guò)程中,當(dāng)玩家用鼠標(biāo)點(diǎn)擊相應(yīng)的方塊,程序就會(huì)做出相應(yīng)的響應(yīng)事件,程序處理這些事件來(lái)完成圖形的繪制[6]。
MainWindow類(lèi)的定義如下:

下面以 void on_pushButton00_clicked()為例,簡(jiǎn)要說(shuō)明代碼如何為繪圖做準(zhǔn)備:
void Lines :on_pushButton00_clicked () //按鈕被鼠標(biāo)左鍵點(diǎn)擊

void Lines:on_pushButton00_clicked() 函數(shù)中,有很多變量隨著鼠標(biāo)的按下而改變。是否有5個(gè)或者以上相同顏色連在一起以及游戲得分可能也隨著鼠標(biāo)按下而改變。這些改變需要通過(guò)定時(shí)器定時(shí)去檢測(cè)。檢測(cè)到有變量的改變則需要重繪圖形,重繪圖形的指令update放在paint_show()函數(shù)中,系統(tǒng)通過(guò)定時(shí)器定時(shí)調(diào)用paint_show()函數(shù)[7]。
創(chuàng)建一個(gè) QTimer:m_timer=new QTimer (this);
使用start()來(lái)開(kāi)始并且把它的 timeout()連接到適當(dāng)?shù)牟?:connect (m_timer, SIGNAL (timeout ()),this, SLOT(paint_show())); m_timer->start(10);
槽函數(shù):

繪圖函數(shù)paintEvent()主要?jiǎng)討B(tài)繪制3部分內(nèi)容:
1)下一次出現(xiàn)的小球,根據(jù)m=qrand()%8得到的隨機(jī)數(shù)的值繪制小球的顏色,不同的數(shù)字代表小球不同的顏色;
2)被移動(dòng)后重新放置的小球和重新出現(xiàn)的小球,及5個(gè)或以上相同顏色小球相連時(shí)被移除的小球。這些小球是否被移除或者被繪制根據(jù)paint[i][j]的值,繪制什么顏色根據(jù)j[i][j]的值;
3)被點(diǎn)擊小球外面的紅框,是否有紅框框根據(jù)p_flag[i][j]的值。
關(guān)鍵代碼如下:


//以上繪畫(huà)重新放置和重新出現(xiàn)的小球,及5個(gè)或以上相同顏色小球相連時(shí)被移除的小球
}
本文著重分析了Qt的信號(hào)與槽機(jī)制和事件功能,并詳細(xì)描述了繪圖函數(shù)paintEvent()、添加音頻、按鈕QpushButton和定時(shí)器QTimer的用法。通過(guò)設(shè)計(jì)及實(shí)現(xiàn)單機(jī)小游戲Lines,來(lái)表現(xiàn)出Qt在構(gòu)建圖形界面、實(shí)現(xiàn)事件響應(yīng)、繪圖、播放聲音等方面的卓越特性。隨著越來(lái)越多的產(chǎn)品需要有完美的操作界面以滿足人機(jī)交互的需求,使用Qt來(lái)開(kāi)發(fā)圖形用戶界面程序?qū)?huì)變得越來(lái)越廣泛。
[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.