wxWidgets編程入門


0,完整實例

#include "wx/wx.h"

// 定義應用程序類

class MyApp : public wxApp
{
public:    // 這個函數將會在程序啟動的時候被調用    

virtual bool OnInit();

};

// 定義主窗口類

class MyFrame : public wxFrame
{
public:    // 主窗口類的構造函數    
MyFrame(const wxString& title);    // 事件處理函數    
void OnQuit(wxCommandEvent& event); 
void OnAbout(wxCommandEvent& event);
private:    // 聲明事件表    
DECLARE_EVENT_TABLE()
};
// 有了這一行就可以使用  MyApp& wxGetApp()了

DECLARE_APP(MyApp)

// 告訴wxWidgets主應用程序是哪個類

IMPLEMENT_APP(MyApp)

// 初始化程序

bool MyApp::OnInit()

{    

// 創建主窗口    

MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));   

 // 顯示主窗口  

  frame->Show(true);  

  // 開始事件處理循環   

return true;

}

// MyFrame類的事件表

BEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
END_EVENT_TABLE()
void MyFrame::OnAbout(wxCommandEvent& event)
{    
wxString msg;   
msg.Printf(wxT("Hello and welcome to %s"),   wxVERSION_STRING);  

  wxMessageBox(msg, wxT("About Minimal"),  wxOK | wxICON_INFORMATION, this);

}
void MyFrame::OnQuit(wxCommandEvent& event)

{   

 // 釋放主窗口

    Close();

}

#include "mondrian.xpm"
MyFrame::MyFrame(const wxString& title)       : wxFrame(NULL, wxID_ANY, title)

{  

  // 設置窗口圖標 

   SetIcon(wxIcon(mondrian_xpm)); 

   // 創建菜單條   

wxMenu *fileMenu = new wxMenu; 

   // 添加“關於”菜單項   

wxMenu *helpMenu = new wxMenu; 
helpMenu->Append(wxID_ABOUT, wxT("&About.../tF1"),  wxT("Show about dialog"));    
fileMenu->Append(wxID_EXIT, wxT("E&xit/tAlt-X"),   wxT("Quit this program")); 

   // 將菜單項添加到菜單條中  

  wxMenuBar *menuBar = new wxMenuBar();  
  menuBar->Append(fileMenu, wxT("&File")); 

   menuBar->Append(helpMenu, wxT("&Help")); 

   // ...然后將菜單條放置在主窗口上

    SetMenuBar(menuBar);

    // 創建一個狀態條來讓一切更有趣些。  

  CreateStatusBar(2);  

  SetStatusText(wxT("Welcome to wxWidgets!"));

}  

這個小例子創建了一個主窗口(是一個wxFrame類的實例),這個主窗口有一個菜單條和一個狀態條。菜單條上的菜單按照你的命令顯示一個關於窗口或者退出這個小程序。

一,應用程序類
每一個wxWidgets程序都需要定義一個wxApp類的子類,並且需要並且只能構造一個這個類的實例,這個實例控制着整個程序的執行。你的這個繼承自 wxApp的子類至少需要定義一個OnInit函數,當wxWidgets准備好運行你寫的代碼的時候,它將會調用這個函數(和一個典型的Win32程序中的main函數或者WinMain函數類似)。你定義這個子類的代碼可能和下面的代碼類似: 

class MyApp : public wxApp

{  

public:      

virtual bool OnInit();

};

在這個OnInit函數中,你通常應該創建至少一個窗口,對傳入的命令行參數進行解析,為應用程序進行數據設置和其它的一些初始化的操作.如果這個函數返回真,wxWidgets將開始事件循環用來處理用戶輸入並且在必要的情況下處理這些輸入。如果OnInit函數返回假, wxWidgets將會釋放它內部已經分配的資源,然后結束整個程序的運行。接下來我們看一個最簡單的OnInit函數的實現: 

bool MyApp::OnInit()

  MyFrame *frame = new MyFrame(wxT("Minimal wxWidgets App"));  

 frame->Show(true);  

 return true;

}

