級別: 中級
Noel Rappin (noelrappin@gmail.com), 高級軟件工程師, Motorola, Inc.
2006 年 10 月 26 日
wxWidgets 工具包提供圖形用戶界面(GUI)開發(fā)所需的一些功能強大的跨平臺工具。除了原生的 C++, 還有其他幾種語言提供了使用該工具包所需的包裝程序。本文將介紹如何使用 wxWidgets 工具包來在所選用的編程語言中創(chuàng)建用途廣泛的 GUI。
為什么要使用 wxWidgets 呢?原因很簡單,它讓您可以快速輕松地編寫能夠跨平臺運行的 GUI,能讓您隨意選用編程語言,還能讓您的 GUI 如下圖所示一樣優(yōu)秀:
圖 1 顯示了 Chandler,一個在開放源碼應用程序基礎上開發(fā)的日歷和電子郵件管理程序。它是使用 wxWidgets 工具包編寫的。雖然原始版本的 wxWidgets 是在 C++ 中實現(xiàn)的,Chandler 的創(chuàng)建者們卻使用了 wxPython 工具包及 Python 作為包裝程序來與 C++ 庫進行無縫的交互。wxWidgets 工具包會盡可能地利用原生對象,這些對象通過在需要的地方使用強大的定制窗口小部件得以擴充。您可以編寫能夠運行在多種平臺的 wxWidgets 程序,并且可以使用多種編程語言來實現(xiàn)。
開始之前,我假定您已經(jīng)到 wxWidgets 主頁下載了所用平臺的相關軟件包。如果還沒有,請參見 參考資料 部分的鏈接并下載它。我還進一步假定您已經(jīng)掌握了將 wxWidgets 庫與編譯器或所選的集成開發(fā)環(huán)境(IDE)進行集成所需的命令和設置。如果還不是很明白,本文后面 參考資料 部分中有指向所需信息的相關鏈接。完成上述工作后,您就可以著重進行程序代碼編寫了。
wxWidgets 程序的主體包括兩種主要的對象:應用程序?qū)ο蠛涂蚣軐ο?。可以有多個框架,而且在代碼中還可能需要一些特定于 wxWidgets 的宏。以下解釋了它們是如何組合在一起的。
要鏈接到 wxWidgets 庫,必須先包括它。在頭文件的頂部放上如下一行代碼:
#include "wx/wx.h" |
wx/wx.h 頭文件包括所有可能需要的 wxWidgets 定義。如果十分關注于性能,則可以用一些針對將要用到的特定頭文件的 include
語句來替代該文件。
接下來,必須定義應用程序類。在很多簡單的情況下,該類的作用不大,但是您必須有一個自己的類。wxWidgets 應用程序繼承自 wxApp
類,其定義很簡單:
class DemoApp : public wxApp { public: virtual bool OnInit(); } |
應用程序開始時,調(diào)用 OnInit()
函數(shù) —— 實際上是 main()
方法。
定義了應用程序類之后,在代碼中放上如下的宏:
IMPLEMENT_APP(DemoApp) |
您可以用自己的應用程序類的名稱來替代 DemoApp
。這個宏創(chuàng)建 wxWidgets 所使用的真正的 main()
方法。另外,它還創(chuàng)建應用程序?qū)ο蟮囊粋€實例并開始初始化過程。
現(xiàn)在開始定義框架類,它代表應用程序中的主窗口。xWidgets 父類是 wxFrame
。清單 1 給出了一個簡單的示例。
class DemoFrame : public wxFrame { public: DemoFrame(const wxString& title); void OnButtonPress(wxCommandEvent& event); private: DECLARE_EVENT_TABLE() }; |
為了去除不太熟悉的名稱,wxString
是一種特定于 wxWidgets 的字符串包裝程序類,它在整個 wxWidgets 工具包中用于字符串操作。工具包拒絕標準模板庫 (STL) 類的使用以避免將 wxWidgets 限制在 STL 可用的平臺。如果需要,可以采用編譯時間開關將 STL 用作底層實現(xiàn)。類似地,wxCommandEvent
是事件的一種父類 —— 具體來說是命令事件,是通常與用戶操作(如單擊按鈕或從列表中選擇)相關的高級事件。而 DECLARE_EVENT_TABLE
宏是任何需要對事件做出響應的 wxWidgets 對象(它無疑包括本文中的小演示框架)所必需的。
要真正響應事件,必須在實現(xiàn)文件內(nèi)定義事件表。它是另一種宏,在本文的示例中它如清單 2 所示。
BEGIN_EVENT_TABLE(DemoFrame, wxFrame) EVT_BUTTON(wxID_CLOSE, DemoFrame::OnButtonPress) END_EVENT_TABLE() |
BEGIN_EVENT_TABLE()
宏有兩個參數(shù):事件表實際針對的類和此類的中間父類。如您所預期的一樣,END_EVENT_TABLE
宏指出事件表的末尾。中間可以包含若干個特定的事件宏,本例中只有一個。
wxWidgets 工具包包含幾個不同的事件宏,每個對應于不同的事件。在本例中,EVT_BUTTON
代表按鈕單擊。這個宏的第一個參數(shù)是指代正在處理的特定按鈕的標識符。wxID_CLOSE
標識符是與應用程序的一些公共特性相關的幾個預定義的標識符之一。這里使用預定義的標識符完全是為了方便,雖然在有些情況下,某些特定的標識符會觸發(fā) wxWidgets 系統(tǒng)的特別處理。第二個參數(shù)是在觸發(fā)菜單事件時調(diào)用的方法的全限定名。您可以用類似于此處所描述的事件宏來管理 wxWidgets 中的所有事件。
現(xiàn)在,可以開始定義一些方法了。我這里給出了三個簡單的方法,第一個是清單 3 中所示的 OnInit()
方法。
bool DemoApp::OnInit() { DemoFrame *frame = new DemoFrame("DeveloperWorks Demo"); frame->Show(true); return true; } |
該方法的前兩行的用途在 GUI 程序的開頭部分很常見:它們創(chuàng)建和顯示主窗口。第三行對應用程序的其余部分非常重要。如果返回 true
,則向 wxWidgets 引擎的其余部分發(fā)出一個信號表明初始化已經(jīng)成功完成,程序可以繼續(xù)。相反,如果返回 false
,則應用程序會停止并退出。
OnInit()
方法為 DemoFrame
引用構(gòu)造函數(shù)。在該方法,可以向框架添加按鈕,如清單 4 所示。
DemoFrame::DemoFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title) { wxSizer *sizer = new wxBoxSizer(wxVERTICAL); this->SetSizer(sizer); wxButton *button = new wxButton(this, wxID_CLOSE, "Click Me"); sizer->Add(50, 50); sizer->Add(button, 0, wxALL, 50); } |
但是在可以向框架添加按鈕之前,需要先創(chuàng)建 sizer。wxWidgets 中的 Sizer 相當于 Java 編程語言中的布局管理器:它們允許您使用預定義的規(guī)則在窗口中放置對象,而不需要單獨為每個窗口小部件設置大小和位置。本例中使用了 wxBoxSizer
,它將窗口小部件布局在一條直線上。一個方框 sizer 可以是垂直的也可以是水平的。創(chuàng)建 sizer 之后,使用 SetSizer()
方法將它附加到了框架。然后創(chuàng)建按鈕。按鈕構(gòu)造函數(shù)的參數(shù)有:
不必顯式地將按鈕添加到框架,只要將框架標識為父容器就可以了。但必須將按鈕顯式地添加到 sizer,以便 sizer 的布局算法知道它。在方法的最后一行實現(xiàn)這一點,但必須是在行的頂部添加了 50 x 50 像素的空白之后。當添加按鈕時,sizer 還會用 50 像素的邊框圍繞按鈕。這可以通過使用 wxALL
標志和最后一個參數(shù) 50
來實現(xiàn)。
最后,需要定義簡單的事件處理程序,如清單 5 所示。
void DemoFrame::OnButtonPress(wxCommandEvent& event) { Close(true); } |
這再簡單不過了。編譯后會得到如圖 2 所示的包含有一個按鈕的窗口。單擊按鈕,窗口會關閉。在 wxWidgets,關閉最后一個父框架會自動退出應用程序,所以單擊此按鈕也會導致完全退出應用程序。
在本例中還剛剛接觸到用 wxWidgets 所能實現(xiàn)的功能的皮毛。進一步的探索請參閱 參考資料 部分的指南。
![]() ![]() |
![]()
|
wxWidgets 確實是一種功能強大的工具包,但并不是所有人都愿意使用 C++ 析構(gòu)函數(shù)、內(nèi)存管理等等。所幸的是一組優(yōu)秀的程序員已經(jīng)創(chuàng)建了到 wxWidgets 庫的、可從其他編程語言使用的包裝程序綁定。所以,即使所選擇使用的編程工具不是 C++ ,仍然可以從 wxWidgets 庫獲益。
發(fā)展得最為成熟和全面的 wxWidgets 綁定是 wxPython, 通過它可以使用 Python 編程語言創(chuàng)建 wxWidgets 程序。有針對 Microsoft? Windows?、Mac 和 Linux? 平臺的下載,其用戶社區(qū)規(guī)模也很大并十分活躍。想了解一下它?清單 6 中給出的 Python 程序可以創(chuàng)建與前面在 C++ 中創(chuàng)建的一樣的空白窗口。
#!/usr/bin/env python import wx class DemoApp(wx.App): def OnInit(self): frame = DemoFrame(parent=None, id=-1, title=‘DeveloperWorks‘) frame.Show() return True class DemoFrame(wx.Frame): def __init__(self, parent, id, title): wx.Frame.__init__(self, parent, id, title) sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) button = wx.Button(self, wx.ID_CLOSE, "Click Me") sizer.Add((50, 50)) sizer.Add(button, 0, wx.ALL, 50) self.Bind(wx.EVT_BUTTON, self.OnButtonClick) def OnButtonClick(self, event): self.Close(True) if __name__ == "__main__": app = DemoApp() app.MainLoop() |
如您所見,在這樣一個短小的程序中,在 C++ API 調(diào)用和 wxPython 調(diào)用之間幾乎存在一對一的對應。在兩種情況下,都創(chuàng)建了一個應用程序?qū)ο蠛鸵粋€框架對象。二者都以 OnInit()
方法開頭,并且定義了相似的構(gòu)造函數(shù)和對象處理程序。
本例中最大的不同是事件到處理程序的綁定。C++ 版本使用事件表宏管理該綁定,而 Python 版本則使用 Bind()
方法來管理,這個方法將代表事件類型的 Python 對象和在事件被調(diào)用時實際調(diào)用的對象作為參數(shù)。這種結(jié)構(gòu)利用了 Python 可以將方法視為變量并可以將它們作為參數(shù)傳遞(傳遞的方式與傳遞字符串或整數(shù)相同)的優(yōu)勢。
wxPython 較 C++ wxWidgets 工具包的優(yōu)勢在更長或更復雜一些的程序中會更明顯。即使不將 C++ 和 Python 作為兩種編程語言進行專門的對比,wxPython 工具包的一些吸引人的優(yōu)秀特性仍然非常具有吸引力。使用 Bind()
方法的事件處理機制融合進 wxPython 中比融合進 wxWidgets 中更容易。在 Python 版本中更容易在運行時動態(tài)地更新處理程序。一些復雜或復合的窗口小部件,比如樹狀列表控件或圖像單選按鈕,是 wxPython 工具包中的標準組件,但在 C++ 版中不是的。而且,wxPython 包含開發(fā)工具的 Py 程序包,它使得向 wxPython 程序添加交互式調(diào)試變得很簡單。
![]() ![]() |
![]()
|
Python 并不是惟一一種具有訪問 wxWidgets 庫的綁定的編程語言。雖然 wxPython 是其中最為成熟的一種,如果更愿意使用某種特定的編程語言,很值得了解一下其余的幾個。既然這樣,就讓我們在 wxWorld 中的幾個地方稍作停留吧。請注意這些項目的可靠性和健壯性的評估基于可用資料。在這些項目中,很多都源自一兩個專家程序員對其的熱愛。如果對某個項目感興趣,請務必自己查看一下。
wxPerl 綁定是在 2006 年 6 月正式發(fā)布的。它已重新開始了日??煺盏慕桓?,但可用的文檔卻是幾年前編制的?;顒余]件列表的范圍一般都是一天兩個或三個消息。有針對 Win32、Linux 和 Mac OS X 的二進制下載。除了主要的工具包,還有一些附加工具包可用,包括 OpenGL 包裝程序和用于創(chuàng)建 Mac OS X 應用程序的程序包。
wxPerl 的主要問題在于如何將 wxWidgets API 翻譯成 Perl 中的面向?qū)ο缶幊蹋∣OP)的有些異質(zhì)的變體。清單 7 中所示的代碼片段與前面所給出的框架示例有些相似。
package MyFrame; use base ‘Wx::Frame‘; use Wx::Event qw(EVT_BUTTON); sub new { my $class = shift; my $self = $class->SUPER::new(undef, -1, ‘Trying wxPerl‘, [-1, -1], [250, 200]); my $sizer = Wx::BoxSizer->new(wxVERTICAL); my $button = Wx::Button->new($self, -1, ‘Click me!‘, [-1, -1], [-1, -1]); EVT_BUTTON($self, $button, \&OnButtonClick); $sizer->Add($button); $self->SetSizer($sizer); return $self; } sub OnButtonClick { my($self, $event) = @_; $self->SetTitle(‘You Did It‘); } |
這段代碼基本上是前面已經(jīng)見過的 C++ 和 Python 代碼的逐行翻譯。在本例中,wxWidgets 庫是 Perl 程序包的形式,而且使用了 EVT_BUTTON
函數(shù)調(diào)用,這看起來很像 C++ 版本中的宏定義。
wxRuby 項目現(xiàn)在的情況比較復雜。在最早的版本中,到 wxWidgets API 的綁定是手工創(chuàng)建的。該工具最近的版本于 2004 年 11 月發(fā)布,但從那時開始,對新的版本的開發(fā)就時斷時續(xù),新版本利用更強大的 Simplified Wrapper and Interface Generator(SWIG)工具包來生成 Ruby 和 wxWidgets 之間的綁定。在其郵件列表中對新版本發(fā)布時間的陳述常常是 “很快但也可能要幾個月以后”。
wxRuby 另一個有趣的方面是,與大多數(shù)其他的 wxWidgets 綁定不同,開發(fā)人員大都選擇對 wxWidgets API 調(diào)用的名稱進行調(diào)整以更符合 Ruby 命名約定(具體來說,要采用 lower_case_with_underscores 而非 wxWidgets 的 UpperCaseWithCamelCase)。所以,上述所有代碼示例使用 SetSizer()
函數(shù),在 wxRuby 中會稱為 set_sizer()
。 除此之外,涉及到 wxWidgets API 的大部分 wxRuby 程序都將與前面已經(jīng)給出的示例相似。
其他 wxWidgets 端口完成與否的程度各不相同。以下是關于其余 wxWidgets 的概括:
![]() ![]() |
![]()
|
wxWidgets 可以為各類程序員提供大量的可用功能。其基本工具包非常靈活,能夠處理您的大多數(shù) GUI 需求。多種語言綁定讓大多數(shù)程序員都能得心應手地使用 wxWidgets。充分了解所選擇的語言中的 wxWidgets 工具包將有助于您在自己的應用程序中構(gòu)建優(yōu)秀的界面。
![]() | ||
| ![]() | Noel Rappin 從 Georgia Institute of Technology 的 Graphics, Visualization and Usability Center 獲得了博士學位,現(xiàn)在是 Motorola, Inc. 的一名高級軟件工程師。他還是 wxPython in Action(Manning Publications,2006 年 3 月)一書和 Jython Essentials(O‘Reilly,2002 年 5 月)一書的合著者之一。 |