消息反射机制要解决什么问题呢?

消息反射机制主要是为了控件而实现的。每当控件需要某些资讯(比如,绘制自身背景的画刷,显示字体的颜色等等)时,都会频繁地向其父窗口发送通告消息(notification message),比如最常见的WM_CTLCOLOR通告消息,让父窗口为它提供各种服务。当子控件很多时,父窗口很会变得臃肿不堪,而子控件却没事可做。另一方面,假如我们想继续创建一个相同风格的控件时,必须在父类中重复相同的代码。前者让我们程序不够健壮,父窗口可能累死;后者不利于代码的重用,很可能把程序员给累死了。显然,这不是面向对象程序设计的思维。如果我们在子控件对应的类中处理自身的通告消息,父窗口就不需要为子控件而操心了,子控件处理自己的事情。既解决前面两个问题,两个模块的耦合度也小了。

哪些消息可以被反射?

能够被反射都是控件发送给父窗口的通告消息notification message, 从MFC的源代码中我们可以得知,能够被反射的消息总共有12中,根据MFC对它们不同的包装方式,我将它们分为四类。

反射消息表
第一类 WM_COMMAND
第二类

WM_NOTIFY

第三类

WM_CTLCOLOR

第四类

WM_VSCROLL, WM_HSCROLL, WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM,WM_DELETEITEM, WM_CHARTOITEM, WM_VKEYTOITEM,WM_PARENTNOTIFY

消息反射机制具体是如何实现的呢?

事实上,对于上面的每一种消息,子控件的父窗口对应的类中都已为其添加了消息处理。每当控件向发父窗口发送一个通告消息时,都会首先由父窗口中对应的消息处理函数接手,请注意,父窗口中的消息处理函数只是接管这个消息,并没有实际处理它。父窗口中的消息处理函数首先将其“反射”给子控件,看子控件是否处理了这个反射消息。如果子控件处理了该反射消息并且返回TRUE,则消息终结,否则,交由父窗口处理。但是,对于上面的四种反射消息,MFC对它们实现反射的细节还是不一样的。具体的请看下面的讲解,我会尽量贴出源代码,因为源代码才是最有说服力和最有感觉的。

因为控件通常要发很多通告notification给父窗口,这些通告notification分别携带了不同数据量的additional information额外数据,为了便于处理,windows根据额外数据量additional information的区别将其包装成了上面的12种。

第一类:WM_COMMAND的反射消息

有一类通告,它们只需要携带通告代码(notification
code),控件标识符(identifier)和控件句柄hwnd,它们分别保存在wParam的高字节,wParam低字节,lParam中,Windows将其打包成WM_COMMAND消息。

当鼠标点击子控件时,子控件向父窗口发送WM_COMMAND消息,父窗口首先调用一个虚函数OnCommand处理,MFC源代码如下:

  1. BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. ......
  4. if (message == WM_COMMAND)
  5. {
  6. if (OnCommand(wParam, lParam))
  7. {
  8. lResult = 1;
  9. goto LReturnTrue;
  10. }
  11. ......
  12. }

OnCommand首先将其反射给控件自身,如果ReflectLastMsg返回false,子控件没有处理该反射消息,就调用OnCmdMsg,让父窗口处理。mfc源代码如下:

  1. BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
  2. { ......
  3. if (ReflectLastMsg(hWndCtrl))//首先给控件一个机会处理它
  4. return TRUE;
  5. ......
  6. return OnCmdMsg(nID, nCode, NULL, NULL);//若控件没有处理则由父窗口处理
  7. }

由于代码太多,我并没有具体列出调用的每个函数,而只是将其展开,贴出关键的代码,请下载附带的源程序demo_msgreflection,里面的代码断点都设置好了,调试可以看到具体的细节。

  1. BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
  2. {
  3. ...................
  4. if (CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL))//消息反射原型
  5. {
  6. if (pResult != NULL)
  7. *pResult = 1;
  8. return TRUE;
  9. }
  10. ....................
  11. }

第二类:WM_NOTIFY的反射消息