你可能還會注意到上面例子中的wxT這個宏,在接下來的例子中,這個宏還會被頻繁用到。它的作用是讓你的代碼兼容Unicode模式。這個宏和另外一個_T宏的作用是完全一樣的。使用這個宏也不會帶來運行期的性能損失。(你可能還會遇到另外一個類似的"_()"標記,這個標記是用來告訴 wxWidgets將其中的字符串翻譯成其它語言的版本.
那么創建MyApp的實例的代碼在哪里呢?實際上,這是在wxWidgets內部實現的,不過你仍然需要告訴wxWidgets需要創建哪一個App類的實例,所以你還需要增加下面的一個宏: 
IMPLEMENT_APP(MyApp)
如果沒有實現這個類,wxWidgets就不知道怎樣創建一個新的應用程序對象。這個宏除了上述的功能以外,還會檢查編譯應用程序使用的庫文件是否和當前的庫文件的版本相匹配,如果沒有這種檢查,由此而產生的一些運行期的錯誤可能很難被查出原因。當wxWidgets創建這個MyApp類的實例的時候,會將創建的結果賦值給一個全局變量wxTheApp.你當然可以在你的程序中使用這個變量,但是你可能不得不一遍又一遍的進行從wxApp到MyApp的類型強制轉換。增加下面的這一行聲明以后,你就可以調用wxGetApp()函數,這個函數會返回一個到這個MyApp實例的引用,這樣用起來就方便多了。 
DECLARE_APP(MyApp)

一點提示: 

即使沒有聲明DECLARE_APP,你仍然可以不用進行類型強制轉化就直接對wxTheApp變量調用wxApp的方法.這可以避免在所有的頭文件中包含MyApp的頭文件,對於那些庫文件而不是應用程序的代碼來說也更有意義,而且還可以縮短編譯的時間。


二,Frame窗口類

我們來看一看自定義的Frame窗口類MyFrame.一個Frame窗口是一個可以容納別的窗口的頂級窗口,通常擁有一個標題欄和一個菜單欄。下面是我們的例子中這個類的定義,可以將其放在MyApp的定義之后:

class MyFrame : public wxFrame

{

public: 

   MyFrame(const wxString& title); 

   void OnQuit(wxCommandEvent& event);

    void OnAbout(wxCommandEvent& event);

private:  

  DECLARE_EVENT_TABLE()

};

這個窗口類的定義有一個構造函數,兩個用來把菜單命令和C++代碼相連的事件處理函數,還有一個宏來告訴wxWidgets這個類想要自己處理某些事件。


Frame窗口的構造函數
最后,讓我們來看看Frame窗口的構造函數,正是它實現了frame窗口的圖標,菜單條和狀態條。 

#include "mondrian.xpm"MyFrame::MyFrame(const wxString& title)       : wxFrame(NULL, wxID_ANY, title)

   SetIcon(wxIcon(mondrian_xpm));   

 wxMenu *fileMenu = new wxMenu;    

wxMenu *helpMenu = new wxMenu;    

helpMenu->Append(wxID_ABOUT, wxT("&About.../tF1"),   wxT("Show about dialog"));    

fileMenu->Append(wxID_EXIT, wxT("E&xit/tAlt-X"),   wxT("Quit this program"));   

 wxMenuBar *menuBar = new wxMenuBar();   

 menuBar->Append(fileMenu, wxT("&File"));    

menuBar->Append(helpMenu, wxT("&Help"));    

SetMenuBar(menuBar);   

 CreateStatusBar(2);    

SetStatusText(wxT("Welcome to wxWidgets!"));

}

這個構造函數首先調用它的基類(wxFrame)的構造函數,使用的參數是父窗口(還沒有父窗口,所以用NULL),窗口標識(wxID_ANY標識讓 wxWidgets自己選擇一個)和標題。這個基類的構造函數才真正創建了一個窗口的實例。除了這樣的調用方法,還有另外一種方法是直接在構造函數里面顯式調用基類默認的構造函數,然后調用wxFrame::Create函數來創建一個frame窗口的實例。

小圖片或者是圖標在所有的平台上都可以用XPM格式來表示。XPM文件其實是一個ASCII編碼的完全符合C++語法的文本文件,所以可以直接用C++的方式包含到代碼中(譯者注:顯然這樣的包含方式在分發軟件的時候是不需要分發這個圖片文件的)。SetIcon那一行代碼使用 mondrian_xpm變量在堆棧上創建了一個圖標(這個mondrian變量是在mondrian.xpm文件里定義的)。然后將這個圖標和 frame窗口關聯在一起。

接下來創建了菜單條。增加菜單項的Append函數的三個參數的意義分別為:菜單項標識,菜單上的文本以及一個稍微長一些的幫助字符串。這個幫助字符串會自動在菜單項被高亮顯示的時候自動顯示在狀態欄上。菜單上的文本中由"&"符號前導的字符將成為菜單的快捷操作符,在實際的顯示中用下划線表示。而"/t"符號則前導一個全局的快捷鍵,這個快捷鍵甚至可以在菜單項沒有顯示的時候觸發菜單功能。

這個構造函數所做的最后一件事是創建一個由兩個區域組成的狀態條並且在狀態條的第一個區域寫上歡迎的字樣。


3,事件處理函數
你也許已經注意到了,事件處理函數在MyFrame類中不是虛函數。如果不是虛函數,他們是怎樣被調用的呢?答案就在下面的事件表里: 

BEGIN_EVENT_TABLE(MyFrame, wxFrame)   

 EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)    

EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)

END_EVENT_TABLE()

所謂事件表,是一組位於類的實現文件(.cpp文件)中的宏,用來告訴wxWidgets來自用戶或者其它地方的事件應該怎樣和類的成員函數對應起來。

