MFC消息反射

来源:互联网 发布:java程序编写软件 编辑:程序博客网 时间:2024/05/01 09:31

什么是消息反射?

在windows里面,子控件经常向父控件发送消息,例如很多子控件要绘制自己的背景,就可能向父窗口发送消息WM_CTLCOLOR。对于从子控件发来的消息,父控件有可能在处理之前,把消息返还给子控件处理,这样消息看起来就想是从父窗口反射回来一样,故此得名:消息反射

有人会问,为什么子控件要向父控件发消息,而不是直接自己处理呢?又或者是发给父控件消息,为什么又发回来呢?下面看看消息反射的由来,就会明白一些。

消息反射的由来

        在windows和MFC4.0版本一下,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,自控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码,我们可以想象这是多么的复杂,代码是多么的臃肿!
        我们可以想象,如果这些消息都让父窗口类去做,父窗口就成了一个万能的神,一个臃肿不堪的代码机,无论如何消息的处理都集中在父窗口类中,会使父窗口繁重无比,但是子控件却无事可做,并且代码也无法重用,这对于一个程序员来讲是多么痛苦的一件事?!
        在老版本的MFC中,设计者也意识到了这个问题,他们对一些消息采用了虚拟机制,例如:WM_DRAWITEM,这样子控件就有机会控制自己的动作,代码的可重用性有了一定的提高,但是这还没有达到大部分人的要求,所以在高版本的MFC中,提出了一种更方便的机制:消息反射。
        通过消息反射机制,子控件窗口便能够自行处理与自身相关的一些消息,增强了封装性,同时也提高了子控件窗口类的可重用性。不过需要注意的是:消息反射是MFC实现的,不是windows实现的;要让你的消息反射机制工作,你得类必须从CWnd类派生
为了更好的理解,下面看一个例子
在基于对话框的程序中,有一个任务是改变编辑框控件的背景,怎么办?
实现思路如下,子控件要绘制时会向父类(这里是主窗口)发送WM_CTLCOLOR
消息,在父类中(这里是主窗口)响应WM_CTLCOLOR,根据消息传递过来的
参数进行相应的操作,具体代码如下:
CBrush m_brush;  
m_brush.CreateSolidBrush(RGB(0, 0, 255));
HBRUSH CMy112Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)   {      HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);            if (pWnd->GetDlgCtrlID() == IDC_EDIT1)      {          pDC->SetBkMode(TRANSPARENT);          return m_brush;      }        return hbr;  }  

代码解释如下:在主对话框类CMy112Dlg中定义一个成员变量m_brush,并在主对话框类中的构造函数中进行初始化。然后为父类编写WM_CTLCOLOR,在消息响应中判断控件是否为要设置的编辑框控件(要设置的编辑框控件ID为IDC_EDIT1)将背景模式设置为透明,并设置控件背景为蓝色。
这和消息反射有什么关系呢?上述代码有问题吗?上述方法也不失为一种好方法,但从软件工程角度看还有一些缺憾,子类控件的消息都要父类去处理,这样就会导致父类中的代码臃肿,而子类无事可做。另一方面,如果其它的工程要用到类似功能,怎么办?
只好把上述函数代码拷贝到另外工程中,并进行稍微修改。
MFC4.0开始有了解决此问题的一种新方法:消息反射,以上面为例,子控件要绘制时,会向父类发送要绘制的消息,父类收到消息后,首先反射(再发给)给子控件,看子控件是否对此消息做了处理,如果做了处理,父类将不再处理。如果子控件没有处理,则父类对子控件发送的消息做处理。
再看一下上面例子的另一种方法:消息反射法
为工程添加一处新类CYellowEdit,此类从MFC的CEdit类派生,为类添加三个成员变量
COLORREF m_clrText;  COLORREF m_clrBkgnd;  CBrush   m_brBkgnd;  

分别是文本颜色,背景色,画刷,在CYellowEdit类构造函数中进行初始化
CYellowEdit::CYellowEdit()  {      m_clrText = RGB(0, 0, 0);      m_clrBkgnd = RGB(255, 255, 0);      m_brBkgnd.CreateSolidBrush(m_clrBkgnd);  }  