事实上,MFC对WM_CONOTIFY 和WM_COMMAND消息的处理非常类似,这里我就不详解,只贴出代码,大家可参照上面。

  1. BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. LRESULT lResult = 0;
  4. ...............
  5. if (message == WM_NOTIFY)
  6. {
  7. NMHDR* pNMHDR = (NMHDR*)lParam;
  8. if (pNMHDR->hwndFrom != NULL && OnNotify(wParam, lParam, &lResult))//OnNotify专门处理WM_NOTIFY消息
  9. goto LReturnTrue;
  10. return FALSE;
  11. ................
  12. }
  13. BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult)
  14. {
  15. ...................
  16. if (ReflectLastMsg(hWndCtrl, pResult))//反射给控件,让控件首先有机会处理它
  17. return TRUE;
  18. ....................
  19. return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), ¬ify, NULL);//控件没有处理,有父窗口处理
  20. }
  21. BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
  22. {
  23. ................
  24. return CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ¬ify, NULL);//消息反射原型
  25. .....................
  26. }

第三类:WM_CTLCOLOR的反射消息

事实上,32位系统里面的WM_CTLCOLOR消息是被MFC包装出来的,只是为了16位的系统兼容。在32位系统中,当控件需要画刷绘制背景时,向父窗口发送的消息是WM_CTLCOLORSTATIC,  
WM_CTLCOLOREDIT,    WM_CTLCOLORBTN,  WM_CTLCOLORLISTBOX, WM_CTLCOLORDLG, WM_COLORMSGBOX, WM_CTLCOLORSCROLLBAR.  我在上面说过,mfc在父窗口中会首先接管这些通告消息,请看下面的message map,当静态文本框向父窗口发送WM_CTLCOLORSTATIC后,首先是被父窗口中OnNtCtlColor函数处理,我们跟着这个函数,揭开WM_CTLCOLOR这个消息的神秘面纱。

  1. BEGIN_MESSAGE_MAP(CWnd, CCmdTarget)
  2. ON_MESSAGE(WM_CTLCOLORSTATIC, OnNTCtlColor)
  3. ON_MESSAGE(WM_CTLCOLOREDIT, OnNTCtlColor)
  4. ON_MESSAGE(WM_CTLCOLORBTN, OnNTCtlColor)
  5. ON_MESSAGE(WM_CTLCOLORLISTBOX, OnNTCtlColor)
  6. ON_MESSAGE(WM_CTLCOLORDLG, OnNTCtlColor)
  7. ON_MESSAGE(WM_CTLCOLORMSGBOX, OnNTCtlColor)
  8. ON_MESSAGE(WM_CTLCOLORSCROLLBAR, OnNTCtlColor)

OnNtCtlColor只是接管这个消息,他将WM_CTLCOLORSTATIC消息变换为WM_CTLCOLOR传给了WindowProc,但还是传给了父窗口

  1. LRESULT CWnd::OnNTCtlColor(WPARAM wParam, LPARAM lParam)
  2. {
  3. ....................
  4. return WindowProc(WM_CTLCOLOR, 0, (LPARAM)&ctl); //在此处WM_CTLCOLORSTATIC被包装成了WM_CTLCOLOR传递给父窗口
  5. }

父窗口调用CDialog::OnCtlColor函数处理, 该函数又调用OnGrayCtlColor

  1. HBRUSH CDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
  2. {
  3. return OnGrayCtlColor(pDC, pWnd, nCtlColor);
  4. }

OnGrayCtlColor将消息反射给控件处理,如果控件处理了,消息终结,否则传回父窗口,默认处理

  1. HBRUSH CWnd::OnGrayCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
  2. {
  3. LRESULT lResult;
  4. if (pWnd->SendChildNotifyLastMsg(&lResult))//将消息反射给子控件,让子控件首先有机会处理它
  5. return (HBRUSH)lResult;
  6. _AFX_WIN_STATE* pWinState = _afxWinState;
  7. //如果控件没有处理反射消息,有父窗口处理
  8. if (!GrayCtlColor(pDC->m_hDC, pWnd->GetSafeHwnd(), nCtlColor,pWinState->m_hDlgBkBrush, pWinState->m_crDlgTextClr))
  9. return (HBRUSH)Default();
  10. return pWinState->m_hDlgBkBrush;
  11. }

SendChildNotifyLastMsg调用了OnChildNotify

  1. BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
  2. {
  3. _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
  4. return OnChildNotify(pThreadState->m_lastSentMsg.message,pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
  5. }

OnChildNotify调用ReflectChildNotify

  1. BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. ................
  4. return ReflectChildNotify(uMsg, wParam, lParam, pResult);
  5. }