前面展示的事件表表明,要把鼠標點擊標識分別為wxID_EXIT和wxID_ABOUT的菜單項的事件和MyFrame的成員函數OnAbout和 OnQuit關聯起來。這里的EVT_MENU宏只是很多中事件宏的其中之一,事件宏的作用是告訴wxWidgets哪種事件應該被關聯到哪個成員函數。這里的兩個標識wxID_ABOUT和wxID_EXIT是wxWidgets預定義的宏,通常你應該通過枚舉,常量或者預編譯宏的方式定義你自己的標識。

用上面的方法定義的時間表是一種靜態的事件表,它不可以在運行期改變,在下一章里,我們將描述怎樣定義可以在運行期改變的動態事件表。現在,讓我們來看看這兩個事件處理函數. 

void MyFrame::OnAbout(wxCommandEvent& event)

{   

 wxString msg;    

msg.Printf(wxT("Hello and welcome to %s"),               

wxVERSION_STRING);   

 wxMessageBox(msg, wxT("About Minimal"),   wxOK | wxICON_INFORMATION, this);

}


void MyFrame::OnQuit(wxCommandEvent& event)

{    

Close();

}

當用戶點擊關於菜單項的時候,MyFrame::OnAbout函數彈出一個消息框。這用到了wxWidgets提供的API wxMessageBox,它的四個參數分別代表消息內容,標題,窗口類型以及父窗口。當用戶點擊退出菜單項的時候,MyFrame::OnQuit函數被調用(你已經意識到了,這是事件表的功勞)。它調用wxFrame類的Close函數來釋放frame窗口。因為沒有別的窗口存在了,這觸發了應用程序的退出,實際上,wxFrame類的Close函數並不直接關閉 frame窗口,而是產生一個wxEVT_CLOSE_WINDOW事件,這個事件默認的處理函數調用wxWindow::Destroy函數釋放了 frame窗口。

用戶還可以通過別的方法關掉應用程序,比如通過點擊標題欄上的關閉按鈕或者是通過系統菜單中的關閉菜單,在這種情況下,OnQuit函數是怎樣被調用的呢?事實上,在這種情況,OnQuit函數並沒有被調用。這時,wxWidgets會通過Close函數(象OnQuit中的那樣),給 frame窗口發送一個wxEVT_CLOSE_WINDOW事件,這個事件默認的處理函數會釋放掉frame窗口。在你的應用程序中,可以通過重載這個處理函數來增加改變這種默認的行為,比如:如果你想問一問你的用戶是不是真的要關閉窗口。關於這種處理的細節,可以參見第4章,"窗口基礎"。

另外,大多數的應用程序類還應該重載一個OnExit函數,以便在任何時候程序退出時,執行一下清理和資源回收的動作。需要注意的是,這個函數只有在OnInit函數返回真的時候才會被執行。當然,我們這個小例子程序就用不着定義這個函數了。


4,事件驅動編程

當程序員們首次面對蘋果公司的第一個圖形界面個人電腦的時候,他們為這種和以前所有的經驗都不同的電腦操作方法感到驚奇。鼠標指針在一個個的窗口之間移來移去,滾動條,菜單,文本編輯框等等等等,真的很難以想象,這么多讓人眼花繚亂的東西,其背后的代碼該是多么復雜和不可思議。似乎所有這一切都是以完全並行的方式運行的,雖然這只是一個假象。對於很多人來說,蘋果個人電腦是他們對事件驅動編程的第一印象。

所有的GUI程序都是事件驅動的。換句話說,應用程序一直停留在一個循環中,等待着來自用戶或者其他地方(比如窗口刷新或網絡連接)的事件,一旦收到某種事件,應用程序就將其扔給處理這個事件的函數。雖然看上去不同的窗口是同時被刷新的,但實際上,絕大多數的GUI程序都是單線程的,因此窗口的刷新是依次按順序進行的。如果由於某種意外你的電腦變得很慢導致窗口刷新的過程變的很明顯,你就會注意到這一點。

不同的GUI編程架構用不同的方法將它內部的事件處理機制展現給程序開發者。對於wxWidgets來說,事件表機制是最主要的方法。在下一小節我們會對此進行進一步的解釋。


5,事件表和事件處理過程

wxWidgets事件處理系統比起通常的虛方法機制來說要稍微復雜一點,但它的一個好處是可以避免需要實現基類中所有的虛方法,因為實現所有的基類虛方法有時候是不切實際的或者是效率很低的。

每一個wxEvtHandler的派生類,例如frame,按鈕,菜單以及文檔等,都會在其內部維護一個事件表,用來告訴wxWidgets事件和事件處理過程的對應關系。所有繼承自wxWindow的窗口類,以及應用程序類都是wxEvtHandler的派生類.

要創建一個靜態的事件表(意味着它是在編譯期間創建的),你需要下面幾個步驟: 

定義一個直接或者間接繼承自wxEvtHandler的類.
為每一個你想要處理的事件定義一個處理函數。
在這個類中使用DECLARE_EVENT_TABLE聲明事件表。
在.cpp文件中使用使用BEGIN_EVENT_TABLE和END_EVENT_TABLE實現一個事件表。

