本文翻译自MSDN中TN062: Message Reflection for Windows Controls
一 什么是消息反射
Windows控件需要频繁的向父窗口发送通告消息(Notification Message)。例如,很多控件为了改变背景色,需要给父窗口发送WM_CTLCOLOR 这个通告消息,以允许父窗口提供用户绘制控件背景色的画刷。在Windows4.0 和MFC4.0以前,父窗口(通常是一个对话框)负责处理这些通告消息。这意味必须在父窗口类中提供通告消息的消息处理函数;如果多个父窗口都需要处理这个消息,则必须在每个父窗口类中都提供相同的代码。以上面为例,每个想要修改控件背景色的对话框都需要处理WM_CTLCOLOR 消息。因此,如果一个控件类能处理它自己的背景颜色,那么重用代码会容易得多。
在MFC4.0中,旧的机制依然有效——父窗口类可以处理通告消息。另外,MFC4.0为了便于代码重用,提供了一种称为“消息反射”(message reflection)的机制,这种机制允许控件类或者父窗口、或者两者去处理通告消息。以上述修改控件背景色为例,现在你可以写一个控件类,在这个类中可以通过处理反射回来的WM_CTLCOLOR 消息来修改自己的背景色,所有这一切都无需依赖父窗口。(注意因为消息反射是被MFC实现的,不是Windows,所以为了确保消息反射可以工作,父窗口类必须从CWnd类继承而来)。
旧版本的 MFC 通过为一些消息提供虚函数来执行类似于消息反射的操作,例如用于自绘列表框的消息(WM_DRAWITEM 等)。 新的消息反射机制是通用的和一致的。另外,消息反射机制向后兼容MFC4.0之前的代码。
如果在你的父窗口中为特定或者一系列消息提供了消息处理函数,并且你没有在这些处理函数中调用基类的处理函数,它将覆盖相同消息的反射消息处理函数。例如,如果在对话框类中处理了WM_CTLCOLOR 消息,这将覆盖所有的反射消息处理函数。
如果在父窗口类提供了处理指定或者一系列WM_NOTIFY 消息的函数,只有当发送这些消息的子控件没有通过ON_NOTIFY_REFLECT()宏提供反射消息处理函数时,父窗口的这些消息处理函数才会被调用。如果在控件类使用了ON_NOTIFY_REFLECT_EX() 宏来提供消息处理函数,这个处理函数通过返回值确定是否调用父窗口的消息处理函数;如果返回值为FALSE,消息将会传递给父窗口,如果为TRUE,则不会传递给父窗口。请注意,反射消息的处理先于通告消息。
当WM_NOTIFY发送后,控件会优先处理它。其他反射消息发出后,父窗口会优先处理它,然后控件才接收到该消息(译者注:控件能否收到消息,要看父窗口是否调用了基类处理函数)。为了完成上述目的,需要在控件类的消息映射表中提供相应的条目,并且在控件类中提供相应的处理函数。
反射消息的消息映射宏和常规的通告消息映射宏稍有不同。比如,在父窗口中处理WM_NOTIFY 消息,应在父窗口类中使用 ON_NOTIFY 宏。在子控件中处理反射消息,使用ON_NOTIFY_REFLECT 宏。一些情况下,两者的参数也不同。注意类向导可以添加消息映射条目、并提供函数实现框架。
二 反射消息的消息映射表和处理函数原型
为了处理控件反射通告消息,使用下表中的消息映射宏和函数原型(类向导可以完成上述工作)。
从消息名称到反射宏名称的转换方法为:消息名称前加"ON_" ,后面加“_REFLECT”.例如,WM_CTLCOLOR 变为 ON_WM_CTLCOLOR_REFLECT。不过,有三个例外:
- WM_COMMAND反射消息宏为:ON_CONTROL_REFLECT
- WM_NOTIFY反射消息宏ON_NOTIFY_REFLECT.
- ON_UPDATE_COMMAND_UI反射宏为: ON_UPDATE_COMMAND_UI_REFLECT.
在上述三种情况中,你必须指定处理函数的名称。在其他情况中,你必须使用处理函数的标准名称。
函数的参数和返回值的含义记录在函数名称或带有 On 前缀的函数名称下。 例如,CtlColor 记录在 OnCtlColor 中。 与父窗口中的类似处理程序相比,一些反射消息处理程序的参数更少。 只需将下表中的名称与文档中的形式参数名称匹配即可。
Map entry | Function prototype |
---|---|
ON_CONTROL_REFLECT( wNotifyCode, memberFxn ) | afx_msg void memberFxn ( ); |
ON_NOTIFY_REFLECT( wNotifyCode, memberFxn ) | afx_msg void memberFxn ( NMHDR * pNotifyStruct, LRESULT* result ); |
ON_UPDATE_COMMAND_UI_REFLECT( memberFxn ) | afx_msg void memberFxn ( CCmdUI* pCmdUI ); |
ON_WM_CTLCOLOR_REFLECT( ) | afx_msg HBRUSH CtlColor ( CDC* pDC, UINT nCtlColor ); |
ON_WM_DRAWITEM_REFLECT( ) | afx_msg void DrawItem ( LPDRAWITEMSTRUCT lpDrawItemStruct ); |
ON_WM_MEASUREITEM_REFLECT( ) | afx_msg void MeasureItem ( LPMEASUREITEMSTRUCT lpMeasureItemStruct ); |
ON_WM_DELETEITEM_REFLECT( ) | afx_msg void DeleteItem ( LPDELETEITEMSTRUCT lpDeleteItemStruct ); |
ON_WM_COMPAREITEM_REFLECT( ) | afx_msg int CompareItem ( LPCOMPAREITEMSTRUCT lpCompareItemStruct ); |
ON_WM_CHARTOITEM_REFLECT( ) | afx_msg int CharToItem ( UINT nKey, UINT nIndex ); |
ON_WM_VKEYTOITEM_REFLECT( ) | afx_msg int VKeyToItem ( UINT nKey, UINT nIndex ); |
ON_WM_HSCROLL_REFLECT( ) | afx_msg void HScroll ( UINT nSBCode, UINT nPos ); |
ON_WM_VSCROLL_REFLECT( ) | afx_msg void VScroll ( UINT nSBCode, UINT nPos ); |
ON_WM_PARENTNOTIFY_REFLECT( ) | afx_msg void ParentNotify ( UINT message, LPARAM lParam ); |
ON_NOTIFY_REFLECT 和ON_CONTROL_REFLECT 宏具有一些变体,以允许多个对象(比如控件和其父窗口)处理指定消息。
Map entry | Function prototype |
---|---|
ON_NOTIFY_REFLECT_EX( wNotifyCode, memberFxn ) | afx_msg BOOL memberFxn ( NMHDR * pNotifyStruct, LRESULT* result ); |
ON_CONTROL_REFLECT_EX( wNotifyCode, memberFxn ) | afx_msg BOOL memberFxn ( ); |
三 处理反射消息的例子:一个可以重用的编辑控件
这个简单的例子创建了一个可重用的控件,名称为CYellowEdit。这个控件和CEdit控件基本相同,只是背景是黄色的。添加允许 CYellowEdit 控件显示不同颜色的成员函数会很容易。
具体步骤为:
- 在程序中添加一个对话框
- 用类向导创建一个派生于CEdit的类CYellowEdit
- 在CYellowEdit类中添加3个数据成员变量,前两个为 COLORREF 变量,用于保存文字颜色和背景颜色。第三个成员变量为CBrush对象,保存背景色的画刷。CBrush对象近允许创建一次画刷,之后仅仅只是引用它;当CBrush对象销毁时画刷资源自动销毁。
- 在类的构造函数中进行变量初始化
CYellowEdit::CYellowEdit()
{
m_clrText = RGB( 0, 0, 0 );
m_clrBkgnd = RGB( 255, 255, 0 );
m_brBkgnd.CreateSolidBrush( m_clrBkgnd );
}
- 利用类向导,在CYellowEdit中添加反射WM_CTLCOLOR 消息的处理函数。注意,消息名称列表中带“=”的消息表示这是个反射消息。类向导添加了如下代码
ON_WM_CTLCOLOR_REFLECT()
// Note: other code will be in between....
HBRUSH CYellowEdit::CtlColor(CDC* pDC, UINT nCtlColor)
{
// TODO: Change any attributes of the DC here
// TODO: Return a non-NULL brush if the
// parent's handler should not be called
return NULL;
}
- 将函数体替换为下面代码。下述代码指定文字颜色,文字背景色,控件其余部分的背景色。
pDC->SetTextColor( m_clrText ); // text
pDC->SetBkColor( m_clrBkgnd ); // text bkgnd
return m_brBkgnd; // ctl bkgnd
- 在你的对话框中添加一个编辑框控件,然后给这个编辑框添加一个关联控件变量。在添加成员变量对话框中,将控件类型选为"CYellowEdit"。别忘了设置对话框TAB顺序。当然,确保在对话框头文件中包含CYellowEdit类的头文件。
- 编译运行。编辑框的背景已变为黄色。