ReflectChildNotify消息反射消息给控件

  1. BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC)
  4. {
  5. AFX_CTLCOLOR ctl;
  6. ctl.hDC = (HDC)wParam;
  7. ctl.nCtlType = uMsg - WM_CTLCOLORMSGBOX;
  8. ASSERT(ctl.nCtlType >= CTLCOLOR_MSGBOX);
  9. ASSERT(ctl.nCtlType <= CTLCOLOR_STATIC);
  10. // reflect the message through the message map as OCM_CTLCOLOR
  11. BOOL bResult = CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, 0, (LPARAM)&ctl, pResult);
  12. if ((HBRUSH)*pResult == NULL)
  13. bResult = FALSE;
  14. return bResult;
  15. }
  16. }

第四类(以WM_DRAWITEM为例)

这类消息很的反射很简单,没有什么特别,只有WM_DRAWITE另辟蹊径,贴出关键的代码,大家都能懂了。这里有示范例子,点击下载:Demo1

以WM_DRAWITEM,当父窗口收到WM_DRAWITEM消息后,首先调用父类为其添加的消息处理函数OnDrawItem,OnDrawItem接管这个消息调用子控件的ReflectLastMsg

  1. void CWnd::OnDrawItem(int /*nIDCtl*/, LPDRAWITEMSTRUCT lpDrawItemStruct)
  2. {
  3. ..............
  4. if (ReflectLastMsg(lpDrawItemStruct->hwndItem))//将消息反射给控件,让控件首先有机会处理它
  5. return;
  6. ..............
  7. Default(); //如果控件没有处理它,就父窗口处理它
  8. }

下面的代码会揭示出我们熟知的DrawItem这个虚函数的内幕

ReflectLastMsg调用了SendChildNotifyLastMsg

  1. BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult)
  2. {
  3. 。。。。。。
  4. return pWnd->SendChildNotifyLastMsg(pResult);//中转站而已
  5. }

SendChildNotifyLastMsg调用了CButton::OnChildNotify

  1. BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult)
  2. {
  3. _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
  4. return OnChildNotify(pThreadState->m_lastSentMsg.message,pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult);
  5. }

OnChildNotify对这九个消息进行分流,WM_DRAWITEM 流向DrawItem, 而其他的消息则流向 CWnd::OnChildNotify

  1. BOOL CButton::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam,
  2. LRESULT* pResult)
  3. {
  4. if (message != WM_DRAWITEM)
  5. return CWnd::OnChildNotify(message, wParam, lParam, pResult);
  6. ASSERT(pResult == NULL); // no return value expected
  7. UNUSED(pResult); // unused in release builds
  8. DrawItem((LPDRAWITEMSTRUCT)lParam);
  9. return TRUE;
  10. }

CWnd::OnChildNotify调用了ReflectChildNotify

  1. BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. #ifndef _AFX_NO_OCC_SUPPORT
  4. if (m_pCtrlSite != NULL)
  5. {
  6. LRESULT lResult = SendMessage(OCM__BASE+uMsg, wParam, lParam);
  7. if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC &&
  8. (HBRUSH)lResult == NULL)
  9. {
  10. return FALSE;
  11. }
  12. if (pResult != NULL)
  13. *pResult = lResult;
  14. return TRUE;
  15. }
  16. return ReflectChildNotify(uMsg, wParam, lParam, pResult);
  17. }

ReflectChildNotify则将其反射给子控件

  1. BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
  2. {
  3. switch (uMsg)
  4. {
  5. case WM_HSCROLL:
  6. case WM_VSCROLL:
  7. case WM_PARENTNOTIFY:
  8. case WM_DRAWITEM:
  9. case WM_MEASUREITEM:
  10. case WM_DELETEITEM:
  11. case WM_VKEYTOITEM:
  12. case WM_CHARTOITEM:
  13. case WM_COMPAREITEM:
  14. // reflect the message through the message map as WM_REFLECT_BASE+uMsg
  15. return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult);//消息反射原型
  16. }