在事件表的實現中增加事件宏,來實現從事件到事件處理過程的映射。


所有的事件處理函數擁有相同的形式。他們的返回值都是void,他們都不是虛函數,他們都只有一個事件對象作為參數。(如果你熟悉MFC,這可能會讓你覺得輕松,因為在MFC中消息處理函數並沒有一個統一的形式。)這個事件對象的類型是隨這個處理函數要處理的事件的變化而變化的。例如簡單控件(比如按鈕)的命令處理函數和菜單命令的處理函數的參數都是wxCommandEvent類型,而size事件(這個事件通常是由用戶改變窗口的客戶區尺寸而引起的)處理函數的參數則是wxSizeEvent的類型。不同的事件參數類型可以調用的方法也不相同,通過這些方法,你可以獲得事件產生的原因以及產生這個事件的控件的值的改變情況(比如,文本框中的值的改變)。當然最簡單的情形是你完全不需要訪問這個參數的任何方法,比如按鈕點擊事件。

讓我們來擴展一下前一章中的例子,來增加一個窗口大小改變事件的處理和一個確定按鈕的處理。下面是擴展以后的MyFrame的定義: 

class MyFrame : public wxFrame

{

public:  

  MyFrame(const wxString& title); 

   void OnQuit(wxCommandEvent& event);

    void OnAbout(wxCommandEvent& event);

    void OnSize(wxSizeEvent& event);

    void OnButtonOK(wxCommandEvent& event);

private: 

   DECLARE_EVENT_TABLE()

};

增加菜單項的代碼和前一章的代碼類似,而在frame窗口增加一個按鈕的代碼也只需要在MyFrame的構造函數中增加下面的代碼: 
wxButton* button = new wxButton(this, wxID_OK, wxT("OK"), wxPoint(200, 200));
類似的,在事件表中也需要相應的增加事件映射宏: 

BEGIN_EVENT_TABLE(MyFrame, wxFrame)

    EVT_MENU(wxID_ABOUT,     MyFrame::OnAbout)

    EVT_MENU(wxID_EXIT,      MyFrame::OnQuit)

    EVT_SIZE(                MyFrame::OnSize)

    EVT_BUTTON(wxID_OK,        MyFrame::OnButtonOK)

END_EVENT_TABLE()

當用戶點擊關於菜單或者退出菜單的時候,這個事件被發送到frame窗口,而MyFrame的事件表告訴wxWidgets,對於標識符為 wxID_ABOUT的菜單事件應該發送到MyFrame::OnAbout函數,而標識符為wxID_EXIT的菜單事件應該發送到MyFrame:: OnQuit函數。換句話說:當事件處理循環處理這兩個事件的時候,相應的函數將會以一個的wxCommandEvent類型的參數被調用。

EVT_SIZE事件映射宏不需要標識符參數因為這個事件只會被產生這個事件的控件所處理。

EVT_BUTTON這一行將導致當frame窗口及其子窗口中標識符為wxID_OK的按鈕被點擊的時候,OnButtonOK函數被調用。這個例子表明,事件可以不必被產生這個事件的控件所處理。讓我們假定這個按鈕是MyFrame的子窗口。當按鈕被點擊的時候,wxWidgets會首先檢查wxButton類是否指定了相應的處理函數,如果找不到,則在其父親的類所屬的事件表中進行查找,在這個例子中,按鈕的父親是MyFrame類型的一個實例,在其事件表中指明了一個對應的處理函數,因此MyFrame::OnButtonOK函數就被調用了。類似的搜索過程不光發生在窗口控件的父子繼承樹中,也發生在普通的類繼承關系中,這意味着你可以選擇在哪里定義事件的處理函數。舉例來說,如果你設計了一個對話框,這個對話框需要響應的類似標識符為wxID_OK的command事件。但是你可能需要把這個控件的創建工作留給使用你的代碼的其他的程序員,只要他在創建這個控件的時候使用同樣的標識符,你仍然可以給這個控件為這個事件定義默認的處理函數。

下圖中演示了一次普通的按鈕點擊事件發生以后,wxWidgets搜索所有事件表的順序。圖中只演示了wxButton和 MyFrame兩次繼承關系。當用戶點擊了確認按鈕的時候,一個新的wxCommandEvent事件被創建,其中包含標識符wxID_OK和事件類型 wxEVT_COMMAND_BUTTON_CLICKED,然后這個按鈕的事件表開始通過wxEvtHandler::ProcessEvent函數進行匹配,事件表中的每一個條目都會去嘗試匹配,然后是其父類wxControl的事件表,然后是wxWindow的。如果都沒有匹配到, wxWidgets會搜索其父親的類事件表,然后就找到了一條匹配條目: 

EVT_BUTTON(wxID_OK,MyFrame::OnButtonOK)
因此MyFrame::OnButtonOK被調用了。 



