wxWidgets源码分析-事件机制(下)

发布者: Tank 

上一部分分析了wxWidgets事件机制实现时一些基本概念,和所涉及到的数据结构。基于此继续讨论事件哈希表的建立,事件表相关宏处理的背后, 触发事件处理的方式,以及事件在不同平台的分发过程。

1、事件哈希表的实现

wxEventHashTable构造函数里面,并没有构建哈希表,而是用一个布尔变量标识哈希表尚未重建,采用这种延迟重建的方式来实现哈希表, 哈希表真正建立,发生在第一次查找事件表时,重建以后,设置布尔变量,以后只要对哈希表查找即可,从而加速了事件表的查找。 哈希函数采用除以size取余的方法。

以下是wxEventTable -> wxEventHashTable的过程

  1. 取当前事件表table
  2. table为空结束
  3. 遍历table中所有事件表条目,把事件表条目加入哈希表
  4. table = table->base,转1继续对父类事件表哈希
       
       
       
       
  1. void wxEventHashTable::InitHashTable()
  2. {
  3. // Loop over the event tables and all its base tables.
  4. const wxEventTable *table = &m_table;
  5. while (table)
  6. {
  7. // Retrieve all valid event handler entries
  8. const wxEventTableEntry *entry = table->entries;
  9. while (entry->m_fn != 0)
  10. {
  11. // Add the event entry in the Hash.
  12. AddEntry(*entry);
  13. entry++;
  14. }
  15. table = table->baseTable;
  16. }
  17. //....
  18. }
  19. void wxEventHashTable::AddEntry(const wxEventTableEntry &entry)
  20. {
  21. //..
  22. EventTypeTablePointer *peTTnode = &m_eventTypeTable[entry.m_eventType % m_size];
  23. EventTypeTablePointer eTTnode = *peTTnode;
  24. if (eTTnode)
  25. {
  26. if (eTTnode->eventType != entry.m_eventType)
  27. {
  28. // Resize the table!
  29. GrowEventTypeTable();
  30. // Try again to add it.
  31. AddEntry(entry);
  32. return;
  33. }
  34. }
  35. else
  36. {
  37. eTTnode = new EventTypeTable;
  38. eTTnode->eventType = entry.m_eventType;
  39. *peTTnode = eTTnode;
  40. }
  41. // Fill all hash entries between entry.m_id and entry.m_lastId...
  42. eTTnode->eventEntryTable.Add(&entry);
  43. }

对于上面的第3步,把事件条目加入哈希表,wxEventTableEntry -> wxEventHashTable的过程

  1. 由事件表表条中事件类型对哈希表长度取模得到i 
  2. 根据i的值,得到事件类型表地址数组m_eventTypeTable的下标 
  3. m_eventTypeTable[i] = 0,说明该事件类型事件类型表尚未填入哈希表,转4 ,否则转5 
  4. 新建一个事件类型表,将表地址填入哈希表,转6 
  5. 该事件类型表已经存在,冲突,扩容哈希表,重新回到1 
  6. 将事件表条目的地址填入事件类型表的eventEntryTable,结束 

2、事件表有关宏的背后

wxEvtHandler中有三个静态成员,sm_eventTable sm_eventHashTable分别代表当前事件处理类事件表和事件哈希表, 要被子类连接事件表所有为protected。所有要处理事件的类继承wxEvtHandler,需要重新定义这个两个静态成员。 因此两个虚函数分别返回当前类中事件表和事件哈希表。另一个私有的静态成员sm_eventTableEntries表示当前类的 事件条目数组,用于构成事件表。

       
       
       
       
  1. class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
  2. {
  3. //..
  4. private:
  5. static const wxEventTableEntry sm_eventTableEntries[];
  6. protected:
  7. static const wxEventTable sm_eventTable;
  8. virtual const wxEventTable *GetEventTable() const;
  9. static wxEventHashTable sm_eventTable;
  10. virtual wxEventHashTable& GetEventHashTable() const;
  11. //..
  12. }

在类的声明中使用DECLARE_EVENT_TABLE(),实际上市覆盖wxEvtHandler中的事件表和事件哈希表,并重写返回当前 类事件表和事件哈希表的虚函数

       
       
       
       
  1. #define DECLARE_EVENT_TABLE() \
  2. private: \
  3. static const wxEventTableEntry sm_eventTableEntries[]; \
  4. protected: \
  5. static const wxEventTable sm_eventTable; \
  6. virtual const wxEventTable* GetEventTable() const; \
  7. static wxEventHashTable sm_eventHashTable; \
  8. virtual wxEventHashTable& GetEventHashTable() const;

