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


    菜单命令响应顺序


    首先,我们利用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中会增加如下代码:

    //MainFrm.h 响应函数声明
    afx_msg void OnTest();
    
    //MainFrm.cpp 命令消息映射宏
    BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_COMMAND(ID_TEST, &CMainFrame::OnTest)
    END_MESSAGE_MAP()
    
    //MainFrm.cpp 消息响应函数实现
    void CMainFrame::OnTest()
    {
        // TODO: 在此添加命令处理程序代码  
    }
    按照相同的方式,分别在CMenuApp、CMenuDoc、CMenuView、CMenuFrame添加OnTest消息响应函数,并添加的内容分别如下:

    CMenuApp:AfxMessageBox("app click");
    CMenuDoc:AfxMessageBox("Doc click");
    CMenuView:MessageBox("View click");
    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两个函数,它们的函数声明如下:

    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
    	AFX_CMDHANDLERINFO* pHandlerInfo);
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

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

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

    //首先响应菜单命令消息的是框架类
    BOOL CFrameWnd::OnCommand(WPARAM wParam, LPARAM lParam)
    	// return TRUE if command invocation was attempted
    {
    	HWND hWndCtrl = (HWND)lParam;
    	UINT nID = LOWORD(wParam);
    
    	CFrameWnd* pFrameWnd = GetTopLevelFrame();
    	ENSURE_VALID(pFrameWnd);
        ...
    
    	// route as normal command,调用基类的OnCommand函数实现(1)
    	return CWnd::OnCommand(wParam, lParam);
    }
    
    BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
    {
    	UINT nID = LOWORD(wParam);
    	HWND hWndCtrl = (HWND)lParam;
    	int nCode = HIWORD(wParam);
    
    	// default routing for command messages (through closure table)
    	if (hWndCtrl == NULL)
    	{
    		// zero IDs for normal commands are not allowed
    		if (nID == 0)
    			return FALSE;
    		// make sure command has not become disabled before routing
    		CTestCmdUI state;
    		state.m_nID = nID;
    		//多态,调用CMainFrame中的OnCmdMsg(2)
    		OnCmdMsg(nID, CN_UPDATE_COMMAND_UI, &state, NULL);
    		...
    		// menu or accelerator
    		nCode = CN_COMMAND;
    	}
    	else
    	{	
    	...
    	}
    
    	return OnCmdMsg(nID, nCode, NULL, NULL);
    }
    
    // CFrameWnd command/message routing
    BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
    	AFX_CMDHANDLERINFO* pHandlerInfo)
    {
    	CPushRoutingFrame push(this);
    
    	// pump through current view FIRST ,视图类(3-4),包含视类和文档类
    	CView* pView = GetActiveView();
    	if (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
    		return TRUE;
    
    	// then pump through frame 主框架(5),CWnd::OnCmdMsg查找的映射表是CMainFrame中
    	if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
    		return TRUE;
    
    	// last but not least, pump through  app类(6)
    	CWinApp* pApp = AfxGetApp();
    	if (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
    		return TRUE;
    
    	return FALSE;
    }
    
    BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,
    	AFX_CMDHANDLERINFO* pHandlerInfo)
    {
    	// first pump through pane 先视类(3)
    	if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
    		return TRUE;
    
    	// then pump through document 后文档类(4)
    	if (m_pDocument != NULL)
    	{
    		// special state for saving view before routing to document
    		CPushRoutingView push(this);
    		return m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
    	}
    
    	return FALSE;
    }
    

    我们将代码总结为如下:


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

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


  • 相关阅读:
    git rebase命令
    java中HashSet对象内的元素的hashCode值不能变化
    Spring中WebApplicationInitializer的理解
    mysql判断表字段或索引是否存在,然后修改
    mysql存储过程
    判断地图上的点是否在圆形,多边形,区域内
    计算任意多边形的面积、中心、重心
    判断点是否在任意多边形内
    springMvc将对象json返回时自动忽略掉对象中的特定属性的注解方式
    String.format()详细用法
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468376.html
Copyright © 2020-2023  润新知