6、wxWidgets 事件處理
wxWidgets事件處理
事件處理是所有GUI程序重要的組成部分,所有GUI程序都是由事件驅動的。一個應用程序對其運行周期內產生的不同事件類型做出不同反應。事件主要由應用程序的用戶產生,但是它們也能以其它方法產生,例如:一個網絡請求、窗口管理器、定時器,當一個應用程序開始運行時,一個主循環開始啟動,程序被設置在這個主循環內執行,同時等待事件的產生,當退出這個程序時,主循環也就同時停止。
定義
事件是一個底層框架下面的程序級別的信息,被封裝成一個GUI工具包。事件循環是一個等待和派遣事件或消息的編程結構,事件循環反復的尋找事件並處理它們,事件句柄和方法對事件做出反應。
事件對象是一個和事件本身有關聯的對象,它通常是一個窗體,事件類型是一個剛產生的獨一無二的事件。
在wxWidgets裏面使用事件的傳統方法是使用一個靜態事件表,這是受MFC的影響。一個更加靈活和現代的方法是使用Connect()方法。
靜態事件表
下面是一個簡單的使用靜態事件表的例子:
main.h
1 #include <wx/wx.h> 2 //定義主窗口類 3 class MyFrame : public wxFrame 4 { 5 public: 6 MyFrame(const wxString& title); 7 8 //定義事件處理函數 9 void OnQuit(wxCommandEvent& event); 10 private: 11 //聲明事件表 12 DECLARE_EVENT_TABLE() 13 14 }; 15 //定義應用程序類 16 class MyApp : public wxApp 17 { 18 public: 19 virtual bool OnInit(); 20 };
main.cpp
1 #include "main.h" 2 3 //定義事件表,完成事件和處理函數的映射 4 BEGIN_EVENT_TABLE(MyFrame, wxFrame) 5 EVT_BUTTON(wxID_EXIT, MyFrame::OnQuit)6 END_EVENT_TABLE() 7 8 MyFrame::MyFrame(const wxString& title) 9 : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150)) 10 { 11 //添加狀態欄 12 CreateStatusBar(); 13 //將狀態欄分為兩欄 14 //CreateStatusBar(2); 15 //添加狀態欄顯示內容 16 SetStatusText(wxT("Welcome to wxWidgets!")); 17 18 //在wxFrame組件中定義了一個Panel容器,用於放置Button按鈕 19 wxPanel * panel = new wxPanel(this, wxID_ANY); 20 //添加一個按鈕 21 wxButton * button = new wxButton(panel, wxID_EXIT, wxT("Quit"), wxPoint(20, 20)); 22 button->SetFocus();//按鈕自動獲取焦點 23 24 //使整個wxFrame框架位於屏幕中間 25 Centre(); 26 } 27 void MyFrame::OnQuit(wxCommandEvent& event) 28 { 29 Close(true); 30 } 31 32 //聲明應用程序 33 IMPLEMENT_APP(MyApp) 34 35 //初始化應用程序 36 bool MyApp::OnInit() 37 { 38 MyFrame *myframe = new MyFrame(wxT("MyFrame")); 39 myframe->Show(true); 40 41 return true; 42 }
在我們的程序中,我們創建了一個簡單的按鈕,當我們點擊按鈕時,應用程序關閉。
1 private: 2 DECLARE_EVENT_TABLE()
在我們的頭文件中,我們通過DECLARE_EVENT_TABLE()宏定義了一個靜態事件表。
1 BEGIN_EVENT_TABLE(MyButton, wxFrame) 2 EVT_BUTTON(wxID_EXIT, MyButton::OnQuit) 3 END_EVENT_TABLE()
我們通過把事件和相應的處理函數對應起來實現了這個靜態時間表。
Connect()方法
下面將討論一個移動事件,一個移動事件包含了運動狀態變化事件。當我們移動一個窗口時,一個移動事件相應產生。代表移動事件的類是wxMoveEvent,wxEVT_MOVE是這個事件的類型。
main.h
1 //事件處理函數動態關聯 2 #include <wx/wx.h> 3 4 //定義主框架類 5 class MyFrame : public wxFrame 6 { 7 public: 8 MyFrame(const wxString & title); 9 10 //窗口移動事件的處理函數 11 void OnMove(wxMoveEvent & event); 12 //兩個靜態文本 13 wxStaticText * st1; 14 wxStaticText * st2; 15 }; 16 17 //定義應用程序類 18 class MyApp : public wxApp 19 { 20 public: 21 virtual bool OnInit(); 22 };
main.cpp
1 #include "main.h" 2 3 MyFrame::MyFrame(const wxString & title) 4 : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130)) 5 { 6 //定義一個面板容器 7 wxPanel * panel = new wxPanel(this, -1); 8 //定義了兩個靜態文本組件 9 st1 = new wxStaticText(panel, wxID_ANY, _T(""), wxPoint(10, 10)); 10 st2 = new wxStaticText(panel, wxID_ANY, _T(""), wxPoint(10, 30)); 11 //事件處理函數的動態關聯 12 Connect(wxEVT_MOVE, wxMoveEventHandler(MyFrame::OnMove)); 13 14 Centre(); 15 } 16 17 void MyFrame::OnMove(wxMoveEvent & event) 18 { 19 //獲取主窗口左上角的屏幕坐標 20 wxPoint size = event.GetPosition(); 21 //修改靜態文本組件的值 22 st1->SetLabel(wxString::Format(_T("x: %d"), size.x)); 23 st2->SetLabel(wxString::Format(_T("y: %d"), size.y)); 24 } 25 //聲明應用程序 26 IMPLEMENT_APP(MyApp) 27 28 bool MyApp::OnInit() 29 { 30 MyFrame * myFrame = new MyFrame(_T("MyFrame")); 31 myFrame->Show(true); 32 33 return true; 34 }
在這個例子中我們顯示了當前窗口的坐標
Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
這裏我們把一個wxEVT_MOVE事件類型和OnMove()方法連接起來。
wxPoint size = event.GetPosition();
在OnMove()方法中的event參數是一個特定事件的對象,在我們的例子中它是一個wxMoveEvent類的實例,這個對象存儲了關於這個事件的信息,我們可以調用GetPosition()方法來獲得當前窗口的坐標。
事件的傳遞
wxWidgets有兩種事件,Basic事件和Command事件,它們在傳遞性方面有所不同。事件從子控件傳遞到父控件,依次往上傳遞。Basic事件不會傳遞而Command事件會傳遞。例如wxCloseEvent是一個Basic事件,對於這個事件而言傳遞到父窗口是沒有意義的。
通常情況下,被事件處理函數捕獲的事件不會再傳遞到父窗口,為了使它傳遞上去,我們必須調用Skip()方法。
main.h
1 //事件的傳遞 2 #include <wx/wx.h> 3 4 //分別定義了自己的wxFrame wxPanel wxButton,並定義了相應的點擊事件處理函數 5 class Propagate : public wxFrame 6 { 7 public: 8 Propagate(const wxString & title); 9 10 void OnClick(wxCommandEvent & event); 11 }; 12 13 class MyPanel : public wxPanel 14 { 15 public: 16 MyPanel(wxFrame * frame, wxWindowID id); 17 18 void OnClick(wxCommandEvent & event); 19 }; 20 21 class MyButton : wxButton 22 { 23 public: 24 MyButton(MyPanel * panel, wxWindowID id, const wxString & label); 25 26 void OnClick(wxCommandEvent & event); 27 }; 28 29 //定義應用程序類 30 class MyApp : public wxApp 31 { 32 public: 33 virtual bool OnInit(); 34 };
main.cpp
1 #include "main.h" 2 3 const int ID_BUTTON = 1; 4 5 Propagate::Propagate(const wxString & title) 6 : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130)) 7 { 8 //新建了一個面板組件,放在wxFrame上 9 MyPanel * panel = new MyPanel(this, -1); 10 //定義了一個Button放在面板上 11 new MyButton(panel, ID_BUTTON, _T("OK")); 12 //按鈕點擊事件處理函數的動態關聯 13 Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 14 wxCommandEventHandler(Propagate::OnClick)); 15 16 Centre(); 17 } 18 //在wxFrame的點擊事件處理函數中,對點擊事件進行了處理,關閉了主窗口界面 19 void Propagate::OnClick(wxCommandEvent & event) 20 { 21 wxMessageBox(_T("Event reach the frame class")); 22 //event.Skip(); 23 Close(true); 24 } 25 26 MyPanel::MyPanel(wxFrame * frame, wxWindowID id) 27 : wxPanel(frame, id) 28 { 29 //面板上button點擊事件處理函數的動態關聯 30 Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 31 wxCommandEventHandler(MyPanel::OnClick)); 32 } 33 //在Panel的點擊事件處理函數中,並沒有對點擊事件進行處理,而是將其傳遞給了他的上層組件wxFrame 34 void MyPanel::OnClick(wxCommandEvent & event) 35 { 36 wxMessageBox(_T("Event reach the panel class")); 37 event.Skip(); 38 } 39 40 MyButton::MyButton(MyPanel * panel, wxWindowID id, const wxString & label) 41 : wxButton(panel, id, label, wxPoint(15, 15)) 42 { 43 //Button點擊事件處理函數的動態管理 44 Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED, 45 wxCommandEventHandler(MyButton::OnClick)); 46 } 47 //在Button的點擊事件處理函數中,並沒有對點擊事件進行處理,而是將其傳遞給了他的上層組件Panel 48 void MyButton::OnClick(wxCommandEvent & event) 49 { 50 wxMessageBox(_T("Event reach the button class")); 51 event.Skip(); 52 } 53 //聲明應用程序 54 IMPLEMENT_APP(MyApp) 55 56 bool MyApp::OnInit() 57 { 58 Propagate * prop = new Propagate(_T("Propagate")); 59 prop->Show(true); 60 61 return true; 62 }
在上面的例子中,我們把一個button放在panel上,然後把panel放在一個frame控件上,我們為每一個控件都定義了一個事件處理函數。當我們單機按鈕時,這個事件從button一直傳遞到了frame。嘗試去掉Skip()方法,看看會怎樣。
否決一個事件
有些時候我們需要停止處理一個事件,我們可以調用Veto()方法
main.h
1 //否決一個事件 2 #include <wx/wx.h> 3 4 class Veto : public wxFrame 5 { 6 public: 7 Veto(const wxString & title); 8 9 void OnClose(wxCloseEvent & event); 10 }; 11 12 class MyApp : public wxApp 13 { 14 public: 15 virtual bool OnInit(); 16 };
main.cpp
1 #include "main.h" 2 //主窗口類的實現 3 Veto::Veto(const wxString & title) 4 : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130)) 5 { 6 //窗口關閉事件處理函數的動態關聯 7 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(Veto::OnClose)); 8 //讓窗口在屏幕中居中顯示 9 Centre(); 10 } 11 12 void Veto::OnClose(wxCloseEvent & event) 13 { 14 //彈出對話框 15 wxMessageDialog * dial = new wxMessageDialog(NULL, _T("Are you sure to quit?"), 16 _T("Question"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); 17 //彈出對話框選項值的獲取 18 int ret = dial->ShowModal(); 19 //對話框關閉 20 dial->Destroy(); 21 22 //根據對話框選項值采取相應的操作 23 if(ret == wxID_YES) 24 { 25 Destroy(); 26 } 27 else 28 { 29 event.Veto(); 30 } 31 } 32 //聲明應用程序 33 IMPLEMENT_APP(MyApp) 34 35 bool MyApp::OnInit() 36 { 37 Veto * veto = new Veto(_T("Veto")); 38 veto->Show(true); 39 40 return true; 41 }
在我們的例子中,我們處理一個wxCloseEvent事件,當我們按下窗口標題欄右邊的X、輸入Alt+F4或者從系統菜單上把程序關閉時這個事件產生。在許多應用程序中,我們希望阻止窗口意外關閉。要實現它,我們必須要連接wxEVT_CLOSE_WINDOW這個事件類型。
1 wxMessageDialog * dial = new wxMessageDialog(NULL, _T("Are you sure to quit?"),
_T("Question"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
在關閉事件產生之後,我們顯示了一個消息對話框。
1 if(ret == wxID_YES) 2 Destroy(); 3 else 4 event.Veto();
我們通過返回值確定是銷毀窗口還是阻止這個事件,註意,我們要銷毀一個窗口,必須要調用它的Destroy()方法。通過調用Close()方法會讓我們陷入一個無窮的循環。
窗口標識符
窗口標識符是在事件系統中指定的唯一一個整數,有三種方法可以建立一個標識符。
1、讓系統自動創建一個ID
2、使用wxWidgets自帶的標準ID
3、使用你自己的ID
每一個控件都有一個ID參數,這個ID在整個事件系統中是獨一無二的。
1 wxButton(parent, -1); 2 wxButton(parent, wxID_ANY);
如果我們把id參數設為-1或者wxID_ANY,wxWidgets會自動為我們創建一個ID,這個自動創建的ID總是一個負數,然而用戶指定的ID必須是正數。當我們不需要改變控件的狀態時,通常使用wxID_ANY這個選項,例如一個靜態的文本控件,它在整個程序的生命周期內都不會發生改變。但是我們仍然能夠指定我們自己的ID。有一個GetID()方法會返回控件的ID。
只要有可能,就盡量使用標準的ID,這些標準ID提供了一些獨立於平臺的小圖形或者一些行為。
main.h
1 //窗口標識符 2 #include <wx/wx.h> 3 //定義主窗口類 4 class Ident : public wxFrame 5 { 6 public: 7 Ident(const wxString & title); 8 }; 9 10 class MyApp : public wxApp 11 { 12 public: 13 virtual bool OnInit(); 14 };
main.cpp
1 #include "main.h" 2 3 Ident::Ident(const wxString & title) 4 : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(200, 150)) 5 { 6 //定義了一個面板放在wxFrame上 7 wxPanel * panel = new wxPanel(this, -1); 8 //定義了一個wxGridSizer布局控件,2行 3列 9 wxGridSizer * grid = new wxGridSizer(2, 3); 10 11 //向wxGridSizer布局控件中添加組件 12 grid->Add(new wxButton(panel, wxID_CANCEL), 0, wxTOP | wxLEFT, 9); 13 grid->Add(new wxButton(panel, wxID_DELETE), 0, wxTOP, 9); 14 15 grid->Add(new wxButton(panel, wxID_SAVE), 0, wxTOP | wxLEFT, 9); 16 grid->Add(new wxButton(panel, wxID_EXIT), 0, wxTOP, 9); 17 18 grid->Add(new wxButton(panel, wxID_STOP), 0, wxTOP | wxLEFT, 9); 19 grid->Add(new wxButton(panel, wxID_NEW), 0, wxTOP, 9); 20 21 //將wxGridSizer布局控件加載到Panel中 22 panel->SetSizer(grid); 23 Centre(); 24 } 25 //聲明應用程序 26 IMPLEMENT_APP(MyApp) 27 28 bool MyApp::OnInit() 29 { 30 Ident * ident = new Ident(_T("Ident")); 31 ident->Show(true); 32 33 return true; 34 }
在我們的例子中,我們在按鈕上使用了標準標識符。正常情況下,在Linux系統下,按鈕上會顯示一個小圖標。(我用的Ubuntu14.04,不知為什麽,沒有圖標顯示)
效果展示:
6、wxWidgets 事件處理