在类的实现中使用BEGIN_EVENT_TABLEEND_EVENT_TABLE()实际上初始化当前类的事件表(一个静态成员), 事件表父指针指向父类事件表,事件表的条目指针事件等于事件条目数组首地址。事件哈希表有事件表生成, 这里还没有构造,值是分配了31个为0的空间。中间的一堆事件映射宏实际上是用一堆5元组初始化当前类的事件条目表。

       
       
       
       
  1. #define BEGIN_EVENT_TABLE(theClass, baseClass) \
  2. const wxEventTable theClass::sm_eventTable = \
  3. { &baseClass::sm_eventTable, &theClass::sm_eventTableEntries[0] }; \
  4. const wxEventTable *theClass::GetEventTable() const \
  5. { return &theClass::sm_eventTable; } \
  6. wxEventHashTable theClass::sm_eventHashTable(theClass::sm_eventTable); \
  7. wxEventHashTable &theClass::GetEventHashTable() const \
  8. { return theClass::sm_eventHashTable; } \
  9. const wxEventTableEntry theClass::sm_eventTableEntries[] = { \
  10. EVT_MENU(Minimal_About, MyFrame::OnAbout)
  11. DECLARE_EVENT_TABLE_ENTRY(evt, id1, id2, fn, NULL),
  12. #define END_EVENT_TABLE() DECLARE_EVENT_TABLE_ENTRY( wxEVT_NULL, 0, 0, 0, 0 ) };

这样每个事件处理的类会用事件映射宏的5元组,构造事件条目表,事件条目表首地址, 放在事件表中,同时事件表会记录父类的事件表地址。哈希表初始化为一堆0地址,搜索一次后, 会把当前事件表和父类所有的事件表哈希到当前事件哈希表中,这样以后该类的对象, 事件表查找,只要查找已经构建好的事件哈希表, 事件复杂度近似O(1)。

3、引发处理事件的方式

上面已经分析过事件处理的流程,那么什么情况会触发一个事件被处理呢?有以下几种方式 * 窗口或控件感应到操作(来自用户的操作引发事件处理流程)  * 调用wxTheApp->ProcessPendingEvents() * 调用wxEvtHandler::ProcessEvent(wxEvent &event)(应用程序自己调用)

wx保存了一个全局的指针链表,里面保存当前应用程序所有事件处理类wxEvtHandler对象的指针

       
       
       
       
  1. /* code: common/event.cpp:144 */
  2. wxList *wxPendingEvents = (wxList *)NULL;

对于所有事件类的基类wxEvtHandler,有个成员m_pendingEvents保存当前wxEvtHandler对象的未决事件

       
       
       
       
  1. class WXDLLIMPEXP_BASE wxEvtHandler : public wxObject
  2. {
  3. //..
  4. wxList* m_pendingEvents;
  5. //..
  6. }

遍历全局链表未决事件链表wxPendingEvents,取出所有的wxEvtHandler,然后调用每个wxEvtHandler上的ProcessPendingEvents()方法

       
       
       
       
  1. /* code: src/common/Appbase.cpp:267 */
  2. void wxAppConsole::ProcessPendingEvents()
  3. {
  4. // ...
  5. // iterate until the list becomes empty
  6. wxList::compatibility_iterator node = wxPendingEvents->GetFirst();
  7. while (node)
  8. {
  9. wxEvtHandler *handler = (wxEvtHandler *)node->GetData();
  10. wxPendingEvents->Erase(node);
  11. // In ProcessPendingEvents(), new handlers might be add
  12. // and we can safely leave the critical section here.
  13. wxLEAVE_CRIT_SECT( *wxPendingEventsLocker );
  14. handler->ProcessPendingEvents();
  15. wxENTER_CRIT_SECT( *wxPendingEventsLocker );
  16. node = wxPendingEvents->GetFirst();
  17. }
  18. // ...
  19. }

wxEvtHandler上的ProcessPendingEvents()方法中wxEvtHandler遍历处理自己未决链表m_pendingEvents的事件

       
       
       
       
  1. void wxEvtHandler::ProcessPendingEvents()
  2. {
  3. //..
  4. size_t n = m_pendingEvents->size();
  5. for ( wxList::compatibility_iterator node = m_pendingEvents->GetFirst();
  6. node;
  7. node = m_pendingEvents->GetFirst() )
  8. {
  9. wxEventPtr event(wx_static_cast(wxEvent *, node->GetData()));
  10. // It's important we remove event from list before processing it.
  11. // Else a nested event loop, for example from a modal dialog, might
  12. // process the same event again.
  13. m_pendingEvents->Erase(node);
  14. wxLEAVE_CRIT_SECT( Lock() );
  15. ProcessEvent(*event);
  16. wxENTER_CRIT_SECT( Lock() );
  17. if ( --n == 0 )
  18. break;
  19. }
  20. //..
  21. }

wxEvtHandler提供了向自己接未决链表加入事件的方法,wxEvtHandler:AddPendingEvent, 注意这里只是把event clone一份,然后加入未决链表, 并不处理就返回。

       
       
       
       
  1. void wxEvtHandler:AddPendingEvent(wxEvent& event)

wxEvtHandler::ProcessEvent用于立即处理这个事件。

       
       
       
       
  1. bool wxEvtHandler::ProcessEvent(wxEvent& event)

4、消息分发机制

上述所有wxEvent相关的东西都是跨平台的,wxEvent是wx定义出来的一个wx事件类, wx程序中处理的都是wxEvent,并不设计到具体平台上的消息处理。然后实际上, 对于GUI程序设计每个平台都有消息循环、消息分发机制,wx为了跨平台提供了一个抽象层, 屏蔽了平台相关的消息处理部分。在include/wx下的头文件提供了wx对外公共的接口, 利用里面的接口我们可以编写平台无关的GUI的程序,然而include/wx中头文件接口的实现却依赖于具体的平台, 实际上上平台相关的接口位于/include/wx/gtk /include/wx/msw等,但用户不必关心这里平台相关的细节, 只要利用提供的公共接口编程即可。下面分析wx中消息分发以及在不同平台的实现。

wx程序启动流程如下: * 建立个wxApp子类MyApp的实例 * 调用MyApp中重写的虚函数wxApp::OnInit完成初始化(主要是创建顶层窗口),返回false结束 * 调用AppBase::OnRun->wxAppBase::MainLoop进入消息循环

       
       
       
       
  1. /* src/common/appcmn.cpp::357 */
  2. int wxAppBase::OnRun()
  3. {
  4. // see the comment in ctor: if the initial value hasn't been changed, use
  5. // the default Yes from now on
  6. if ( m_exitOnFrameDelete == Later )
  7. {
  8. m_exitOnFrameDelete = Yes;
  9. }
  10. //else: it has been changed, assume the user knows what he is doing
  11. return MainLoop();
  12. }
  13. // .....
  14. int wxAppBase::MainLoop()
  15. {
  16. wxEventLoopTiedPtr mainLoop(&m_mainLoop, new wxEventLoop);
  17. return m_mainLoop->Run();
  18. }
  19. /* inclucde/wx/app.h:329 */
  20. class WXDLLIMPEXP_CORE wxAppBase : public wxAppConsole
  21. {
  22. //..
  23. wxEventLoop *m_mainLoop;
  24. //..
  25. }

m_mainLoop是个事件循环类wxEventLoop的对象指针,wxEventLoop是个平台相关的事件循环类,从此进入不同平台的消息循环

5、MSW版本中消息分发机制

      
      
      
      
  1. WinMain -> wxEntry -> wxEntryReal -> wxAppBase::OnRun -> wxAppBase::MainLoop ->
  2. wxEventLoopManual::Run -> wxEventLoop::Dispatch -> wxEventLoop::ProcessMessage
       
       
       
       
  1. /* src/common.Evtloopcmn.cpp:65 */
  2. int wxEventLoopManual::Run()
  3. {
  4. while ( !Pending() && (wxTheApp && wxTheApp->ProcessIdle()) )
  5. ;
  6. if ( m_shouldExit )
  7. {
  8. while ( Pending() )
  9. Dispatch();
  10. break;
  11. }
  12. }

程序进入一个无限的循环,如果没消息处理,就处理idle消息,有消息就分发消息

       
       
       
       
  1. bool wxEventLoop::Dispatch()
  2. {
  3. // ..
  4. MSG msg;
  5. BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0);
  6. ProcessMessage(&msg);
  7. //..
  8. }

