張 楓 張曉民
(南陽理工學(xué)院軟件學(xué)院,河南 南陽 473000)
wxWidgets事件處理機(jī)制
張 楓 張曉民
(南陽理工學(xué)院軟件學(xué)院,河南 南陽 473000)
wxWidgets是一個(gè)可以在多個(gè)平臺(tái)上進(jìn)行圖形用戶界面開發(fā)的C++語言框架庫。如同其它所有的GUI框架一樣,wxWidgets的程序控制采用事件驅(qū)動(dòng)的方式。wxWidgets主要有兩種方法來處理事件:一種通過事件表宏定義靜態(tài)地把事件和事件處理過程綁定在一起;另外一種通過函數(shù)動(dòng)態(tài)地綁定事件和事件處理過程。本文介紹了這兩種事件處理方法并對(duì)其作出分析比較,說明了動(dòng)態(tài)事件處理機(jī)制代碼量稍大但比靜態(tài)事件表機(jī)制更加靈活方便。
跨平臺(tái);事件驅(qū)動(dòng);綁定
wxWidgets是一個(gè)開源跨平臺(tái)(操作系統(tǒng))的C++框架(framework)庫,它可以提供豐富的圖形用戶界面(GUI:Graphical User Interface)應(yīng)用程序接口(API:Application Programming Interface)。用戶的程序開發(fā)工作在很大程度上是在進(jìn)行界面設(shè)計(jì),其工作量往往占整個(gè)開發(fā)工作的60%-70%[1],wx-Widgets通過隱藏平臺(tái)相關(guān)代碼,與不同系統(tǒng)及編譯器各自對(duì)應(yīng)的庫相連接,用wxWidgets開發(fā)的應(yīng)用程序?qū)?huì)呈現(xiàn)出最適合于那個(gè)平臺(tái)的外觀和感覺,增強(qiáng)程序的靈活性和可移植性,在很大程度上減少用戶在進(jìn)行界面設(shè)計(jì)時(shí)的開發(fā)工作。這種平臺(tái)無關(guān)性,即"Write Once Run Everywhere"的理念,使得wxWidgets在程序開發(fā)中具有實(shí)際的意義。你不需要重新學(xué)習(xí)某個(gè)特定平臺(tái)的API,而且,你的程序可能在將來很長(zhǎng)時(shí)間仍然不會(huì)過時(shí),因?yàn)殡S著計(jì)算機(jī)科技的演進(jìn),wx-Widgets也將會(huì)進(jìn)行相應(yīng)的演進(jìn),這樣你的程序?qū)?huì)很方便地移植到將來的最新的操作系統(tǒng)以支持最新的特性而不需要學(xué)習(xí)[2,3]。WxWidgets提供了對(duì)現(xiàn)今幾乎所有流行操作系統(tǒng)平臺(tái)和移動(dòng)開發(fā)平臺(tái)的支持,但不同于java的跨平臺(tái)特性。Java語言的跨平臺(tái)是建立在“中間代碼”的基礎(chǔ)上的,而wxWidgets被直接翻譯成機(jī)器碼,從而獲得速度上的優(yōu)勢(shì)[3]。在GUI編程方面,這意味著很大的不同。wxWidgets在世界范圍內(nèi)廣泛應(yīng)用于各個(gè)領(lǐng)域,用戶有個(gè)體軟件開發(fā)者、科研院校,以及AOL、AMD等大公司[4]。在我國(guó),wxWidgets的研究還很少,使用者也非常有限[5]。但隨著蘋果的崛起,Linux操作系統(tǒng)的大力推廣以及移動(dòng)設(shè)備種類的日漸豐富,免費(fèi)、開源、靈活易用的wxWidgets已經(jīng)成為程序員進(jìn)行跨平臺(tái)軟件開發(fā)的一個(gè)重要選擇。本文就wxWidgets事件處理的方法進(jìn)行了研究和探討。
事件是GUI程序的重要部分,所有的GUI程序都是事件驅(qū)動(dòng)的[2,6,7]。事件可以定義為程序發(fā)生了某些事情的信號(hào)。外部用戶行為,如移動(dòng)鼠標(biāo)、點(diǎn)擊鼠標(biāo)和按下鍵盤等,操作系統(tǒng)或程序行為,如時(shí)鐘,網(wǎng)絡(luò)等也可以引發(fā)事件。事件驅(qū)動(dòng)可以定義為:應(yīng)用程序開始運(yùn)行之后就設(shè)置一個(gè)循環(huán)等待用戶或其它的事件源產(chǎn)生事件,一旦接收到某個(gè)事件,就將它交給某個(gè)事件函數(shù)進(jìn)行處理[7]。雖然看上去不同的窗口是同時(shí)被刷新的,但實(shí)際上,絕大多數(shù)的GUI程序仍然是單線程的[2]。
事件機(jī)制是GUI程序開發(fā)的重中之重。wxWidgets事件處理系統(tǒng)比起通常的虛方法機(jī)制來說要稍微復(fù)雜一點(diǎn),但它的一個(gè)好處是可以避免需要實(shí)現(xiàn)基類中所有的虛方法,因?yàn)閷?shí)現(xiàn)所有的基類虛方法有時(shí)候是不切實(shí)際的或者是效率很低的[2]。wxWidgets的事件可以由三部分來描述:
(1)事件類型(Event Type):唯一標(biāo)示事件類型的整型值,在wxWidgets中的定義如下:typedef int wxEventType。當(dāng)應(yīng)用程序發(fā)生不同的事情會(huì)產(chǎn)生不同類型的事件。
(2)事件類(Event Class):wxWidgets封裝了諸如Windows里的message、GTK+的signal,將事件統(tǒng)一抽象成為事件類wxEvent,又從這個(gè)基類派生了很多的事件類。事件類攜帶了和事件相關(guān)的一些信息,從而使得不同的事件都有相應(yīng)的事件類與之對(duì)應(yīng)[2];不同的事件也可以使用相同的事件類。總之,不管事件是如何產(chǎn)生的,都可以以相同的方式來進(jìn)行處理。wxWidgets能處理的事件有按鈕事件、鍵盤事件和鼠標(biāo)事件等,它們都是wxEvent的子類。
(3)事件源(Event Source):wxEvent存儲(chǔ)了產(chǎn)生事件的對(duì)象和它的窗體標(biāo)示符(Window Identifier)。窗體標(biāo)示符是事件系統(tǒng)中唯一確定事件產(chǎn)生源(窗口)的整數(shù)。不同的窗體可能會(huì)產(chǎn)生相同類型的事件,通過窗體標(biāo)示符可以對(duì)它們加以區(qū)分。
wxWidgets有兩種主要的事件處理方法,一是靜態(tài)事件表機(jī)制,二是動(dòng)態(tài)事件處理方法。下文將介紹兩種方法并對(duì)其進(jìn)行比較分析。
wxWidgets對(duì)事件的處理是通過事件處理器(Event Handler)來完成的,每一個(gè)wxEvtHandler的派生類,例如窗體、按鈕、菜單等,都會(huì)在其內(nèi)部維護(hù)一個(gè)事件表,即事件哈希表(Event Hash Table),用來告訴wxWidgets事件和事件處理過程的映射關(guān)系。因此,要?jiǎng)?chuàng)建靜態(tài)的事件表,首先需要一個(gè)處理事件的類,而該類必須是wxEvtHandler的派生類。wxWidgets中有各種能接收消息的控件都是wxEventHandler的派生類,表明它們具備事件處理能力[1],這也是在我們的程序中自定義的窗體類或控件類需要繼承自wxFrame或者wxDialog類的原因。wxWidgets將相應(yīng)的函數(shù)指針保存在事件哈希表中,當(dāng)事件發(fā)生時(shí),從這個(gè)哈希表中,找到相應(yīng)的事件函數(shù)指針,然后通過事件函數(shù)指針調(diào)用函數(shù)[8]。在使用事件哈希表之前必須定義它,而每一個(gè)需要處理事件的類都需要相同的代碼,因此,wxWidgets用 一 個(gè) 宏 DECLARE_EVENT_TABLE()或 wxDECLARE_EVENT_TABLE()來定義哈希表這個(gè)復(fù)雜的過程。該宏定義可以放置在窗體類定義的任何位置。同樣,wxWidgets使用宏BEGIN_EVENT_TABLE和END_EVENT_TABLE或者wxBEGIN_EVENT_TABLE和wxEND_EVENT_TABLE實(shí)現(xiàn)一個(gè)事件表,其中BEGIN_EVENT_TABLE或者wxBEGIN_EVENT_TABLE將事件表進(jìn)行了初始化,事件關(guān)連宏根據(jù)程序員提供的窗口ID,以及事件處理函數(shù)最終生成事件表項(xiàng),并添加到事件表項(xiàng)數(shù)組中。加入事件的最后一步是聲明和實(shí)現(xiàn)事件處理函數(shù)。
如下幾個(gè)步驟和代碼展示了靜態(tài)事件表的使用方法:
定義一個(gè)類直接或間接繼承自wxEvtHandler的類。wx-Window窗體或控件子類、wxApp都是wxEvtHandler的直接或者間接派生類;
(1)在類中為每一個(gè)要處理的事件定義一個(gè)方法作為事件處理器。事件處理方法都具有相同的形式:它們都不是虛函數(shù),需要一個(gè)non-const wxEvent引用類型的參數(shù)。方法的返回值是void類型,因?yàn)橐妙愋偷膮?shù)將帶回事件的返回信息;
(2)在類的聲明中使用DECLARE_EVENT_TABLE()或wx DECLARE_EVENT_TABLE()聲明事件表;
(3)類的實(shí)現(xiàn)代碼中,使用宏BEGIN_EVENT_TABLE和END_EVENT_TABLE或者wxBEGIN_EVENT_TABLE和wxEND_EVENT_TABLE實(shí)現(xiàn)一個(gè)事件表;
(4)實(shí)現(xiàn)事件處理函數(shù),并在事件表中使用事件關(guān)聯(lián)宏實(shí)現(xiàn)從事件到事件函數(shù)的映射。
如下代碼展示了wxWidgets靜態(tài)事件表編程的核心部分。圖1顯示的是一個(gè)基于wxWidgets的跨平臺(tái)計(jì)算器在Win7下的運(yùn)行結(jié)果,它和動(dòng)態(tài)事件處理方法在Win7下的運(yùn)行效果一樣,展示了Win7本地風(fēng)格和觀感。程序運(yùn)行環(huán)境:Win7,使用wxWidgets3.0動(dòng)態(tài)鏈接庫,編譯選項(xiàng)UNICODE=
/*事件處理函數(shù),函數(shù)原型都一樣,返回值為void,一個(gè)wx-Event派生類對(duì)象的非常量(non-const)引用*/
void OnClose(wxCloseEvent&event);
void OnButton(wxCommandEvent&event);
void OnSize(wxSizeEvent&event);
void OnTimer(wxTimerEvent&event);
void OnChar(wxKeyEvent&event);
wxDECLARE_EVENT_TABLE();//此處也可用DECLARE_EVENT_TABLE()
};
//MyFrame.cxx
/*可以用BEGIN_EVENT_TABLE和END_EVENT_TABLE代替wxEND_EVENT_TABLE和 wxBEGIN_EVENT_TABLE*/
/*ID_MENU和ID_BUTTON分別對(duì)應(yīng)程序菜單項(xiàng)和按鈕的窗體標(biāo)示符*/
wxBEGIN_EVENT_TABLE(MyFrame,wxFrame)
EVT_BUTTON(ID_BUTTON,MyFrame::OnButton)
EVT_SIZE(MyFrame::OnSize)
EVT_CLOSE_WINDOW(MyFrame::OnClose)
EVT_TIMER(MyFrame::OnTimer)
EVT_CHAR(MyFrame::OnChar)
wxEND_EVENT_TABLE()
圖1 Win7下程序運(yùn)行結(jié)果
盡管靜態(tài)事件表機(jī)制比較簡(jiǎn)單,但并不意味著這是一種比較好的選擇,因?yàn)殪o態(tài)事件表的映射通過宏定義在編譯期間進(jìn)行的,這就意味著我們沒有辦法改變這種映射關(guān)系[6]。而程序在運(yùn)行期的不同階段又確切需要不同的映射關(guān)系,或者需要在不同的類之間共享事件處理函數(shù),比如我們的程序需要先進(jìn)行初始化,只有在初始化成功之后才進(jìn)行事件和事件處理函數(shù)的映射;或者我們需要在不同的類之間共享事件處理函數(shù)。這時(shí),使用wxWidgets強(qiáng)大的動(dòng)態(tài)事件處理機(jī)制,可以更加精確地處理事件表細(xì)節(jié)。
和動(dòng)態(tài)事件處理相關(guān)的API函數(shù)有兩個(gè):wxEvtHandler::Connect()和wxEvtHandler::DisConnect()。依據(jù)事件的處理策略,這兩個(gè)函數(shù)又有三種重載形式。wxEvtHandle::Connect()函數(shù)只能夠映射事件到wxEvtHandler派生類的方法。在wxWidgets2.9.0之后,wxWidgets提供了一組更加靈活的動(dòng)態(tài)事件處理函數(shù)wxEvtHandler::Bind()和wx-EvtHandler::UnBind(),它們?cè)诠δ苌贤瑆xEvtHandler::Connect()和wxEvtHandler::DisConnect()是相同的,但可以用任意類的方法、普通函數(shù)或?qū)S泄δ芙M件作為事件處理函數(shù)[6]。在大多數(shù)情況下,無需手動(dòng)調(diào)用UnBind()或DisConnect(),因?yàn)榇翱陬惐会尫诺臅r(shí)候,它們將自動(dòng)被調(diào)用[2]。以下代碼展示了動(dòng)態(tài)事件的綁定過程。圖2展示了程序在Ubuntu12.04下的運(yùn)行結(jié)果,它和靜態(tài)事件表方法在Ubuntu12.04下的運(yùn)行效果一樣,展示的是Ubuntu本地風(fēng)格和觀感。程序運(yùn)行環(huán)境:Ubuntu12.04,使用wxWidgets3.0類庫。
};
//MyFrame.cxx
//沒有事件宏定義
圖2 Ubuntu12.04下程序運(yùn)行結(jié)果
wxWidgets的事件處理方法和C++中的虛函數(shù)多態(tài)比較相似:允許在事件處理器(wxEvtHandler)的派生類中重新定義函數(shù)來改變基類的行為[6]。而這兩種機(jī)制最大的不同在于派生類調(diào)用基類方法的方式:派生類的虛函數(shù)可以直接調(diào)用基類的方法,所以可以在派生類事件處理函數(shù)的開始先調(diào)用基類方法再處理事件(post-process the event),也可以在派生類事件處理函數(shù)中先處理事件(pre-process the event)再在結(jié)束時(shí)調(diào)用基類方法;實(shí)際上,默認(rèn)的事件處理行為可能由底層操作系統(tǒng)的平臺(tái)相關(guān)代碼來實(shí)現(xiàn),并不屬于wxWidgets的API,因此wxWidgets不允許直接調(diào)用基類默認(rèn)事件處理函數(shù),只能夠采用pre-process the event方法處理事件,要想調(diào)用基類默認(rèn)行為的話需要使用wxEvent::Skip()函數(shù)。
wxWidgets可以使用靜態(tài)事件表機(jī)制和動(dòng)態(tài)事件處理方法把事件和事件處理函數(shù)綁定在一塊,并產(chǎn)生同操作系統(tǒng)平臺(tái)一致的本地觀感。靜態(tài)事件表通過宏定義把所有的事件映射書寫在同一個(gè)地方,簡(jiǎn)單明了,但不能夠在運(yùn)行期改變事件的映射關(guān)系,處理事件的類只能夠是wxEvtHandler的派生類,適合在簡(jiǎn)單的應(yīng)用程序中使用。動(dòng)態(tài)事件的處理方法則根據(jù)需要以更加精確地事處理事件表的細(xì)節(jié),顯得更加靈活。表1對(duì)靜態(tài)事件表和動(dòng)態(tài)事件表在各個(gè)方面做出了一系列的比較。
表1 靜態(tài)事件表和動(dòng)態(tài)事件綜合比較
總之,wxWidgets封裝了底層平臺(tái)的差異,采用了更加靈活的事件處理機(jī)制,既可以使用靜態(tài)事件表代替虛函數(shù),也可以動(dòng)態(tài)地進(jìn)行事件處理,獲得較高的效率和良好的可移植性。
[1]熊凱,高茂庭,于仁師.C++語言開發(fā)跨平臺(tái)程序的研究與實(shí)現(xiàn)[J].電腦知識(shí)與技術(shù),2006(2):127-128.
[2]Kevin Hock,Stefan Csomor,Julian Smart.Cross-Platform GUI Programming with WxWidgets[M].UK:Prentice Hall PTR,2005.7.
[3]王艷紅,張憲生.基于wxWidgets的跨平臺(tái)軟件開發(fā)[J].中國(guó)科技博覽,2012(26):388.
[4]張長(zhǎng)亮.wxWidgets跨平臺(tái)程序開發(fā)[M].北京:機(jī)械工業(yè)出版社,2012.9.
[5]李森林,鄧小武.一種跨平臺(tái)類庫WxWidgets應(yīng)用開發(fā)研究[J].電腦與電信,2009(3):32-34. [6]Julian Smart,Robert Roebling,et al.wxWidgets Cross-Platform GUI Toolkit[M/OL].https://sourceforge.net/projects/wxwindows/files/ 3.0.0/wxWidgets-docs-chm-3.0.0.zip 2013.11.
[7]李寧.wxWidgets:全能的跨平臺(tái)軟件開發(fā)包[J].程序員,2006 (11):126-127.
[8]張羽.WxWidgets框架應(yīng)用及其模塊化開發(fā)[J].硅谷,2009(22):67.
The Mechanism of Handling Events in wxWidgets
Zhang Feng Zhang Xiaomin
(School of Software,Nanyang Institute of Technology,Nanyang 473000,Henan)
wxWidgets is a C++framework providing GUI(Graphical User Interface)and other facilities on multi-platforms. Like other GUI frameworks,the control of flow in wxWidgets applications is event-based.There are two principal ways to handle events in wxWidgets.One of them uses event table macros and allows you to define the binding between events and their handlers only statically;the other way maps events and their handlers dynamically.The paper introduces and analyzes the two methods and summarizes that the dynamical way requires slightly more codes but is much more flexible than the static way.
cross-platform;event-based;binding
張楓,男,河南南陽人,碩士研究生,講師,研究方向:跨平臺(tái)軟件開發(fā),數(shù)字圖像處理。