MFC消息反射机制的更多相关文章

  1. MFC的消息反射机制

    1.消息反射解释: 父窗口将子窗口发给它的通知消息,首先反射回子窗口进行处理(即给子窗口一个机会,让子窗口处理此消息),这样通知消息就有机会能被子窗口自身进行处理. 2.MFC中引入消息反射的原因: ...

  2. MFC消息映射机制以及画线功能实现

    ---此仅供用于学习交流,切勿用于商业用途,转载请注明http://www.cnblogs.com/mxbs/p/6213404.html. 利用VS2010创建一个单文档标准MFC工程,工程名为Dr ...

  3. MFC编程入门之五(MFC消息映射机制概述)

    在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息,通过对消息的处理实现相应的操作.比较典型的过程是,用户操作窗口,然后有消息产生,送给窗口的消息处理函数处理,对用户的操作做出响应. 一.什 ...

  4. VS2010/MFC编程入门之五(MFC消息映射机制概述)

    VS2010/MFC编程入门之五(MFC消息映射机制概述)-软件开发-鸡啄米 http://www.jizhuomi.com/software/147.html 上一讲鸡啄米为大家简单分析了MFC应用 ...

  5. MFC消息响应机制 q

    MFC消息响应机制分析 1 引言微软公司提供的MFC基本类库(Microsoft Foundation Classes),是进行可视化编程时使用最为流行的一个类 库.MFC封装了大部分Windows ...

  6. MFC消息映射机制

    1.MFC应用框架主要类之间的关系 MFC自动生成的框架中重要的类有:C-App.CMainFrame.C-Doc和C-View. 其他的类如CClassView.CFileView等都是在框架窗口( ...

  7. MFC消息响应机制分析

    ---- 摘要: ---- MFC是Windows下程序设计的最流行的一个类库,但是该类库比较庞杂,尤其是它的消息映射机制,更是涉及到很多低层的东西,我们在这里,对它的整个消息映射机制进行了系统的分析 ...

  8. VS2010-MFC(MFC消息映射机制概述)

    转自:http://www.jizhuomi.com/software/147.html 前面已经说过,Windows应用程序是消息驱动的.在MFC软件开发中,界面操作或者线程之间通信都会经常用到消息 ...

  9. Meandering Through the Maze of MFC Message and Command Routing MFC消息路由机制分析

    Meandering Through the Maze of MFC Message and Command Routing Paul DiLascia Paul DiLascia is a free ...

随机推荐

  1. 【AC自动机】病毒

    [题目链接] https://loj.ac/problem/10062 [题意] 寻找一个没有模式串为子串的无限01串.是否存在. [题解] 其实就是用dfs找一个环. 1.环需要从根结点出发找到这个 ...

  2. 【Trie】背单词

    参考博客: https://www.luogu.org/problemnew/solution/P3294 https://blog.csdn.net/VictoryCzt/article/detai ...

  3. .NET Core 创建Windows服务

    .NET Core 创建Windows服务 作者:高堂 原文地址:https://www.cnblogs.com/gaotang/p/10850564.html 写在前面 使用 TopShelf+Au ...

  4. caffe笔记

    1. 训练    cifar10 示例 ① cd caffe.1.0.0 ./data/cifar10/get_cifar10.sh    #获取图片 ② ./examples/cifar10/cre ...

  5. webpack4快速上手

    1.在项目根目录cnpm init -y初始化.生成package.json文件 2.在项目里面使用cnpm安装webpack 3.需要在根目录下新建webpack.config.js文件(在其里面配 ...

  6. set 集合的函数调用

    方法 意义 S.add(e) 在集合中添加一个新的元素e:如果元素已经存在,则不添加 S.remove(e) 从集合中删除一个元素,如果元素不存在于集合中,则会产生一个KeyError错误 S.dis ...

  7. 报错处理(UnicodeEncodeError: 'gbk' codec can't encode character '\xa0' in position 2: ill egal multibyte sequence)

    参照文[https://blog.csdn.net/Dillon2015/article/details/53204955]的说法, 第一个错 [UnicodeEncodeError:'gbk' co ...

  8. opencv读取图像python和c++版本的结果不同

    问题: 在读取同一张图像时,python读取的结果和c++读取的结果差异较大,测试图像中最大误差达到16. 原因: python的opencv采用的是4.1.1,c++采用的是3.1.0,在解析JPE ...

  9. etcd安装和简单使用

    etcd作为一个高可用强一致性的服务发现存储仓库,在Kubernetes等开源项目中用的很多,这里简单记录下安装和常用命令以及api 安装 安装包可以从 https://github.com/etcd ...

  10. Hadoop_15_MapRduce_案例1_Wordcount 单词统计

    1.Wordcount示例编写: MapReduce采用”分而治之”的思想,把对大规模数据集的操作,分发给一个主节点管理下的各个分节点共同完成,然后通过整合各 个节点的中间结果,得到最终结果.简单地说 ...