取消息处理消息,这里的消息是WXMSG *msg typedef struct tagMSG WXMSG; 在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下(参见MSDN):

       
       
       
       
  1. typedef struct tagMSG { // msg
  2. HWND hwnd; //标识窗口过程接收消息的窗口
  3. UINT message; //指定消息号
  4. WPARAM wParam; //指定有关消息的附加信息。 确切含义取决于 message 成员的值
  5. LPARAM lParam; //指定有关消息的附加信息。 确切含义取决于 message 成员的值。
  6. DWORD time; //指定消息已传递的时间
  7. POINT pt; //当消息已传递了,指定光标位置,在屏幕坐标
  8. } MSG;

这里的消息就是windows中的消息了

       
       
       
       
  1. void wxEventLoop::ProcessMessage(WXMSG *msg)
  2. {
  3. // give us the chance to preprocess the message first
  4. if ( !PreProcessMessage(msg) )
  5. {
  6. // if it wasn't done, dispatch it to the corresponding window
  7. ::TranslateMessage(msg);
  8. ::DispatchMessage(msg);
  9. }
  10. }

这里就是win32 SDK中的消息循环了,说白了wx中的事件处理,最终还是用了平台上的消息机制, wx只是完成了跨平台的封装,对用户屏蔽了平台相关的是实现细节,抽象出一个用户直接利用的抽象层。

  • windows中的消息WXMSG怎么转换成wxEvent*

    wxEntryReal -> wxEntryStart -> wxApp::Initialize() -> wxApp:: RegisterWindowClasses()