需要注意的事:只有Command事件(其事件類型直接或者間接的繼承自wxCommandEvent)才會被遞歸的應用到其父親的事件表。通常這是wxWidgets的用戶經常會感到困惑的地方,因此我們把那些不會傳遞給其父親的事件表的事件列舉如下:wxActivate, wxCloseEvent, wxEraseEvent, wxFocusEvent, wxKeyEvent, wxIdleEvent, wxInitDialogEvent, wxJoystickEvent, wxMenuEvent, wxMouseEvent, wxMoveEvent, wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent, wxScrollWinEvent, 和wxSysColourChangedEvent,這些事件都不會傳給事件源控件的父窗口.

這些事件不會傳遞給其父窗口,是因為這些事件僅對產生這個事件的窗口才有意義,舉例來說,把一個子窗口的重繪事件發送給它的父窗口,其實是沒有任何意義的。

6,過濾某個事件 

wxWidgets事件處理系統實現了一些和C++中的虛方法非常類似的機制,通過這種機制,你可以通過重載某種基類的事件表的方法來改變基類的默認的事件處理過程。在多數情況下,通過這種方法,你甚至可以改變本地原生控件的默認行為。舉例來說,你可以過濾某些按鍵事件以便本地原生的編輯框控件不處理這些按鍵。要達到這個目的,你需要實現一個繼承自wxTextCtrl的新的類,然后在其事件表中使用EVT_KEY_DOWN事件映射宏。過濾所有的按鍵事件也許不是你想要的,這時候,你可以通過調用wxEvent::Skip函數來提示事件處理過程對於其中的某些按鍵事件應該繼續尋找其父類的事件表。

總的來說,在wxWidgets中,你應該通過調用事件的Skip方法,而不是通過顯式直接調用其父類對應函數的方法來實現對特殊事件的過濾。下面的這個例子演示怎樣讓你自己的文本框控件只接受"a"到"z"和"A"到"Z"的按鍵,而忽略其它按鍵的方法: 

void MyTextCtrl::OnChar(wxKeyEvent& event)

   if ( wxIsalpha( event.KeyCode() ) ) 

   {

       //這些按鍵在可以接受的范圍,所以按照正常的流程處理

       event.Skip();

    } 

   else

    {

       // 這些事件不在我們可以接受的范圍,所以我們不調用Skip函數

       // 由於事件表已經匹配並且沒有調用Skip函數,所以事件處理過程不會

       // 再繼續匹配別的事件表,而是認為事件處理已經結束

       wxBell();

    }

}

7,掛載事件表

你並不一定非要實現繼承自某個類的新類,才可以改變它的事件表。對於那些繼承自wxWindow的類來說,有另外一種可取代的方法。你可以通過實現一個新的直接繼承自wxEvtHandler的新類,然后定義這個新類事件表,然后使用wxWindow::PushEventHandler函數將這個事件表壓入到某個窗口類的事件表棧中。最后壓入的那個事件表在事件匹配過程中將會被最先匹配,如果在其中沒有匹配到對應的事件處理過程,那么棧中以前的事件表仍將被匹配,如此類推。你還可以用wxWindow::PopEventHandler函數來彈出最頂層的事件表,如果你給wxWindow:: PopEventHandler函數傳遞的是True的參數,那么這個彈出的事件表將被刪除。

這種方法可以避免大量的類重載,也使不同的類的實例共享同一個事件表成為可能。

有時候,你需要手動調用窗口類的事件表,這時候你應該使用wxWindow::GetEventHandler方法,而不是直接使用調用這個窗口類的成員函數。雖然wxWindow::GetEventHandler通常返回這個窗口類本身。但是如果你之前曾經使用 PushEventHandler壓入另外一個事件表,那么函數將會返回處於最頂層的事件表。因此使用wxWindow:: GetEventHandler函數才可以保證事件被正確的處理。

PushEventHandler的方法通常用來臨時的或者永久的改變圖形界面的行為。舉例來說,加入你想在你的應用程序實現對話框編輯的功能。你可以捕獲這個對話框和它的內部控件的所有的鼠標事件,先使用你自己的事件表處理這些事件,來進行類似拖拽控件,改變控件大小以及移動控件動作。這在聯機教學中也是很有用技術。你可以在你自己的事件表中過濾收到的事件,如果是可以接受的,則調用wxEvent::Skip函數正常處理


8,動態事件處理方法

前面我們討論的事件處理方法,都是靜態的事件表,這也是我們處理事件最常用的方式。接下來,我們來討論一下事件表的動態處理,也就是說在運行期改變事件表的映射關系。使用這種事件映射方法的原因,可能是你想在程序運行的不同時刻使用不同的映射關系,或者因為你使用的那種語言(例如python)不支持靜態映射,或者僅僅是因為你更喜歡動態映射。因為動態映射的方法可以使你更精確的控制事件表的細節,你甚至可以單獨的將事件表中的某一個條目在運行期打開或者關閉,而前面說的PushEventHandler和PopEventHandler的方法只能針對整個事件表進行處理。除此以外,動態事件處理還允許你在不同的類之間共享事件函數