为CYellowEdit类添加反射函数(消息前面带有=的是反射消息,本例中为=WM_CTRLCOLR,

HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)   {      pDC->SetTextColor(m_clrText);    // 文本颜色      pDC->SetBkColor(m_clrBkgnd);     // 背景色      return m_brBkgnd;                // 控件背景  }  
在主对话框上再添加一个编辑框,并为之关联变量,关联变量类型为CYelloEdit,运行程序可以看到编辑框的背景已变成黄色了。
这就是消息反射的一个用处,使用消息反射和不使用消息反射这两个例子有什么区别呢?个人认为最大的区别就是代码的可重用性,使用消息反射后,如果碰到类似应用,直接将
CYellowEdit拷贝到其它工程直接应用,不需要修改代码。
上述例子在MSDN中有详细介绍,在MSDN中主题为:ON_NOTIFY_REFLECT,里面有一篇 TN062: Message Reflection for Windows Controls的文章专门讲述了消息反射。

Message-Map中的处理

如果想要处理消息反射,必须了解相应的Message-Map宏和函数原型。一般来讲,Message-Map是有一定的规律的,通常她在消息的前面加上一个ON_ ,然后再消息的最后加上 _REFLECT。例如我们前面提到的WM_CTLCOLOR 经过处理后变成了ON_WM_CTLCOLOR_REFLECT;WM_MEASUREITEM则变成了ON_WM_MEASUREITEM_REFLECT。
    凡事总会有例外,这里也是这样,这里面有3个例外:
    (1) WM_COMMAND 转换成 ON_CONTROL_REFLECT;
    (2) WM_NOTIFY  转换成 ON_NOTIFY_REFLECT;
    (3) ON_UPDATE_COMMAND_UI 转换成 ON_UPDATE_COMMAND_UI_REFLECT;
    对于函数原型,也必须是以 afx_msg 开头。
    利用ClassWizard添加消息反射
    (1)在ClassWizard中,打开选择项Message Maps;
    (2)在下拉列表Class name中选择你要控制的类;
    (3)在Object IDs中,选中相应的类名;
    (4)在Messages一栏中找到前面带有=标记的消息,那就是反射消息;
    (5)双击鼠标或者单击添加按钮,然后OK!

消息反射的处理过程

如果不考虑有OLE控件的情况,消息反射的处理流程如下:
首先,调用CWnd的成员函数SendChildNotifyLastMsg,它从线程状态得到本线程最近一次获取的消息和消息参数,并且把这些参数传递给函数OnChildNotify。注意,当前的CWnd对象就是MFC控制子窗口对象。

OnChlidNofify是CWnd定义的虚拟函数,不考虑OLE控制的话,它仅仅只调用ReflectChildNotify。OnChlidNofify可以被覆盖,所以如果程序员希望处理某个控制的通知消息,除了采用消息映射的方法处理通知反射消息以外,还可以覆盖OnChlidNotify虚拟函数,如果成功地处理了通知消息,则返回TRUE。

ReflectChildNotify是CWnd的成员函数,完成反射消息的派发。
对于WM_COMMAND,它直接调用CWnd::OnCmdMsg派发反射消息WM_REFLECT_BASE+WM_COMMAND;
对于WM_NOTIFY,它直接调用CWnd::OnCmdMsg派发反射消息WM_REFLECT_BASE+WM_NOFITY;
对于其他消息,则直接调用CWnd::OnWndMsg(即CmdTarge::OnWndMsg)派发相应的反射消息,例如WM_REFLECT_BASE+WM_HSCROLL。

注意:ReflectChildNotify直接调用了CWnd的OnCmdMsg或OnWndMsg,这样反射消息被直接派发给控制子窗口,省却了消息发送的过程。

接着,控制子窗口如果处理了当前的反射消息,则返回反射消息被成员处理的信息。

MFC消息映射的回顾

从处理命令消息的过程可以看出,Windows消息和控制消息的处理要比命令消息的处理简单,因为查找消息处理函数时,后者只要搜索当前窗口对象(this所指)的类或其基类的消息映射入口表。但是,命令消息就要复杂多了,它沿一定的顺序链查找链上的各个命令目标,每一个被查找的命令目标都要搜索它的类或基类的消息映射入口表。

MFC通过消息映射的手段,以一种类似C++虚拟函数的概念向程序员提供了一种处理消息的方式。但是,若使用C++虚拟函数实现众多的消息,将导致虚拟函数表极其庞大;而使用消息映射,则仅仅感兴趣的消息才加入映射表,这样就要节省资源、提高效率。这套消息映射机制的基础包括以下几个方面:

1)消息映射入口表的实现:采用了C++静态成员和虚拟函数的方法来表示和得到一个消息映射类(CCmdTarget或派生类)的映射表。
2)消息查找的实现:从低层到高层搜索消息映射入口表,直至根类CCmdTarget。
3)消息发送的实现:主要以几个虚拟函数为基础来实现标准MFC消息发送路径:OnComamnd、OnNotify、OnWndMsg和OnCmdMsg。

OnWndMsg是CWnd类或其派生类的成员函数,由窗口过程调用。它处理标准的Windows消息。