注册一个窗口类,这个窗口类绑定了窗口处理过程

       
       
       
       
  1. /* src/msw/app.cpp */
  2. bool wxApp::RegisterWindowClasses()
  3. {
  4. WNDCLASS wndclass;
  5. wxZeroMemory(wndclass);
  6. // for each class we register one with CS_(V|H)REDRAW style and one
  7. // without for windows created with wxNO_FULL_REDRAW_ON_REPAINT flag
  8. static const long styleNormal = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
  9. static const long styleNoRedraw = CS_DBLCLKS;
  10. // the fields which are common to all classes
  11. wndclass.lpfnWndProc = (WNDPROC)wxWndProc;
  12. wndclass.hInstance = wxhInstance;
  13. wndclass.hCursor = ::LoadCursor((HINSTANCE)NULL, IDC_ARROW);
  14. // register the class for all normal windows
  15. wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
  16. wndclass.lpszClassName = wxCanvasClassName;
  17. wndclass.style = styleNormal;
  18. if ( !RegisterClass(&wndclass) )
  19. {
  20. wxLogLastError(wxT("RegisterClass(frame)"));
  21. }
  22. //..
  23. }

然后里创建窗口

       
       
       
       
  1. /* src/msw/window.cpp */
  2. bool wxWindowMSW::MSWCreate(const wxChar *wclass,
  3. const wxChar *title,
  4. const wxPoint& pos,
  5. const wxSize& size,
  6. WXDWORD style,
  7. WXDWORD extendedStyle)
  8. {
  9. wxString className(wclass);
  10. if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE) )
  11. {
  12. className += wxT("NR");
  13. }
  14. // do create the window
  15. wxWindowCreationHook hook(this);
  16. m_hWnd = (WXHWND)::CreateWindowEx
  17. (
  18. extendedStyle,
  19. className,
  20. title ? title : m_windowName.c_str(),
  21. style,
  22. x, y, w, h,
  23. (HWND)MSWGetParent(),
  24. (HMENU)controlId,
  25. wxGetInstance(),
  26. NULL // no extra data
  27. );
  28. if ( !m_hWnd )
  29. {
  30. wxLogSysError(_("Can't create window of class %s"), className.c_str());
  31. return false;
  32. }
  33. SubclassWin(m_hWnd);
  34. }

其中,void wxWindowMSW::SubclassWin(WXHWND hWnd)用于设置新窗口的窗口处理过程

       
       
       
       
  1. WXLRESULT wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
  2. {
  3. //...
  4. switch ( message )
  5. {
  6. case WM_CREATE:
  7. case WM_LBUTTONDOWN:
  8. case WM_LBUTTONUP:
  9. case WM_LBUTTONDBLCLK:
  10. case WM_RBUTTONDOWN:
  11. case WM_RBUTTONUP:
  12. case WM_RBUTTONDBLCLK:
  13. case WM_MBUTTONDOWN:
  14. case WM_MBUTTONUP:
  15. case WM_MBUTTONDBLCLK:
  16. }

可见MSW 版本中的事件wxEvent由windows中消息WN_XX而来

6、GTK版本中消息分发机制

程序启动后,GTK版本进入的gtk的wxEventLoop事件循环类,GTK是一种事件驱动工具包,这意味着它将在gtk_main函数 中一直等待,直到事件发生和控制权被传递给相应的函数。gtk_main()是在每个GTK应用程序都要调用的函数。 当程序运行到这里时, Gtk将进入等待态,等候X事件(比如点击按钮或按下键盘的某个按键)、Timeout 或文件输入/输出发生。

       
       
       
       
  1. /* src/gtk/evtloop.cpp */
  2. int wxEventLoop::Run()
  3. {
  4. // event loops are not recursive, you need to create another loop!
  5. wxCHECK_MSG( !IsRunning(), -1, _T("can't reenter a message loop") );
  6. wxEventLoopActivator activate(this);
  7. m_impl = new wxEventLoopImpl;
  8. gtk_main();
  9. OnExit();
  10. int exitcode = m_impl->GetExitCode();
  11. delete m_impl;
  12. m_impl = NULL;
  13. return exitcode;
  14. }

你可能感兴趣的:(wxWidgets源码分析-事件机制(下))