和動態事件處理相關的API有兩個:wxEvtHandler::Connect和wxEvtHandler::Disconnect。大多數情況下你不需要手動調用wxEvtHandler::Disconnect函數,這個函數將在窗口類被釋放的時候自動。下面我們還用前面的MyFrame類來舉例說明: 

class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString& title); 
   void OnQuit(wxCommandEvent& event);    
   void OnAbout(wxCommandEvent& event);
    private:
};
你可能已經注意到,這次我們沒有使用DECLARE_EVENT_TABLE來聲明一個事件表。為了動態進行事件映射,我們需要在OnInit函數中增加下面的代碼: 
frame->Connect( wxID_EXIT,    wxEVT_COMMAND_MENU_SELECTED,    wxCommandEventHandler(MyFrame::OnQuit) );
frame->Connect( wxID_ABOUT,    wxEVT_COMMAND_MENU_SELECTED,    wxCommandEventHandler(MyFrame::OnAbout) );

我們傳遞給Connect函數的三個參數分別為菜單標識符,事件標識符和事件處理函數指針。要注意這里的事件標識符 wxEVT_COMMAND_MENU_SELECTED不同於前面在靜態事件表中用於表示事件映射的宏EVT_MENU,實際上EVT_MENU內部也使用了wxEVT_COMMAND_MENU_SELECTED.EVT_MENU其實也自動包含了用於對事件處理指針類型強制轉換的宏 wxCommandEventHandler()。一般說來,如果事件處理函數的參數類型是wxXYZEvent,那么其處理函數的類型就應該用 wxXYZEventHandler宏進行強制轉換.

如果我們希望在某個時候中止上面的事件映射,可以使用下面的方法: 

frame->Disconnect( wxID_EXIT,    wxEVT_COMMAND_MENU_SELECTED,    wxCommandEventHandler(MyFrame::OnQuit) );

frame->Disconnect( wxID_ABOUT,    wxEVT_COMMAND_MENU_SELECTED,    wxCommandEventFunction(MyFrame::OnAbout) );


9,自定義事件

如果你要使用自定義的事件,你需要下面的幾個步驟: 
1,從一個合適的事件類派生一個你自己的事件類,聲明動態類型信息並且實現一個Clone函數,按照你自己的意願增加新的數據成員和函數成員.如果你希望這個事件在窗口繼承關系之間傳遞,你應該使用的wxCommandEvent派生類,如果你希望這個事件的處理函數可以調用Veto(譯者注:某些事件可以調用這個函數來阻止后續可能對這個事件進行的任何操作(如果有的話),最典型的例子是關閉窗口事件wxEVT_CLOSE),你應該使用 wxNotifyEvent的派生類.
2,為這個事件類的處理函數定義類型.
3,定義一個你的事件類支持的事件類型的表。這個表應該定義在你的頭文件中。用BEGIN_DECLARE_EVENT_TYPES()宏和END_DECLARE_EVENT_TYPES()宏包含起來。其中的每一個支持的事件的聲明應該使用DECLARE_EVENT_TABLE (name, integer)格式的宏.然后在你的.cpp文件中使用DEFINE_EVENT_TYPE(name)來實現這個事件類.

4,為每個你的事件類支持的事件定義一個事件映射宏。


我們還是通過例子來讓上面這段繞口的話顯的更生動一些。假如我們要實現一個新的控件wxFontSelectorCtrl,這個控件將可以顯示字體的預覽。用戶通過點擊字體的預覽來彈出一個對話框讓用戶可以更改字體。應用程序也許想攔截這個字體改變事件,因此我們在我們的底層鼠標消息處理過程中將會給應用程序發送一個自定義的字體改變事件。

因此我們需要定義一個新的事件wxFontSelectorCtrlEvent.應用程序可以通過事件映射宏 EVT_FONT_SELECTION_CHANGED(id, func)來增加對這個事件的處理。我們還需要給這個事件定義一個事件類型: wxEVT_COMMAND_FONT_SELECTION_CHANGED. 這樣,我們的頭文件看上去就象下面的樣子: 

/*! * Font selector event class */
class wxFontSelectorCtrlEvent : public wxNotifyEvent
{
public:  
  wxFontSelectorCtrlEvent(wxEventType commandType = wxEVT_NULL,      int id = 0): 
   wxNotifyEvent(commandType, id)    {}   
   wxFontSelectorCtrlEvent(const wxFontSelectorCtrlEvent& event): wxNotifyEvent(event) 
   {} 
   virtual wxEvent *Clone() const            
      { return new wxFontSelectorCtrlEvent(*this); }
DECLARE_DYNAMIC_CLASS(wxFontSelectorCtrlEvent);};
typedef void (wxEvtHandler::*wxFontSelectorCtrlEventFunction)

       (wxFontSelectorCtrlEvent&);

/*! * Font selector control events and macros for handling them */