OnCommand是CWnd类或其派生类的成员函数,由OnWndMsg调用来处理WM_COMMAND消息,实现命令消息或者控制通知消息的发送。如果派生类覆盖该函数,则必须调用基类的实现,否则将不能自动的处理命令消息映射,而且必须使用该函数接受的参数(不是程序员给定值)调用基类的OnCommand。

OnNotify是CWnd类或其派生类的成员函数,由OnWndMsg调用来处理WM_NOTIFY消息,实现控制通知消息的发送。

OnCmdMsg是CCmdTarget类或其派生类的成员函数。被OnCommand调用,用来实现命令消息发送和派发命令消息到命令消息处理函数。

自动更新用户对象状态是通过MFC的命令消息发送机制实现的。

控制消息可以反射给控制窗口处理。

队列消息在发送给窗口过程之前可以进行消息预处理,如果消息被MFC窗口对象预处理了,则不会进入消息发送过程。

通用控件的WM_NOTIFY消息反射

ON_NOTIFY不反射消息。如果自己处理不了,就传给上级窗口;如果再处理不了,再往上传。实在处理不了,由框架默认处理。

ON_NOTIFY_REFLECT反射消息,一般情况下控件传递给父窗体的普通的消息都是由父窗体函数来处理。但由ON_NOTIFY_REFLECT映射的消息先由该控件处理,如果该控件没有处理函数再发往父窗体处理。实际上使用ON_NOTIFY_REFLECT宏,父窗口是得不到通知的。要使父窗口也得到通知,使用另外一个宏ON_NOTIFY_REFLECT_EX。

ON_NOTIFY_REFLECT_EX映射的处理函数,这个宏和ON_NOTIFY_REFLECT的唯一区别就是有函数的返回值。一般用在子窗口类中添加消息处理函数,返回值若为true,表示我已经在子窗口类中做了处理,不会再传给父窗口,其实是在return true之前做了一些处理的;若返回值为false,表示我没在子窗口类中做处理,那么它就会传给父窗口,让父窗口接着处理,其实是可以再return false之前来做一些处理,个人理解这里return的目的只是看程序员是否要在父窗口中继续处理。已经程序验证,例子如下:

MFC创建一个对话框程序,在对话框中添加了一个自定义控件CMyTreeCtrl(继承了CTreeCtrl),并且在CMyTreeCtrl中为NM_CLICK(其实是=NM_CLICK,表示反射消息)消息绑定了反射消息处理函数OnNMClick,同时在其父窗口类中也绑定了NM_CLICK消息的处理函数
在MyTreeCtrl.h中NM_CLICK
DECLARE_MESSAGE_MAP()afx_msg BOOL OnNMClick(NMHDR *pNMHDR, LRESULT *pResult);

在MyTreeCtrl.cpp中
BEGIN_MESSAGE_MAP(CMyTreeCtrl, CTreeCtrl)ON_NOTIFY_REFLECT_EX(NM_CLICK, &CMyTreeCtrl::OnNMClick)END_MESSAGE_MAP()
BOOL CMyTreeCtrl::OnNMClick(NMHDR *pNMHDR, LRESULT *pResult){MessageBox(_T("CMyTreeCtrl"));*pResult = 0;return true;}

在MsgReflect001Dlg.cpp中
void CMsgReflect001Dlg::OnNMClickTree1(NMHDR *pNMHDR, LRESULT *pResult){MessageBox(_T("CMsgReflect001Dlg"));*pResult = 0;}

结果:
1)若在MyTreeCtrl.cpp中的OnNMClick函数返回值为true,则不会再执行CMsgReflect001Dlg中的OnNMClickTree1函数;
2)若MyTreeCtrl.cpp中的OnNMClick函数返回值为false,则会接着执行CMsgReflect001Dlg中的OnNMClickTree1函数;
3)其实MyTreeCtrl.cpp中的OnNMClick函数返回值为false时,也执行了MyTreeCtrl中的OnNMClick函数,可以看出,当使用宏ON_NOTIFY_REFLECT_EX为消息NM_CLICK绑定反射消息处理函数时,消息是先走的子类的函数,然后再判断是否走父窗口;若是使用的ON_NOTIFY_REFLECT,也是先走的子类,但是不走父窗口了,即子窗口自己处理;这也印证了反射的目的是将以往ON_NOTIFY宏的情况下由父窗口处理消息的模式改为了交由子窗口自己处理;由此可以总结出三种模式,即消息由子类处理ON_NOTIFY_REFLECT来实现消息由父类处理ON_NOTIFY消息由两者共同处理ON_NOTIFY_REFLECT_EX 其中消息处理函数返回的是false,返回true的话就相当于ON_NOTIFY_REFLECT













0 0