么是消息反射?
在 Windows 里面,子控件经常向父控件发送消息,例如很多子控件要绘制自己的背景,就可能向父窗口发送消息 WM_CTLCOLOR 。对于从子控件发来的消息,父控件有可能在处理之前,把消息返还给子控件处理,这样消息看起来就想是从父窗口反射回来一样,故此得名:消息反射。
在 Windows 里面,子控件经常向父控件发送消息,例如很多子控件要绘制自己的背景,就可能向父窗口发送消息 WM_CTLCOLOR 。对于从子控件发来的消息,父控件有可能在处理之前,把消息返还给子控件处理,这样消息看起来就想是从父窗口反射回来一样,故此得名:消息反射。
消息反射的由来
在 Windows 和 MFC4.0 版本一下,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,自控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码,我们可以想象这是多么的复杂,代码是多么的臃肿!
我们可以想象,如果这些消息都让父窗口类去做,父窗口就成了一个万能的神,一个臃肿不堪的代码机,无论如何消息的处理都集中在父窗口类中,会使父窗口繁重无比,但是子控件却无事可做,并且代码也无法重用,这对于一个 程序 员来讲是多么痛苦的一件事?!
在老版本的 MFC 中, 设计 者也意识到了这个问题,他们对一些消息采用了虚拟机制,例如: WM_DRAWITEM ,这样子控件就有机会控制自己的动作,代码的可重用性有了一定的提高,但是这还没有达到大部分人的要求,所以在高版本的 MFC 中,提出了一种更方便的机制:消息反射。
通过消息反射机制,子控件窗口便能够自行处理与自身相关的一些消息,增强了封装性,同时也提高了子控件窗口类的可重用性。不过需要注意的是:消息反射是 MFC 实现的,不是 windows 实现的;要让你的消息反射机制工作,你得类必须从 CWnd 类派生。
在 Windows 和 MFC4.0 版本一下,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,自控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码,我们可以想象这是多么的复杂,代码是多么的臃肿!
我们可以想象,如果这些消息都让父窗口类去做,父窗口就成了一个万能的神,一个臃肿不堪的代码机,无论如何消息的处理都集中在父窗口类中,会使父窗口繁重无比,但是子控件却无事可做,并且代码也无法重用,这对于一个 程序 员来讲是多么痛苦的一件事?!
在老版本的 MFC 中, 设计 者也意识到了这个问题,他们对一些消息采用了虚拟机制,例如: WM_DRAWITEM ,这样子控件就有机会控制自己的动作,代码的可重用性有了一定的提高,但是这还没有达到大部分人的要求,所以在高版本的 MFC 中,提出了一种更方便的机制:消息反射。
通过消息反射机制,子控件窗口便能够自行处理与自身相关的一些消息,增强了封装性,同时也提高了子控件窗口类的可重用性。不过需要注意的是:消息反射是 MFC 实现的,不是 windows 实现的;要让你的消息反射机制工作,你得类必须从 CWnd 类派生。
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 开头。
如果想要处理消息反射,必须了解相应的 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!
(1) 在 ClassWizard 中,打开选择项 Message Maps ;
(2) 在下拉列表 Class name 中选择你要控制的类;
(3) 在 Object IDs 中,选中相应的类名;
(4) 在 Messages 一栏中找到前面带有 = 标记的消息,那就是反射消息;
(5) 双击 鼠标 或者单击添加按钮,然后 OK!
消息处理的过程
(1) 子窗口向父窗口发送通知消息,激发父窗口去调用它的虚函数 CWnd::OnNotify 。大致的结构如下:
(1) 子窗口向父窗口发送通知消息,激发父窗口去调用它的虚函数 CWnd::OnNotify 。大致的结构如下:
BOOL CWnd::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) { if (ReflectLastMsg(hWndCtrl, pResult)) file://hWndCtrl, 为发送窗口 return TRUE; file:// 如果子窗口已处理了此消息,返回 AFX_NOTIFY notify; notify.pResult = pResult; notify.pNMHDR = pNMHDR; return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY)? notify:NULL); } |
(2)ReflectLastMsg
声明如下:
static BOOL PASCAL ReflectLastMsg(HWND hWndChild, LRESULT* pResult = NULL);
|
它的主要任务就是调用发送窗口的
SendChildNotifyLastMsg
。
(3)SendChildNotifyLastMsg 声明如下:
(3)SendChildNotifyLastMsg 声明如下:
BOOL SendChildNotifyLastMsg(LRESULT* pResult = NULL);
|
调用发送窗口的虚函数
OnChildNotify
函数,进行处理。
如果发送窗口没有进行重载处理,则调用
ReflectChildNotify(...)
函数进行标准的反射消息的消息映射处理。
使用的一个例子
这里面我们举一个简单的例子,希望大家能够更清晰的掌握消息反射机制。
(1) 创建一个基于对话框的工程。
(2) 利用向导创建一个新的类: CMyEdit ,基类是 CEdit 。
(3) 在 CMyEdit 头文件中加入 3 个成员变量:
这里面我们举一个简单的例子,希望大家能够更清晰的掌握消息反射机制。
(1) 创建一个基于对话框的工程。
(2) 利用向导创建一个新的类: CMyEdit ,基类是 CEdit 。
(3) 在 CMyEdit 头文件中加入 3 个成员变量:
COLORREF m_clrText ; COLORREF m_clrBkgnd ; CBrush m_brBkgnd; |
(4)
利用向导在其中加入
WM_CTLCOLOR
(看到了么,前面是不是有一个
=
?),并且将它的函数体改为:
HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor) { pDC->SetTextColor( m_clrText ); // text pDC->SetBkColor( m_clrBkgnd ); // text bkgnd return m_brBkgnd; // ctl bkgnd } |
同时我们在
.cpp
文件中会看到
ON_WM_CTLCOLOR_REFLECT()
,这就是我们所说的经过处理的宏,是不是很符合规则?
(5) 在对话框中加入一个 Edit ,增加一个关联的变量,选择 Control 属性,类别为 CMyEdit 。
(6) 在对话框 .cpp 文件中加入 #include "MyEdit.h" ,运行,看到了什么?
(5) 在对话框中加入一个 Edit ,增加一个关联的变量,选择 Control 属性,类别为 CMyEdit 。
(6) 在对话框 .cpp 文件中加入 #include "MyEdit.h" ,运行,看到了什么?