菜单命令消息路由过程分析

我们利用VS2008中的MFC AppWizard建立一个名为Menu的单文档工程。打开资源视图中的IDR_MAINFRAME菜单,如图1所示。


            图1 默认的IDR_MAINFRAME菜单


我们可以在虚线框中输入用户自定菜单名,这里名为Test。在属性列表里PopUp类型设置为False,ID号编辑为IDM_TEST。如图2所示。


           图2 添加后的IDR_MAINFRAME菜单


在Test菜单上点击鼠标右键,点击“添加事件处理程序”,消息类型设置为Command,类列表选择CMainFrame,最后点击“添加编辑”,如图3所示。


                                     图3 菜单命令响应函数设置


设置完成后,在MainFrm.h和MainFrm.cpp中会增加如下代码:

[cpp]  view plain  copy
  1. //MainFrm.h 响应函数声明  
  2. afx_msg void OnTest();  
  3.   
  4. //MainFrm.cpp 命令消息映射宏  
  5. BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)  
  6. ON_COMMAND(ID_TEST, &CMainFrame::OnTest)  
  7. END_MESSAGE_MAP()  
  8.   
  9. //MainFrm.cpp 消息响应函数实现  
  10. void CMainFrame::OnTest()  
  11. {  
  12.     // TODO: 在此添加命令处理程序代码    
  13. }  
按照相同的方式,分别在CMenuApp、CMenuDoc、CMenuView、CMenuFrame添加OnTest消息响应函数,并添加的内容分别如下:

[cpp]  view plain  copy
  1. CMenuApp:AfxMessageBox("app click");  
  2. CMenuDoc:AfxMessageBox("Doc click");  
  3. CMenuView:MessageBox("View click");  
  4. CMenuFrame:MessageBox("Mainframe click");  


运行程序,发现最先响应该消息的是CMenuView中的OnTest响应函数。删除CMenuView中的OnTest响应函数,重新执行程序,最先响应消息的是CMenuDoc中的OnTest响应函数;

以此类推我们可以得到OnTest菜单命令响应函数顺序是:视类->文档类->框架类->应用程序类

在分析菜单命令路由原理之前,先介绍下Windows消息的分类。


Windows消息分类


实际上,菜单命令也是一种消息,在Windows中,消息被分为以下三类:

第一类:标准消息

除了WM_COMMAND之外,所有的WM_开头的消息都是标准消息。从CWnd派生的类,都可以接收这类消息。

例如WM_CHAR,WM_CLOSE,WM_CREATE等。


第二类:命令消息

来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND形式呈现。在MFC中,通过菜单项的ID来区分不同的命令消息,从CCmdTarget派生的类,都可以接收这类消息。


第三类:通告消息

由控件产生的消息,例如按钮的单击、列表框的选择等都会产生这类消息,目的是为了向其父窗口(通常是对话框)通知事件的产生。这类消息也是以WM_COMMAND形式呈现的。从CCmdTarget派生的类,都可以接收这类消息。


在本例中CMenuDoc和CWinApp类从CCmdTarget类派生出来,这两个类对象只能接收命令消息和通过消息。而CMenuView、CMenuFrame类继承于CWnd类,Cwnd类又从CCmdTarget派生出来,因此CMenuView、CMenuFrame的对象对这三种消息都能接收。


菜单命令路由分析


点击Test菜单,通过分析函数调用栈,可以得到菜单命令消息路由过程,如图4所所示。



                                      图4 命令消息的路由


MFC在后台把窗口过程函数替换成AfxWndProc函数,该函数位于wincore.cpp文件中,最终AfxWndProc间接的又调用了OnWndMsg函数,该函数完成对Windows消息的分类处理,这三种消息都是通过消息映射机制完成的。

若是标准消息,则利用形式如“ON_WM_MOUSEMOVE()”消息映射宏完成标准消息的处理。

若是命令消息,则交给OnCommand函数来处理。

若是通告消息,则交给OnNotify函数来处理。

