博客
关于我
wxWidgets源码分析(4) - 消息处理过程
阅读量:434 次
发布时间:2019-03-06

本文共 13494 字,大约阅读时间需要 44 分钟。

目录

消息处理过程

消息如何到达wxWidgets

Windows程序有其自身运行的一套规律,::SendMessage是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSendMessage接口调用用户的消息处理函数。

所以我们每次看到消息处理函数都是通过gapfnScSendMessage调用进入的。

Win32消息与wxWidgets消息的转换

wxWidgets注册窗口时同时指定了窗口处理函数wxWndProc,当收到消息后系统会调用此函数来处理消息。

处理过程如下:

  1. 调用wxFindWinFromHandle根据当前消息指定的HWND来查找对应的wxWindow,如果没有则需要与最近创建的一个窗口关联起来。
  2. 接着调用窗口的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类型来执行不同的函数:

  1. WM_CLOSE: 退出操作,直接调用wxFrame::Close;
  2. WM_SIZE: 调用wxFrame::HandleSize;
  3. WM_COMMAND: 需要特殊处理,注释中写的很清楚,wxWidgets提供了一套自己的机制来进行父窗口和子窗口之间的消息调用,所以就不要再使用Win32提供的功能了。
  4. 如果自己没有处理,则给父类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类只处理工具栏、菜单和加速键命令,实现过程:

  1. 调用FindItemInMenuBar根据消息中指定的ID来查找对应的wxMenuItem,这个函数的实现就是获取到当前wxFrame的MenuBar,然后循环查询,菜单越多查询的速度也就越慢;
  2. 找到MenuItem后则调用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。

重点关注win和mb两个变量,wxMenu首先使用自己的wxEvtHandler进行处理,然后检查它是否关联到了win或者menubar,如果有则它还增加了一个标记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,所有者为wxMenuBarwxMenuBar继承自wxWindow类,它也是一个独立的窗口,所以这次调用的函数是wxWindowBase::HandleWindowEvent,调用过程如下:

  1. GetEventHandler()方法返回的就是自身,wxFrame本身就是继承自wxEvtHandler
  2. ProcessEvent方法是由父类wxEvtHandler提供的;
bool wxWindowBase::HandleWindowEvent(wxEvent& event) const{    return GetEventHandler()->SafelyProcessEvent(event);}bool wxEvtHandler::SafelyProcessEvent(wxEvent& event){    return ProcessEvent(event);}

接着调用ProcessEvent,这个函数是通用的,只要是继承自wxEvtHandler都会调用到这里,下面我们分两种情况来说明情况:

通用的处理:

  1. 处理全局过滤器,wxEvtHandler内部创建了静态变量ms_filterList,用于保存wxEventFilter列表,用户可以通过调用静态函数wxEvtHandler::AddFilter来向系统中增加过滤器,具体可参考过滤器使用章节;
  2. 调用TryBeforeAndHere仅对本对象处理,调用此函数需要依赖一个标记ShouldProcessOnlyIn,这个标记仅仅在DoTryChain中会被设置,也就是只有进入了DoTryChain函数才会有此标记;
  3. 调用ProcessEventLocally执行本对象处理;
  4. 调用TryAfter执行parent的处理;

第一种情况: 位于wxMenuBar中的处理:

此时我们位于wxMenuBar中,此类继承自wxEvtHandler,所以这里调用的实际是wxEvtHandler::ProcessEvent,处理过程:

  1. 此时是不会有ShouldProcessOnlyIn标记,所以不会执行TryBeforeAndHere
  2. 进入到ProcessEventLocally;由于wxMenuBar对象中并没有绑定此菜单的处理函数,所以ProcessEventLocally是不会处理的;
  3. 进入到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||TryHereOnlyTryBefore我们暂时忽略,重点是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;}

继续进入入wxMenuBarDoTryChain,这里是通过设置EventHandlerChain来达到消息传递的目的,但是在wxWidgets系统中,wxWindow继承的过程中明确不使用这种方式进行消息传递,而是通过wxWindow自身的父子关系来进行消息传递,所以对于wxMenuBar来说,这个GetNextHandler必定返回是空的,所以DoTryChain返回false,进而wxMenuBarProcessEventLocally返回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;}

再回到前两步,此时我们只能通过调用wxMenuBarTryAfter(event)继续消息传递,前文有描述wxMenuBar继承自wxWindows,所以这里调用的是wxWindowBase::TryAfter,在下面的调用中,窗口只要可能正常接收消息,则会向上查找parent,然后调用父类的ProcessEvent继续处理。

在本例中,wxMenuBar的parent是wxFrame,所以会继续调用wxFrameProcessEvent继续处理

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);}

wxFrameProcessEvent的调用顺序与wxMenuBar的相同,只不过wxFrame会在ProcessEventLocally方法中返回true,进而导致整个处理流程完成。

消息处理链(基于wxEvtHandler)

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;}

消息处理链(基于wxWindow)

除了将消息处理类注入到当前wxEvtHandler对象中,还有一个办法就是调用wxWindow::PushEventHandler将消息处理类注入到当前windows的栈中,两种方式有区别:

  1. 注入到wxEvtHandler,wxWidgets会优先处理当前对象的wxEvtHandler,然后检查当前对象的wxEvtHandler是否有链接其他的wxEvtHandler,如果有则调用之;
  2. 通过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/

你可能感兴趣的文章
查看已经开放的端口,查看和清理tomcat日志文件
查看>>
ORA-00600: internal error code, arguments: [kole_t2u], [34]
查看>>
应用人员反馈报错,ORA-03137: TTC protocol internal error : [12333]
查看>>
数据泵使用NETWORK_LINK不落地导入数据
查看>>
实验之-----------修改oracle实例名
查看>>
Oracle text组件安装
查看>>
在不影响程序使用的情况下添加shellcode
查看>>
刷LeetCode的简易姿势
查看>>
test!
查看>>
JavaScript 构造树形结构的一种高效算法
查看>>
通过Attached Property给控件绑定Command(二)
查看>>
Linq使用心得——SelectMany替代二重foreach循环
查看>>
UWP开发入门(二)——RelativePanel
查看>>
UWP开发入门(三)——{x:Bind}扩展标记
查看>>
微信小程序开发技巧总结 (一)-- 数据传递和存储
查看>>
dock基本使用
查看>>
ASP查询数据RS转换成COMMAND
查看>>
getDomain(url)-我的JavaScript函数库-mazey.js
查看>>
CSS:text-decoration参数说明
查看>>
ES6通过Set数组去重
查看>>