BEGIN_DECLARE_EVENT_TYPES()

    DECLARE_EVENT_TYPE(wxEVT_COMMAND_FONT_SELECTION_CHANGED, 801)

END_DECLARE_EVENT_TYPES()
#define EVT_FONT_SELECTION_CHANGED(id, fn) 
DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_FONT_SELECTION_CHANGED, id, -1, (wxObjectEventFunction) (wxEventFunction)(wxFontSelectorCtrlEventFunction) & fn,(wxObject *) NULL ),
而在我們的.cpp文件中,看上去則象下面的樣子: 

DEFINE_EVENT_TYPE(wxEVT_COMMAND_FONT_SELECTION_CHANGED)

IMPLEMENT_DYNAMIC_CLASS(wxFontSelectorCtrlEvent, wxNotifyEvent)

然后,在我們的新控件的鼠標處理函數中,可以通過下面的方法在檢測到用戶選擇了一個新的字體的時候,發送一個自定義的事件: 

wxFontSelectorCtrlEvent event(wxEVT_COMMAND_FONT_SELECTION_CHANGED, GetId());event.SetEventObject(this);

GetEventHandler()->ProcessEvent(event);

現在,在使用我們的新控件的應用程序的代碼中就可以通過下面代碼來處理我們定義的這個新事件了: 

BEGIN_EVENT_TABLE(MyDialog, wxDialog)

  EVT_FONT_SELECTION_CHANGED(ID_FONTSEL, MyDialog::OnChangeFont)

END_EVENT_TABLE()

void MyDialog::OnChangeFont(wxFontSelectorCtrlEvent& event)

{    // 字體已經更改了,可以作一些必要的處理。    ...}

上面用到的事件標識符801在最新的版本中已經沒有用處了,之所以這樣寫只是為了兼容wxWidgets2.4的版本。讓我們再來看一眼事件映射宏的定義: 

#define EVT_FONT_SELECTION_CHANGED(id, fn) DECLARE_EVENT_TABLE_ENTRY( wxEVT_COMMAND_FONT_SELECTION_CHANGED, id, -1, (wxObjectEventFunction)

 (wxEventFunction)(wxFontSelectorCtrlEventFunction) & fn,

(wxObject *) NULL ),

這個宏的作用其實是把一個五元組放入到一個數組中,所以這段代碼的語法看上去是奇怪了一些,這個五元組的意義分別解釋如下: 
1.事件類型:一個事件類可以處理多種事件類型,但是在我們的例子中,只處理了一個事件類型,所以就只有一個事件映射宏的記錄。這個事件類型必須要和事件處理函數所有處理的事件的類型一致。
2.傳遞給事件映射宏的標識符:只有當事件的標識符和事件表中的標識符一致的時候,相應的事件處理函數才會被調用。
3.第二標識符。在這里-1表示沒有第二標識符。
4.事件處理函數。如果沒有類型的強制轉換,一些編譯器會報錯,這也就是我們要定義事件處理函數類型的原因。
4.用戶數據,通常都是NULL;

10,窗口標識符

窗口標識符是在事件系統中用來唯一確定窗口的整數。事實上,在整個應用程序的范圍內,窗口標識符不必一定是唯一的,而只要在某個固定的上下文(比如說,在一個frame窗口和它的所有子窗口)內是唯一的就可以了。舉例來說:你可以在無數個對話框中使用wxID_OK這個標識符,只要在某個對話框內不要重復使用就可以了。

如果在窗口的構造函數中使用wxID_ANY作為其標識符,則意味着你希望wxWidgets自動為你生成一個標識符。這或者是因為你不關心這個標識符的值,或者是因為這個窗口不需要處理任何事件,或者是因為你將在同一個地方處理所有的事件。如果是最后一種情況,在使用 wxEvtHandler::Connect函數或者在靜態事件表中,你應該使用wxID_ANY作為窗口的標識符。wxWidgets自動創建的標識符是總是一個負數,所以永遠不會和用戶定義的窗口標識符重復,用戶定義的窗口標識符只能是正整數。

下表列舉了wxWidgets提供的一些標准的標識符。你應該盡可能的使用這些標識符,這是由於下面一些原因。某些系統會給特定的標識符提供一些小圖片(例如GTK+系統上的OK和取消按鈕)或者提供默認的處理函數(例如自動產生wxID_CANCEL事件來響應Escape鍵)。在 Mac OS X系統上,wxID_ABOUT, wxID_PREFERENCES和wxID_EXIT菜單項也有特別的處理。另外一些wxWidgets的控件也會自動處理標識符為 wxID_COPY, wxID_PASTE或 wxID_UNDO等的一些菜單或者按鈕的命令。 

