Windows 3.x下的通知消息
在windows 3.x下,控件将诸如鼠标点击、内容修改、选择事件、背景绘制等通知消息发送给父窗口处理。简单的通知消息以WM_COMMAND消息发送,并在wParam参数中存放通知码(如BN_CLICKED)和控件ID,在lParam参数中存放控件句柄。因为wParam与lParam都被使用了,也就没得办法去传递额外的数据。简单的通知消息也有传递额外数据的需求,举个例子,点击事件BN_CLICKED发生时,没有办法去传递鼠标当前的位置信息。
在windows 3.x下,解决上述不能传递额外数据的方法就是定义更多特殊目的的消息,如WM_CTLCOLOR、WM_VSCROLL、WM_HSCROLL、WM_DRAWITEM、WM_MEASUREITEM、WM_COMPAREITEM、WM_DELETEITEM、WM_CHARTOITEM、WM_VKEYTOITEM等等。这些消息可以反射回给控件自己处理,之后由另外一篇译文来介绍这项技术。
Win32下的通知消息
为了兼容存在于Windows 3.1中的诸多控件,Win32 API继续使用了Windows 3.x下的很多通知消息。Win32也为Windows 3.x下支持的控件增加了许多更精细、更复杂的控件(增强版),这些控件在发送通知时,往往需要传递额外的数据。Win32 API的设计者们觉得原来那种增加形如WM_*的Windows消息来解决问题的方式不可取,故设计了一种新的方案,只用一个WM_NOTIFY,这个消息可以传递大量数据。
WM_NOTIFY消息在wParam存放控件ID,而在lParam参数中存放了一个指向一块数据结构的指针,这块数据结构可以是NMHDR或任何第一个成员为NMHDR的更大块的数据结构,之后使用的时候,根据逻辑对这个指针进行相应的类型强转即可。
在大多数情况下,这个指针指向的是一块比NMHDR更大的数据结构,故你在使用时,通常需要进行类型强转。只有少部分公共通知消息(如以NM_打头的消息)及信息提示控件中的TTN_SHOW、TTN_POP通知消息才真正地使用NMHDR。
NMHDR结构包含句柄、发送此消息的控件ID以及消息码(如TTN_SHOW),NMHDR结构定义如下:
typedef struct tagNMHDR {
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR;
对于TTN_SHOW消息,上述结构的code将会被设置为TTN_SHOW。
大多数通知消息传递一个指向第一个成员为NMHDR的大块数据结构。举个例子,当一个按键在list view控件中按下时,List View控件会发送一个LVN_KEYDOW通知消息,这个通知消息的lParam存放的指针指向的数据结构为LV_KEYDOWN,LV_KEYDOWN结构如下:
typedef struct tagLV_KEYDOWN {
NMHDR hdr;
WORD wVKey;
UINT flags;
} LV_KEYDOWN;
注:因为LV_KEYDOWN的第一个成员是NMHDR,所以你可以将lParam中的指针强转成NMHDR*,也可强转成LV_KEYDOWN*。
Windows新控件的通用通知
Windows新控件存在一些通用通知,这个通知传送的是一个NMHDR结构,如下:
通知码(code)
发送来源
NM_CLICK
鼠标左健单击
NM_DBLCLK
鼠标左健双击
NM_RCLICK
鼠标右键单击
NM_RDBLCLK
鼠标右键双击
NM_RETURN
控件拥有输入焦点时,回国事件
NM_SETFOCUS
控件获得输入焦点
NM_KILLFOCUS
控件失去输入焦点
NM_OUTOFMEMORY
控件内存溢出
MFC框架宏ON_NOTIFY
CWnd::OnNotify函数处理通知消息,它的默认实现是检测消息映射数组,查找处理函数并调用。通常来说,你不需要去重写OnNotify,需要做的是针对各种控件事件增加相应的处理函数,并使用MFC提供的宏在消息映射表中添加处理项。
通过MFC提供的类向导,你可以创建ON_NOTIFY入口(即自动在消息映射表中添加处理项)。
ON_NOTIFY消息映射宏的法语如下:
ON_NOTIFY(wNotifyCode, id, memberFxn)
参数说明如下:
wNotifyCode:消息通知码,如LVN_KEYDOWN.
id:控制ID
memberFxn:处理函数,函数原型如下:
afx_msg void memberFxn(NMHDR* pNotifyStruct, LRESULT* result);
参数说明如下:
pNotifyStruct:指针,指向第一块内存为NMHDR的数据结束
result:返回结果
举例
假设有个ID为IDC_LIST1的CListCtrl,你要使用OnKeydonwList1来处理其LVN_KEYDOWN的通知消息,你可以使用类向导或手动在消息映射表中添加如下项:
ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeydownList1)
函数实现如下:
void CMessageReflectionDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
// TODO: Add your control notification handler
// code here
*pResult = 0;
}
ON_NOTIFY_RANGE
如果你想为一组控件ID相邻的控件处理同一个WM_NOTIFY消息,你可以使用ON_NOTIFY_RANGE宏来代替ON_NOTIFY。例如,你可能有一组按钮需要对一个通知消息做出一致的响应,就可使用此宏。
类向导没有提供ON_NOTIFY_RANGE宏的自动生成,所以需要自己手动添加。
宏ON_NOTIFY_RANGE原型如下:
ON_NOTIFY_RANGE(wNotifyCode, id, idLast, memberFxn)
参数说明如下:
wNotifyCode:通知码,如LVN_KEYDOWN
id:第一个控件ID
idLast:最后一个控件ID
memberFxn:处理函数,函数原型如下:
afx_msg void memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);
ON_NOTIFY_EX, ON_NOTIFY_EX_RANGE
如果你想让不止一个地方处理通知消息,那么你可以使用宏ON_NOTIFY_EX或ON_NOTIFY_EX_RANGE来实现,带有EX后缀的宏与之前版本的宏的唯一区别在于memberFxn的返回值是BOOL,当返回TRUE时,表示这个通知消息已经处理完毕了,不用再路由给其它地方处理了;而返回FALSE时,表示可以继续交给其它处理器来处理此通知消息。
类向导没有提供ON_NOTIFY_EX, ON_NOTIFY_EX_RANGE宏的自动生成功能,所以这两个宏也是需要你自己手动添加。
ON_NOTIFY_EX, ON_NOTIFY_EX_RANGE宏的原型如下:
ON_NOTIFY_EX(nCode, id, memberFxn)
ON_NOTIFY_EX_RANGE(wNotifyCode, id, idLast, memberFxn)
对应的memberFxn函数原型如下:
afx_msg BOOL memberFxn(UINT id, NMHDR* pNotifyStruct, LRESULT* result);