消息处理过程

消息如何到达wxWidgets

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

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

Win32消息与wxWidgets消息的转换

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

处理过程如下:

  1. 调用wxFindWinFromHandle根据当前消息指定的HWND来查找对应的wxWindow,如果没有则需要与最近创建的一个窗口关联起来。
  2. 接着调用窗口的MSWWindowProc方法来进行消息处理。
// Main window proc
LRESULT 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的消息处理过程。

wxWidgets源码分析(4) - 消息处理过程的更多相关文章

  1. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  3. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  4. 源码分析HotSpot GC过程(一)

    «上一篇:源码分析HotSpot GC过程(一)»下一篇:源码分析HotSpot GC过程(三):TenuredGeneration的GC过程 https://blogs.msdn.microsoft ...

  5. 源码分析HotSpot GC过程(三):TenuredGeneration的GC过程

    老年代TenuredGeneration所使用的垃圾回收算法是标记-压缩-清理算法.在回收阶段,将标记对象越过堆的空闲区移动到堆的另一端,所有被移动的对象的引用也会被更新指向新的位置.看起来像是把杂陈 ...

  6. 深入理解 spring 容器,源码分析加载过程

    Spring框架提供了构建Web应用程序的全功能MVC模块,叫Spring MVC,通过Spring Core+Spring MVC即可搭建一套稳定的Java Web项目.本文通过Spring MVC ...

  7. wifidog源码分析 - 用户连接过程

    引言 之前的文章已经描述wifidog大概的一个工作流程,这里我们具体说说wifidog是怎么把一个新用户重定向到认证服务器中的,它又是怎么对一个已认证的用户实行放行操作的.我们已经知道wifidog ...

  8. SOFA 源码分析 —— 服务发布过程

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

  9. 深入源码分析SpringMVC执行过程

    本文主要讲解 SpringMVC 执行过程,并针对相关源码进行解析. 首先,让我们从 Spring MVC 的四大组件:前端控制器(DispatcherServlet).处理器映射器(HandlerM ...

随机推荐

  1. Python3内置类型有哪些?

    摘要:Python3目前已经成为主流,和版本2天壤之别,关于Python3的内置类型你了解吗? 本文将专注于解释器支持的内置类型,基于版本3.9.1进行讲解. 内置的主要类型是numerics.seq ...

  2. Hive之insert和insert overwrite

    1. hive 表及数据准备 建表,并插入初始数据.向表中插入 hive> use test; hive> create table kwang_test (id int, name st ...

  3. D - Seek the Name, Seek the Fame

    The little cat is so famous, that many couples tramp over hill and dale to Byteland, and asked the l ...

  4. poj1066 线段相交简单应用(解题报告)

    #include<stdio.h> #include<math.h> const double eps=1e-8; int n; struct Point { double x ...

  5. 【noi 2.6_2989】糖果(DP)

    题意:求取到总和为K的倍数的糖果的最大值. 解法:用模K的余数作为一个维度,f[i][j]表示在前i种糖果中取到总颗数模K余j的最大总颗数. 注意--f[i-1][j]要正常转移,而其他要之前的状态存 ...

  6. codeforces 630K Indivisibility (容斥原理)

    IT City company developing computer games decided to upgrade its way to reward its employees. Now it ...

  7. 使用scrapy爬取jian shu文章

    settings.py中一些东西的含义可以看一下这里 python的scrapy框架的使用 和xpath的使用 && scrapy中request和response的函数参数 & ...

  8. CF1462-C. Unique Number

    题意: 给出一个数字x,让你找出一个由1到9这九个数字组成的数字,这个数字的每一位加起来等于x,并且1到9每个数字只能出现一次.若能找到这样的数字,输出这其中最小的一个,否则输出-1. 思路: 利用二 ...

  9. Operating System:信号量

    pv原语操作(1)操作系统PV意思:PV操作与信号量的处理相关,P表示通过的意度思,V表示释放的意思.(2)p操作和v操作是不可中断问的程序段,称为原语.如果将信号量看作共享变量,则pv操作为其临界区 ...

  10. VS制作可自动覆盖旧版本的安装包

    1.设置属性 DetectNewerInstalledVersion=TrueInstallAllUsers = TrueRemovePreviousVersion = True 2.增加软件版本号, ...