首页 wxWidgets事件处理

wxWidgets事件处理

举报
开通vip

wxWidgets事件处理wxWidgets事件处理 wxWidgets不完全系列之--wxEvtHandler 这里介绍wxWidgets中的一个非常重要的部分wxEvtHandler,很多人对这个也一直感到很困扰,希望这个对大家有所帮助. 大家都知道,windows的程序都是消息驱动的.在wxWidgets中,wxWindow是从wxEvtHandler继承的,所有GUI的消息都是wxEvtHandler来处理的,像大家经常用到的 wxMouseEvent,wxKeyEvent,wxMenuEvent,wxPaintEvent等等...

wxWidgets事件处理
wxWidgets事件处理 wxWidgets不完全系列之--wxEvtHandler 这里介绍wxWidgets中的一个非常重要的部分wxEvtHandler,很多人对这个也一直感到很困扰,希望这个对大家有所帮助. 大家都知道,windows的程序都是消息驱动的.在wxWidgets中,wxWindow是从wxEvtHandler继承的,所有GUI的消息都是wxEvtHandler来处理的,像大家经常用到的 wxMouseEvent,wxKeyEvent,wxMenuEvent,wxPaintEvent等等.说wxEvtHander是GUI部分的核心一点不为过. 当一个消息事件到达一个对象时候,看看事件的处理流程(信息来自wxWidgets的帮助"Event handling overview"),消息都是在ProcessEvent函数中处理: 1.如果对象禁止事件处理的话(通过调用wxEvtHandler::SetEvtHandlerEnabled),直接跳到第六步. 2.如果对象是wxWindow,ProcessEvent会一直递归调用window的wxValidator.如果返回真,事件处理结束. 3.通过调用SearchEventTable函数来查找该消息事件,如果查找失败,会到对象的父类中查找的.直到找到符合的事件处理函数,事件处理结束. 4.把消息事件传递给事件处理链的下一个wxEvtHander,直到链中最后一个wxEvtHander,如果找到符合的事件处理函数,事件处理结束. 5.如果前面都失败了,则需要认真对待了:-).如果对象是wxWindow,并且消息是允许扩散传递的,ProcessEvent将会递归使用对象的parent window的wxEvtHander来处理事件,如果parent window处理了该消息,事件处理结束.什么消息是允许扩散传递的呢?在wxWidgets中还是非常容易判断的,如果事件是从wxCommandEvent继承下来的,就是允许的.这个会在后面详细说明. 6.最后的防线了,ProcessEvent将使用wxApp的wxEvtHander来处理事件. 好的,规则定好了,下面针对性的说一些具体的细节部分. 第一步: 太简单了:-),真不知道可以补充什么了. 第二步: 对wxValidator一点也不熟悉,也一直没有下决心去认真研究研究,在这里就先略过了:-(.希望以后能加上针对性的说明. 第三步: 一般来说,前面两步很多情况都可以直接忽略的,基本很少人会禁止自己的消息处理吧? 消息到了这里后,处理就很像MFC中的了.如果自己不显式处理的话,消息会在父类中处理的.如果自己处理的话,也要看看是否有需要父类也处理.在MFC中,如果想处理某消息,需要重载对应的虚函数,调用父类的消息处理函数来实现传递给父类处理的.例如: void MyButton::OnMouseDown(...) { ... CButton::OnMouseDown(...); } 最初wxWidgets也是使用的虚函数来实现消息处理的,但是从2.0开始,开始使用事件表来实现了.这个可能是很多人不熟悉的地方,特别是从MFC转过来的.例如想处理WM_SIZE消息,需要在事件表中定义对应的事件函数,如下: BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_SIZE (MyFrame::OnSize) END_EVENT_TABLE() EVT_SIZE就是表示WM_SIZE消息,OnSize就是对应的处理函数了.具体的可以查看wxWidgets的帮助,有完整的事件表. 那如果在OnSize里自己处理完后该如何通知父类处理呢?因为现在是事件表了,不像虚函数,能明确的调用父类同名函数来实现.这里就有一个重要的函数:wxEvent::Skip(bool skip = true).为什么不是wxEvtHander的函数?我个人认为,wxEvtHander所有处理都是针对wxEvent的,wxEvent对象在整个消息周期都存在,而且一直从发送者传递给所有的消息处理者,是一个理想的状态携带者.可以看到默认是不在传递的,这样父类会丢失对事件的处理机会的,这个对像wxKeyEvent,wxMouseEvent是非常重要的,如果不让父类处理的话,那真不知道会有什么情况.当然有些特殊情况会要父类忽略某些消息,默认行为就能很好的完成任务了. 第四步: 什么是事件处理链呢?默认每个窗口都有自己的wxEvtHander,但是在wxWidgets中是可以重新设置的,并且可以添加更多监听者.相关函数是:wxWindow::SetEventHandler和wxWindow::PushEventHandler.这样有个好处是,你可以在一个wxEvtHander中处理所有的控件消息(如果你把你的wxEvtHander插入到你关心的所有控件中).第一个函数是直接替换了原来的wxEvtHander,所以一般推荐使用第二种:PushEventHander.需要注意的是(PushEventHander):最后插入的wxEvtHander是第一个处理事件的!这样窗口默认的wxEvtHander是最后才被调用的.所以要非常注意对wxEvent::Skip的调用.如果你不调用事件的Skip的话,后续所有的wxEvtHander将会失去对事件的处理机会!切记,切记!一般来说,自己push的wxEvtHander都会在最后调用wxEvent::Skip(false)的. 第五步: 这里的关键是什么才是允许扩散传递的消息?简单的说就是重wxCommandEvent继承的事件.我们先来看看非扩散传递的消息: wxEvent wxActivateEvent A window or application activation event wxCloseEvent A close window or end session event wxEraseEvent An erase background event wxFocusEvent A window focus event wxKeyEvent A keypress event wxIdleEvent An idle event wxInitDialogEvent A dialog initialisation event wxJoystickEvent A joystick event wxMenuEvent A menu event wxMouseEvent A mouse event wxMoveEvent A move event wxPaintEvent A paint event wxQueryLayoutInfoEvent Used to query layout information wxSetCursorEvent Used for special cursor processing based on current mouse position wxSizeEvent A size event wxScrollWinEvent A scroll event sent by a scrolled window (not a scroll bar) wxSysColourChangedEvent A system colour change event 大家可以看到,windows很多窗口的消息都是不可扩散传递的!比如Paint,Mouse,Key,Size,Move等等.其实这个是和MFC中一致的,只有控件自己才能处理自己的windows的窗口消息. 那什么是可以允许扩散传递的消息呢?这里举些例子:wxGridEvent,wxListEvent,wxSpinEvent,wxSplitterEvent- ,wxTabEvent,wxTreeEvent,wxScrollEvent等等.还有就是wxCommandEvent本身.像大家常用的Menu,Button的Click事件都是属于这类,这些消息在View/Doc框架中应用非常广.这个也是和MFC一致的.比如Menu本身是属于MenuBar或者PopMenu等,但是事件处理可以直接在MainFrame/wxApp中.使用过MFC的View/Doc框架写过SDI,MDI程序的可能比较清楚这些.这样可以把这些事件都放到MVC模式中的Control中,mode/view可以专心负责自己的职责.比如view就负责菜单显示,而不用把事件处理代码也放在View中,否则显示和逻辑代码偶合非常高,这个不符合面向对象中隔离变化的宗旨的.还有像List,Tree,Grid,Scroll事件也是类似的.他们只属于显示窗口,对内部的数据操作不应该放到这些类中的. 总的来说,wxWdigets中的事件实际和MFC消息的处理机制是一样的.只不过一个是虚函数,一个是事件表而已.在view/doc程序中,事件处理流程惊人的相似.其实都是MVC模式的一个实现而已:-) 第六步: 既然是最后的防线,如果这里不处理的话,就没有地方处理了. wxWidget 小记(3) -- helloworld 宏展开 IMPLEMENT_APP(MyApp) 现在展开这些宏来分析内部的运行机制 1. 展开BEGIN_EVENT_TABLE(MyFrame, wxFrame) 2. 展开EVT_MENU(Minimal_Quit, MyFrame::OnQuit) 3. 展开END_EVENT_TABLE() 1. 在wxWidgets-2.6.2\include\wx\event.h文件中宏BEGIN_EVENT_TABLE被定义如下: #define BEGIN_EVENT_TABLE(theClass, baseClass) \ const wxEventTable theClass::sm_eventTable = { &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; \ const wxEventTable *theClass::GetEventTable() const \ { return &theClass::sm_eventTable; } \ wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable); \ wxEventHashTable &theClass::GetEventHashTable() const \ { return theClass::sm_eventHashTable; } \ const wxEventTableEntry theClass::sm_eventTableEntries[] = { \ 经过预编译处理后程序如下: const wxEventTable MyFrame::sm_eventTable = { &wxFrame::sm_eventTable, &MyFrame::sm_eventTableEntries[0] }; const wxEventTable *MyFrame::GetEventTable() const { return &MyFrame::sm_eventTable; } wxEventHashTable MyFrame::sm_eventHashTable(MyFrame::sm_eventTable); wxEventHashTable &MyFrame::GetEventHashTable() const { return MyFrame::sm_eventHashTable; } const wxEventTableEntry MyFrame::sm_eventTableEntries[] = { 2. 在wxWidgets-2.6.2\include\wx\event.h文件中宏EVT_MENU被定义如下: #define EVT_MENU(winid, func) wx__DECLARE_EVT1(wxEVT_COMMAND_MENU_SELECTED, winid, wxCommandEventHandler(func)) #define wx__DECLARE_EVT1(evt, id, fn) wx__DECLARE_EVT2(evt, id, wxID_ANY, fn) #define wx__DECLARE_EVT2(evt, id1, id2, fn) DECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL), #define DECLARE_EVENT_TABLE_ENTRY(type, winid, idLast, fn, obj) wxEventTableEntry(type, winid, idLast, fn, obj) 当中wxCommandEventHandler(func)进行静态强制类型转化,把指定的函数(func)指针类型转化为事件处理函数类型 #define wxCommandEventHandler(func) \ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(wxCommandEventFunction, &func) 注: typedef void (wxObject::*wxObjectEventFunction)(wxEvent&); typedef void (wxEvtHandler::*wxEventFunction)(wxEvent&); typedef void (wxEvtHandler::*wxCommandEventFunction)(wxCommandEvent&); #define wxStaticCastEvent(type, val) wx_static_cast(type, val) #define wx_static_cast(t, x) ((t)(x)) //进行强制类型转化 EVT_MENU(Minimal_Quit, MyFrame::OnQuit)经过预编译处理后程序如下: wxEventTableEntry(wxEVT_COMMAND_MENU_SELECTED, Minimal_Quit, wxI D_ANY, MyFrame::OnQuit, NULL) 3. 在wxWidgets-2.6.2\include\wx\event.h文件中宏BEGIN_EVENT_TABLE被定义如下: #define END_EVENT_TABLE() DECLARE_EVENT_TABLE_ENTRY( wxEVT_NULL, 0, 0, 0, 0 ) }; 再展开DECLARE_EVENT_TABLE_ENTRY: #define DECLARE_EVENT_TABLE_ENTRY(type, winid, idLast, fn, obj) \ wxEventTableEntry(type, winid, idLast, fn, obj) 经过预编译处理后程序如下: wxEventTableEntry(wxEVT_NULL, 0, 0, 0, 0)}; 综合上面所述, BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Minimal_Quit, MyFrame::OnQuit) EVT_MENU(Minimal_About, MyFrame::OnAbout) END_EVENT_TABLE() 最终展开的效果为: const wxEventTable MyFrame::sm_eventTable = { &wxFrame::sm_eventTable, &MyFrame::sm_eventTableEntries[0] }; const wxEventTable *MyFrame::GetEventTable() const { return &MyFrame::sm_ eventTable; } wxEventHashTable MyFrame::sm_eventHashTable(MyFrame::sm_eventTable); wxEventHashTable &MyFrame::GetEventHashTable() const { return MyFrame::s m_eventHashTable; } const wxEventTableEntry MyFrame::sm_eventTableEntries[] = { wxEventTableEntry(wxEVT_COMMAND_MENU_SELECTED, Minimal_Quit, wxID_ANY, MyFrame::OnQuit, NULL), wxEventTableEntry(wxEVT_COMMAND_MENU_SELECTED, Minimal_About, wxID_ANY, MyFrame::OnAbout, NULL), wxEventTableEntry(wxEVT_NULL, 0, 0, 0, 0) }; wxWidgets通过宏的方式在预编译时静态设置好了事件映射表, 把指定的一个唯一的命令 ID和MyFrame的指定函数静态绑定。实际上也可以进行动态绑定,这里先不描述。 IMPLEMENT_APP(MyApp)宏展开如下: IMPLEMENT_APP(appname) , IMPLEMENT_APP_NO_THEMES(appname) , IMPLEMENT_APP_NO_MAIN(appname) wxAppConsole *wxCreateApp() \ { \ wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGN ATURE, \ 'your program'); \ return new appname; \ } \ wxAppInitializer \ wxTheAppInitializer((wxAppInitializerFunction) wxCreateApp); \ appname& wxGetApp() { return *(appname *)wxTheApp; } , IMPLEMENT_WXWIN_MAIN extern 'C' int WINAPI WinMain(HINSTANCE hInstance, \ HINSTANCE hPrevInstance, \ wxCmdLineArgType lpCmdLine, \ int nCmdShow) \ { \ return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow); \ } , IMPLEMENT_WX_THEME_SUPPORT 最终的结果为: wxAppConsole *wxCreateApp() { wxAppConsole::CheckBuildOptions(WX_BUILD_OPTIONS_SIGNA TURE, 'your program'); return new MyApp; } wxAppInitializer wxTheAppInitializer((wxAppInitializerFunction) wxCreateApp); MyApp& wxGetApp() { return *( MyApp*)wxTheApp; } extern 'C' int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wxCmdLineArgType lpCmdLine, int nCmdShow) { return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } 可以看出这个就是win32应用程序主程序了。它通过wxEntry函数实现整个主函数的功能, 该函数与平台相关,可以自己调用本函数实现wxWidgets默认的入口函数(参考manual)。 该函数的实现在wxWidgets-2.6.2\src\msw\main.cpp。 程序继续执行,在函数int wxEntryReal(int& argc, wxChar **argv)内部主要完成三个事情: 1: 调用wxApp::Initialize初始化应用程序,初始化时候调用wxApp::RegisterWindowCl asses()注册一个窗口类,注册窗口类后, wxWidgets-2.6.2\src\msw\window.cpp文件下 的LRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWn d, UINT message, WPARAM wParam, LPARAM lParam)成为窗口事件处理主函 数,负责窗口事件处理。后面创建MyFrame时候最终会调用CreateWindow创建函 数主窗口。 2: 调用wxAppConsole::CallOnInit(),该函数最后调用bool MyApp::OnInit()虚拟函数初 始化应用程序,因此经过wxWidgets封装后,呈现给用户的最终的主程序入口就是用 户自己重载的OnInit()虚拟函数。在MyFrame构造函数调用wxTopLevelWindow::Cr eate()完成窗口创建。 3: 最后调用wxAppBase::OnRun()进行消息循环接收发送, 把属于窗口的消息发送给w xWndProc进行处理。 事件表和事件处理过程 wxWidgets事件处理系统比起通常的虚方法机制来说要稍微复杂一点,但它的一个好处是可以避免需要实现基类中所有的虚方法,因为实现所有的基类虚方法有时候是不切实际的或者是效率很低的。 每一个wxEvtHandler的派生类,例如frame,按钮,菜单以及文档等,都会在其内部维护一个事件表,用来告诉wxWidgets事件和事件处理过程的对应关系。所有继承自wxWindow的窗口类,以及应用程序类都是wxEvtHandler的派生类. 要创建一个静态的事件表(意味着它是在编译期间创建的),你需要下面几个步骤: 1. 定义一个直接或者间接继承自wxEvtHandler的类. 2. 为每一个你想要处理的事件定义一个处理函数。 3. 在这个类中使用DECLARE_EVENT_TABLE声明事件表。 4. 在.cpp文件中使用使用BEGIN_EVENT_TABLE和END_EVENT_TABLE实现一个事件表。 5. 在事件表的实现中增加事件宏,来实现从事件到事件处理过程的映射。 所有的事件处理函数拥有相同的形式。他们的返回值都是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函数就被调用了。类似的搜索过程不光发生在窗口控件的父子继承树中,也发生在普通的类继承关系中,这意味着你可以选择在哪里定义事件的处理函数。举例来说,如果你 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 了一个对话框,这个对话框需要响应的类似标识符为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,这些事件都不会传给事件源控件的父亲. 这些事件不会传递给其父亲,是因为这些事件仅对产生这个事件的窗口才有意义,举例来说,把一个子窗口 的重绘事件发送给它的父亲,其实是没有任何意义的。
本文档为【wxWidgets事件处理】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_471618
暂无简介~
格式:doc
大小:65KB
软件:Word
页数:0
分类:生活休闲
上传时间:2018-04-27
浏览量:14