OnCommand函数和OnNotify函数最终都交给OnCmdMsg函数完成最后的处理,即在查找对应的消息响应函数。


从之前实验可以得到,Test菜单响应的顺序是视类->文档类->框架类->应用程序类,现在从源码角度分析下菜单命令响应过程。从图4可以知道菜单命令路由过程需要调用OnCommand和OnCmdMsg两个函数,它们的函数声明如下:

[cpp]  view plain  copy
  1. virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,  
  2.     AFX_CMDHANDLERINFO* pHandlerInfo);  
  3. virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);  

可以知道,这两个都是虚函数,OnComand在CWnd类中声明的接口,OnCmdMsg在CCmdTarget 类中声明的接口,Cview、CFrameWnd、CDocument类都会相应的重写这两个接口,从而实现多态。

下面代码展示了菜单命令响应过程:

[cpp]  view plain  copy
  1. //首先响应菜单命令消息的是框架类  
  2. BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)  
  3.     // return TRUE if command invocation was attempted  
  4. {  
  5.     HWND hWndCtrl = (HWND)lParam;  
  6.     UINT nID = LOWORD(wParam);  
  7.   
  8.     CFrameWnd* pFrameWnd = GetTopLevelFrame();  
  9.     ENSURE_VALID(pFrameWnd);  
  10.     ...  
  11.   
  12.     // route as normal command,调用基类的OnCommand函数实现(1)  
  13.     return CWnd::OnCommand(wParam, lParam);  
  14. }  
  15.   
  16. BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)  
  17. {  
  18.     UINT nID = LOWORD(wParam);  
  19.     HWND hWndCtrl = (HWND)lParam;  
  20.     int nCode = HIWORD(wParam);  
  21.   
  22.     // default routing for command messages (through closure table)  
  23.     if (hWndCtrl == NULL)  
  24.     {  
  25.         // zero IDs for normal commands are not allowed  
  26.         if (nID == 0)  
  27.             return FALSE;  
  28.         // make sure command has not become disabled before routing  
  29.         CTestCmdUI state;  
  30.         state.m_nID = nID;  
  31.         //多态,调用CMainFrame中的OnCmdMsg(2)  
  32.         OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);  
  33.         ...  
  34.         // menu or accelerator  
  35.         nCode = CN_COMMAND;  
  36.     }  
  37.     else  
  38.     {     
  39.     ...  
  40.     }  
  41.   
  42.     return OnCmdMsg(nID, nCode, NULL, NULL);  
  43. }  
  44.   
  45. // CFrameWnd command/message routing  
  46. BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,  
  47.     AFX_CMDHANDLERINFO* pHandlerInfo)  
  48. {  
  49.     CPushRoutingFrame push(this);  
  50.   
  51.     // pump through current view FIRST ,视图类(3-4),包含视类和文档类  
  52.     CView* pView = GetActiveView();  
  53.     if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  54.         return TRUE;  
  55.   
  56.     // then pump through frame 主框架(5),CWnd::OnCmdMsg查找的映射表是CMainFrame中  
  57.     if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  58.         return TRUE;  
  59.   
  60.     // last but not least, pump through  app类(6)  
  61.     CWinApp* pApp = AfxGetApp();  
  62.     if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  63.         return TRUE;  
  64.   
  65.     return FALSE;  
  66. }  
  67.   
  68. BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,  
  69.     AFX_CMDHANDLERINFO* pHandlerInfo)  
  70. {  
  71.     // first pump through pane 先视类(3)  
  72.     if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  73.         return TRUE;  
  74.   
  75.     // then pump through document 后文档类(4)  
  76.     if (m_pDocument != NULL)  
  77.     {  
  78.         // special state for saving view before routing to document  
  79.         CPushRoutingView push(this);  
  80.         return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);  
  81.     }  
  82.   
  83.     return FALSE;  
  84. }  

我们将代码总结为如下:


                                                                                                                          图4 菜单命令路由整个过程

图4 是菜单命令路由的整个过程,到这菜单命令路由分析到此结束了!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值