本文共 13494 字,大约阅读时间需要 44 分钟。
Windows程序有其自身运行的一套规律,::SendMessage
是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSendMessage
接口调用用户的消息处理函数。
gapfnScSendMessage
调用进入的。 wxWidgets注册窗口时同时指定了窗口处理函数wxWndProc
,当收到消息后系统会调用此函数来处理消息。
处理过程如下:
wxFindWinFromHandle
根据当前消息指定的HWND来查找对应的wxWindow,如果没有则需要与最近创建的一个窗口关联起来。MSWWindowProc
方法来进行消息处理。// Main window procLRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){ wxWindowMSW *wnd = wxFindWinFromHandle(hWnd); // 关联窗口 if ( !wnd && gs_winBeingCreated ) { wxAssociateWinWithHandle(hWnd, gs_winBeingCreated); wnd = gs_winBeingCreated; gs_winBeingCreated = NULL; wnd->SetHWND((WXHWND)hWnd); } LRESULT rc; if ( wnd && wxGUIEventLoop::AllowProcessing(wnd) ) rc = wnd->MSWWindowProc(message, wParam, lParam); else rc = ::DefWindowProc(hWnd, message, wParam, lParam); return rc;}
MSWWindowProc
是在windows平台下特有的虚函数,对于Frame类来说就是wxFrame::MSWWindowProc
,消息根据Message类型来执行不同的函数:
wxFrame::Close
;wxFrame::HandleSize
;wxFrameBase::MSWWindowProc
处理;WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam){ WXLRESULT rc = 0; bool processed = false; switch ( message ) { case WM_CLOSE: processed = !Close(); break; case WM_SIZE: processed = HandleSize(LOWORD(lParam), HIWORD(lParam), wParam); break; case WM_COMMAND: { WORD id, cmd; WXHWND hwnd; UnpackCommand((WXWPARAM)wParam, (WXLPARAM)lParam, &id, &hwnd, &cmd); HandleCommand(id, cmd, (WXHWND)hwnd); processed = true; } break; } if ( !processed ) rc = wxFrameBase::MSWWindowProc(message, wParam, lParam); return rc;}
拿wxFrame::Close
函数举例,wxFrame
直接使用父类的方法wxWindowBase::Close
,内容就是构造wxWidgets的消息类型,然后调用HandleWindowEvent
方法进行消息处理。
bool wxWindowBase::Close(bool force){ wxCloseEvent event(wxEVT_CLOSE_WINDOW, m_windowId); event.SetEventObject(this); event.SetCanVeto(!force); // return false if window wasn't closed because the application vetoed the // close event return HandleWindowEvent(event) && !event.GetVeto();}
对于WM_SIZE
,走的路要多一些,调用关系如下,最终由wxWindowMSW::HandleSize
处理,这里会产生wxSizeEvent
消息,随后将消息递交给HandleWindowEvent
处理:
wxFrame::MSWWindowProc() -> wxTopLevelWindowMSW::MSWWindowProc() -> wxWindowMSW::MSWWindowProc() -> wxWindowMSW::MSWHandleMessage() -> wxWindowMSW::HandleSize()bool wxWindowMSW::HandleSize(int WXUNUSED(w), int WXUNUSED(h), WXUINT wParam){ switch ( wParam ) { case SIZE_RESTORED: wxSizeEvent event(GetSize(), m_windowId); event.SetEventObject(this); processed = HandleWindowEvent(event); }}
也就是不管是哪个消息,最终还是转换称wxEvent消息,然后调用当前窗口的HandleWindowEvent
函数处理,要注意的是此时消息已经是wxWidgets内部的消息类型了。
接下来我们验证菜单消息在wxWidgets中的处理,首先在wxWidgets工程中增加静态消息映射表,并实现相应的代码:
const long ID_MenuUser = wxNewId();BEGIN_EVENT_TABLE(debugWXFrame,wxFrame) EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu) EVT_UPDATE_UI(ID_MenuUser, debugWXFrame::OnCheckMenuUI)END_EVENT_TABLE()void debugWXFrame::OnCheckMenu(wxCommandEvent& event) {}void debugWXFrame::OnCheckMenuUI(wxUpdateUIEvent& event) {}
我们接着看下Command消息的处理,这个用的非常多,收到Command类型的消息后调用HandleCommand
处理,Frame类只处理工具栏、菜单和加速键命令,实现过程:
FindItemInMenuBar
根据消息中指定的ID来查找对应的wxMenuItem
,这个函数的实现就是获取到当前wxFrame
的MenuBar,然后循环查询,菜单越多查询的速度也就越慢;wxFrameBase::ProcessCommand(mitem)
继续处理:bool wxFrame::HandleCommand(WXWORD id, WXWORD cmd, WXHWND control){#if wxUSE_MENUS#if defined(WINCE_WITHOUT_COMMANDBAR) if (GetToolBar() && GetToolBar()->FindById(id)) return GetToolBar()->MSWCommand(cmd, id);#endif // we only need to handle the menu and accelerator commands from the items // of our menu bar, base wxWindow class already handles the rest if ( !control && (cmd == 0 /* menu */ || cmd == 1 /* accel */) ) {#if wxUSE_MENUS_NATIVE if ( !wxCurrentPopupMenu )#endif // wxUSE_MENUS_NATIVE { wxMenuItem * const mitem = FindItemInMenuBar((signed short)id); if ( mitem ) return ProcessCommand(mitem); } }#endif // wxUSE_MENUS return wxFrameBase::HandleCommand(id, cmd, control);;}
wxFrame
类本身没有实现ProcessCommand
,所以将调用父类的方法wxFrameBase::ProcessCommand
,关键流程代码部分调用wxMenu的SendEvent
函数继续处理。
menu->SendEvent
,所以接下来的调用切换到wxMenu类中进行。 bool wxFrameBase::ProcessCommand(wxMenuItem *item){ ... wxMenu* const menu = item->GetMenu(); return menu->SendEvent(item->GetId(), checked);}
wxMenu的SendEvent
实现是wxMenuBase::SendEvent
方法,此时我们位于wxMenu对象中,所以调用GetEventHandler()
获得的是wxMenu的EvntHandler。
event.SetWillBeProcessedAgain()
,也就是命令需要被wen或者menubar处理。 win和mb两个变量代表不同的菜单类型,mb是菜单条中的菜单,win是上下文菜单。
这里我们调用的是mb->HandleWindowEvent(event)
; bool wxMenuBase::SendEvent(int itemid, int checked){ wxCommandEvent event(wxEVT_MENU, itemid); event.SetEventObject(this); event.SetInt(checked); wxWindow* const win = GetWindow(); wxMenuBar* const mb = GetMenuBar(); wxEvtHandler *handler = GetEventHandler(); if ( handler ) { if ( win || mb ) event.SetWillBeProcessedAgain(); // 没有想到调用这个函数的场景? if ( handler->SafelyProcessEvent(event) ) return true; } // If this menu is part of the menu bar, process the event there: this will // also propagate it upwards to the window containing the menu bar. if ( mb ) return mb->HandleWindowEvent(event); // Try the window the menu was popped up from. if ( win ) return win->HandleWindowEvent(event); // Not processed. return false;}
至此,我们将切换到wxMenuBar::HandleWindowEvent
,所有者为wxMenuBar
,wxMenuBar
继承自wxWindow
类,它也是一个独立的窗口,所以这次调用的函数是wxWindowBase::HandleWindowEvent
,调用过程如下:
GetEventHandler()
方法返回的就是自身,wxFrame
本身就是继承自wxEvtHandler
;ProcessEvent
方法是由父类wxEvtHandler
提供的;bool wxWindowBase::HandleWindowEvent(wxEvent& event) const{ return GetEventHandler()->SafelyProcessEvent(event);}bool wxEvtHandler::SafelyProcessEvent(wxEvent& event){ return ProcessEvent(event);}
接着调用ProcessEvent
,这个函数是通用的,只要是继承自wxEvtHandler
都会调用到这里,下面我们分两种情况来说明情况:
通用的处理:
wxEvtHandler
内部创建了静态变量ms_filterList
,用于保存wxEventFilter
列表,用户可以通过调用静态函数wxEvtHandler::AddFilter
来向系统中增加过滤器,具体可参考过滤器使用
章节;TryBeforeAndHere
仅对本对象处理,调用此函数需要依赖一个标记ShouldProcessOnlyIn
,这个标记仅仅在DoTryChain
中会被设置,也就是只有进入了DoTryChain
函数才会有此标记;ProcessEventLocally
执行本对象处理;TryAfter
执行parent的处理;第一种情况: 位于wxMenuBar
中的处理:
此时我们位于
wxMenuBar
中,此类继承自wxEvtHandler
,所以这里调用的实际是wxEvtHandler::ProcessEvent
,处理过程:
- 此时是不会有
ShouldProcessOnlyIn
标记,所以不会执行TryBeforeAndHere
;- 进入到
ProcessEventLocally
;由于wxMenuBar
对象中并没有绑定此菜单的处理函数,所以ProcessEventLocally
是不会处理的;- 进入到
TryAfter
执行parent的处理;
第二种情况: 位于wxFrame
中的处理:
对于wxFrame的
ProcessEvent
流程也有同样的效果,只不过会在ProcessEventLocally
中处理。
bool wxEvtHandler::ProcessEvent(wxEvent& event){ // 处理过滤器 if ( !event.WasProcessed() ) { for ( wxEventFilter* f = ms_filterList; f; f = f->m_next ) { int rc = f->FilterEvent(event); if ( rc != wxEventFilter::Event_Skip ) { return rc != wxEventFilter::Event_Ignore; } } } // 只有执行了 DoTryChain() 之后,ShouldProcessOnlyIn()方法才会返回true // 具体可以参考 wxEventProcessInHandlerOnly 辅助类 if ( event.ShouldProcessOnlyIn(this) ) return TryBeforeAndHere(event); // Try to process the event in this handler itself. if ( ProcessEventLocally(event) ) { return !event.GetSkipped(); } if ( TryAfter(event) ) return true; // No handler found anywhere, bail out. return false;}
还是分成两种情况分别说明:
第一种情况: 位于wxMenuBar
中的处理:
wxEvtHandler::TryBeforeAndHere
会调用TryBefore
||TryHereOnly
,TryBefore
我们暂时忽略,重点是TryHereOnly
,在TryHereOnly
函数中,首先超找动态绑定表,然后查找静态绑定表,如果表中存在处理函数则调用之,否则不会调用,对于本流程来将,wxMenubar
中并没有绑定任何处理函数,所以TryHereOnly
返回false,进而TryBeforeAndHere
函数返回false,所以需要继续调用DoTryChain
。
第二种情况: 位于wxFrame
中的处理:
对于wxFrame来说,本例中菜单的消息处理函数绑定在静态绑定区,所以会在
if ( GetEventHashTable().HandleEvent(event, this) )
中处理掉,返回true。
bool wxEvtHandler::ProcessEventLocally(wxEvent& event){ return TryBeforeAndHere(event) || DoTryChain(event);}bool wxEvtHandler::TryBeforeAndHere(wxEvent& event){ return TryBefore(event) || TryHereOnly(event);}bool wxEvtHandler::TryHereOnly(wxEvent& event){ // Handle per-instance dynamic event tables first if ( m_dynamicEvents && SearchDynamicEventTable(event) ) return true; // Then static per-class event tables if ( GetEventHashTable().HandleEvent(event, this) ) return true; return false;}
继续进入入wxMenuBar
的DoTryChain
,这里是通过设置EventHandlerChain来达到消息传递的目的,但是在wxWidgets系统中,wxWindow
继承的过程中明确不使用这种方式进行消息传递,而是通过wxWindow
自身的父子关系来进行消息传递,所以对于wxMenuBar
来说,这个GetNextHandler
必定返回是空的,所以DoTryChain
返回false,进而wxMenuBar
的ProcessEventLocally
返回false。
bool wxEvtHandler::DoTryChain(wxEvent& event){ for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() ) { wxEventProcessInHandlerOnly processInHandlerOnly(event, h); if ( h->ProcessEvent(event) ) { event.Skip(false); return true; } if ( !event.ShouldProcessOnlyIn(h) ) { event.Skip(); return true; } } return false;}
再回到前两步,此时我们只能通过调用wxMenuBar
的TryAfter(event)
继续消息传递,前文有描述wxMenuBar
继承自wxWindows
,所以这里调用的是wxWindowBase::TryAfter
,在下面的调用中,窗口只要可能正常接收消息,则会向上查找parent,然后调用父类的ProcessEvent
继续处理。
在本例中,wxMenuBar
的parent是wxFrame
,所以会继续调用wxFrame
的ProcessEvent
继续处理
bool wxWindowBase::TryAfter(wxEvent& event){ if ( event.ShouldPropagate() ) { if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) ) { wxWindow *parent = GetParent(); if ( parent && !parent->IsBeingDeleted() ) { wxPropagateOnce propagateOnce(event, this); return parent->GetEventHandler()->ProcessEvent(event); } } } return wxEvtHandler::TryAfter(event);}
wxFrame
的ProcessEvent
的调用顺序与wxMenuBar
的相同,只不过wxFrame
会在ProcessEventLocally
方法中返回true,进而导致整个处理流程完成。
wxWidgets提供了一种手段,用户可以将消息处理函数注入到wxEvtHandler类中,而不需要使用继承方式,实现方法就是用户自定义一个wxEventHandler类,然后调用wxEvtHandler::SetNextHandler()
将消息处理代码加入到指定的wxEvtHandler
对象上,使用举例:
下面的代码用于将菜单处理放到独立的wxEvtHandler
类中,通过wxEvtHandler::SetNextHandler
方法将此Handler对象链接到wxFrame
上:
wxEvtHandler::SetNextHandler
方法注入,不能直接调用SetNextHandler
,因为wxWindow
重载了这个方法,直接调用不会生效。 const long ID_MenuUser = wxNewId();class CMyEvtHandler : public wxEvtHandler {public: bool ProcessEvent(wxEvent& event) { if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser) { wxMessageBox("Menu processed in chain"); return true; } event.Skip(); return false; }};debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id){ ... Menu1->Append(ID_MenuUser, _("Chain Menu")); wxEvtHandler::SetNextHandler(new CMyEvtHandler);}
实际调用时,会进入到wxFram::DoTryChain
函数中,由于我们向wxFrame中增加了wxEvtHandler
,此时会取出关系链上的wxEvtHandler
逐个调用。
注意到使用这种方式调用时会预先设定只在当前对象中处理标记,通过wxEventProcessInHandlerOnly processInHandlerOnly(event, h);
实现,当processInHandlerOnly
销毁后标记消失,作用范围仅仅是在这个循环体内。
bool wxEvtHandler::DoTryChain(wxEvent& event){ for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() ) { wxEventProcessInHandlerOnly processInHandlerOnly(event, h); if ( h->ProcessEvent(event) ) { event.Skip(false); return true; } if ( !event.ShouldProcessOnlyIn(h) ) { event.Skip(); return true; } } return false;}
除了将消息处理类注入到当前wxEvtHandler
对象中,还有一个办法就是调用wxWindow::PushEventHandler
将消息处理类注入到当前windows的栈中,两种方式有区别:
wxEvtHandler
,wxWidgets会优先处理当前对象的wxEvtHandler
,然后检查当前对象的wxEvtHandler
是否有链接其他的wxEvtHandler
,如果有则调用之;wxWindow::PushEventHandler
注入的是改写wxWindow类的当前消息处理对象,当其查找wxWindow对象的消息处理对象时,只调用最后插入的一个,所以,为了保证正常的消息能处理,我们必须在ProcessEvent()
方法中调用下一个wxEvtHandler的方法。举例:
const long ID_MenuUser_wxWinChain = wxNewId();class CMyWinEvtHandler : public wxEvtHandler {public: bool ProcessEvent(wxEvent& event) { if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser_wxWinChain) { wxMessageBox("Menu processed in chain, id="+wxString::Format("%d", event.GetId())); return true; } if (GetNextHandler()) return GetNextHandler()->ProcessEvent(event); return false; }};debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id){ ... Menu1->Append(ID_MenuUser_wxWinChain, _("Chain Menu")); PushEventHandler(new CMyWinEvtHandler);}
拿菜单处理举例,在wxMenuBar
调用wxWindowBase::TryAfter
查找父类调用时,会直接调用父类的方法,对于我们这个例子来说,会直接调用CMyWinEvtHandler::ProcessEvent
方法,所以我们在实现ProcessEvent
必须注意,需要调用GetNextHandler()->ProcessEvent(event)
以保证其他消息的正常处理。
if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) ){ wxWindow *parent = GetParent(); if ( parent && !parent->IsBeingDeleted() ) { wxPropagateOnce propagateOnce(event, this); return parent->GetEventHandler()->ProcessEvent(event); }}
注:在测试过程中发现总会有the last handler of the wxWindow stack should have this window as next handler
的提示,这个是wxWidgets库本身代码的Bug,窗口链不需要双向链表,窗口本身的wxEvtHandler
不需要指向任何wxEvtHandler
,因为它就是最后一个。
本节中主要描述了wxWidgets的消息处理过程。
转载地址:http://poyfz.baihongyu.com/