標識符名稱        描述        
wxID_ANY        讓wxWidgets自動產生一個標識符        
wxID_LOWEST        最小的系統標識符值 (4999)        
wxID_HIGHEST        最大的系統標識符值 (5999)        
wxID_OPEN        打開文件        
wxID_CLOSE        關閉窗口        
wxID_NEW        新建窗口文件或者文檔        
wxID_SAVE        保存文件        
wxID_SAVEAS        文件另存為(應該彈出文件位置對話框)        
wxID_REVERT        恢復文件在磁盤上的狀態        
wxID_EXIT        退出應用程序        
wxID_UNDO        撤消最近一次操作        
wxID_REDO        重復最近一次操作        
wxID_HELP        幫助 (例如對話框上的幫助按鈕可以用這個標識符)        
wxID_PRINT        打印        
wxID_PRINT_SETUP        打印設置        
wxID_PREVIEW        打印預覽        
wxID_ABOUT        顯示一個用來描述整個程序的對話框        
wxID_HELP_CONTENTS        顯示上下文幫助        
wxID_HELP_COMMANDS        顯示應用程序命令        
wxID_HELP_PROCEDURES        顯示應用程序過程        
wxID_HELP_CONTEXT        未使用        
wxID_CUT        剪切        
wxID_COPY        復制到剪貼板        
wxID_PASTE        粘貼        
wxID_CLEAR        清除        
wxID_FIND        查找        
wxID_DUPLICATE        復制        
wxID_SELECTALL        全選        
wxID_DELETE        刪除        
wxID_REPLACE        覆蓋        
wxID_REPLACE_ALL        全部覆蓋        
wxID_PROPERTIES        查看屬性        
wxID_VIEW_DETAILS        列表框中的按照詳細信息方式顯示        
wxID_VIEW_LARGEICONS        列表框按照大圖標的方式顯示        
wxID_VIEW_SMALLICONS        列表框中按照小圖標的方式顯示        
wxID_VIEW_LIST        列表框中按照列表的的方式顯示        
wxID_VIEW_SORTDATE        按照日期排序        
wxID_VIEW_SORTNAME        按照名稱排序        
wxID_VIEW_SORTSIZE        按照大小排序        
wxID_VIEW_SORTTYPE        按照類型排序        
wxID_FILE1 to wxID_FILE9        顯示最近使用的文件        
wxID_OK        確定        
wxID_CANCEL        取消        
wxID_APPLY        應用變更        
wxID_YES        YES        
wxID_NO        No        
wxID_STATIC        靜態文本或者靜態圖片可以用這個標識符        
wxID_FORWARD        向前                
wxID_BACKWARD        向后        
wxID_DEFAULT        恢復默認設置        
wxID_MORE        顯示更多選項        
wxID_SETUP        顯示一個設置對話框        
wxID_RESET        重置所有選項        
wxID_CONTEXT_HELP        顯示上下文幫助        
wxID_YESTOALL        全部選是        
wxID_NOTOALL        全部選否        
wxID_ABORT        中止當前操作        
wxID_RETRY        重試        
wxID_IGNORE        忽略錯誤        
wxID_UP        向上        
wxID_DOWN        向下        
wxID_HOME        首頁        
wxID_REFRESH        刷新        
wxID_STOP        停止正在進行的操作        
wxID_INDEX        顯示一個索引        
wxID_BOLD        加粗顯示        
wxID_ITALIC        斜體顯示        
wxID_JUSTIFY_CENTER        居中        
wxID_JUSTIFY_FILL        格式        
wxID_JUSTIFY_RIGHT        右對齊        
wxID_JUSTIFY_LEFT        左對齊        
wxID_UNDERLINE        下划線        
wxID_INDENT        縮進        
wxID_UNINDENT        反縮進        
wxID_ZOOM_100        放大到100%        
wxID_ZOOM_FIT        縮放到整頁        
wxID_ZOOM_IN        放大        
wxID_ZOOM_OUT        縮小        
wxID_UNDELETE        反刪除        
wxID_REVERT_TO_SAVED        恢復到上次保存的狀態        
為了避免你自己定義的標識符和這些預定義的標識符重復,你可以使用大於wxID_HIGHEST的標識符或者小於wxID_LOWEST的標識符。

11,wxWidgets程序一般執行過程
下面大概的描述一下整個程序的執行過程: 
1.依照系統平台的不同,不同的main函數或者winmain函數或者其它類似的函數被調用(這個函數是由wxWidgets提供的,而不是由應用程序提供的).wxWidgets 初始化它自己的數據結構並且創建一個MyApp的實例.
2.wxWidgets調用MyApp::OnInit函數, 這個函數會創建一個MyFrame的實例.
3.MyFrame的構造函數通過它的基類wxFrame的構造函數創建一個窗口,然后給這個窗口增加圖標,菜單欄和狀態欄.
4.MyApp::OnInit函數顯示主窗口並且返回真.
5.wxWidgets開始事件循環,等待事件發生並且將事件分發給相應的處理過程.
就目前我們所知道的,應用程序會在以下情況下退出:主窗口被關閉,用戶選擇退出菜單或者系統按鈕和系統菜單中的關閉選項(這些系統菜單和系統按鈕在不同的系統中就往往千差萬別了)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM