控件通知消息
在《深度解析VC 中的消息(上)》中,我们提到了消息的分类有3 种:窗口消息、命令消息和控件通知消息,我们这里要谈的是最后一种:控件通知消息。
控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows 公 共控件如树状视图、列表视图等。例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息。她类似于命令消息,当用户与控件窗 口交互时,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示 数据。例如按下一个按钮,他向父窗口发送的消息也可以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。
控件通知消息主要由窗口类即直接或间接由CWND 类派生类处理。
控件通知格式
控件通知经历了一个演变过程,因而SendMessage( ) 的变量Message 、wParam 和lParam 有三种格式。
第一控件通知格式
第一控件通知格式只是窗口消息的子集。它的特征格式如下:WM_XXXX 。它主要来自下面的3 种消息类型:
(1) 表示一个控件窗口要么已经被创建或销毁,要么已经被鼠标单击的消息:WM_PARENTNOTIFY ;
(2) 发送到父窗口,用来绘制自身窗口的消息,例如: WM_CTLCOLOR 、WM_DRAWITEM 、WM_MEASUREITEM 、WM_DELETEITEM 、WM_CHARTOITEM 、WM_VKTOITEM 、WM_COMMAND 和WM_COMPAREITEM
(3) 有滚动调控件发送,通知父窗口滚动窗口的消息:WM_VSCROLL 和WM_HSCROLL
第二控件通知格式
第二控件通知格式与命令消息共享,它的特征格式如下:WM_COMMAND 。
在WM_COMMAND 中,lParam 用来区分是命令消息还是控件通知消息:如果lParam 为NULL ,则这是个命令消息,否则lParam 里面放的必然就是控件的句柄,是一个控件通知消息。对于wParam 则是低位放的是控件ID ,高位放的是相应的消息事件。
第三控件通知格式
这个才真正涉及到我们要讲的内容,同时他也是最为灵活的一种格式。它的特征格式如下:WM_NOTIFY 。
在WM_NOTIFY 中,lParam 中放的是一个称为 NMHDR 结构的指针。在wParam 中放的则是控件的ID 。
NMHDR 结构的由来
NMHDR 结构是很值得一提的,该结构包括有关制作该通知的控件的任何内容,而不受空间和类型的限制,他的来历也是很有意思的。
在最初的windows3.x 中,根本就不存在什么WM_NOTIFY ,控件通知它们父窗口,如鼠标点击, 控件背景绘制事件,通过发送一个消息到父窗口。简单的通知仅发送一个WM_COMMAND 消息,包含一个通知码和一个在wParam 中的控件ID 及一个在lPraram 中的控件句柄。这样一来,wParam 和lParam 就都被填充了,没有额外的空间来传递一些其它的消息,例如鼠标按下的位置和时间。
为了克服这个困难,windows3.x 就提出了一个比较低级的解决策略,那就是给一些消息添加一些附加消息,最为明显的就是控件自画用到的DRAWITEMSTRUCT 。不知道大家对这个结构熟悉不,不过,如果你是老手,你应该非常清楚这个结构,这个结构包含了9 个内容,几乎你需要控制的信息都给你提供了。为什么说它比较低级呢?因为不同的消息附加的内容不同,结果就是一盘散沙,非常混乱。
在win32 中,MS 又提出了一个更好的解决方案:引进NMHDR 结构。这个结构的引进就是消息统一起来,利用它可以传递复杂的信息。这个结构的布局如下:
NMHDR
{
HWnd hWndFrom ; 相当于原WM_COMMAND 传递方式的lParam
UINT idFrom ; 相当于原WM_COMMAND 传递方式的wParam (low-order )
UINT code ; 相当于原WM_COMMAND 传递方式的Notify Code(wParam"s high-order)
} ;
对于这个结构的应用于WM_NOTIFY 信息结构,结果WM_NOTIFY 就变成了:
A 、无附加信息。结构变得很简单,就是一个NMHDR 结构。
B 、有附加信息。定义一个大的结构,它的第一个元素就是NMHDR 结构,它的后面放置附加信息。
WM_NOTIFY 结构的好处
除了上面我们所说的好处外,WN_NOTIFY 还有自己的独特的好处:
由于在大结构中,第一个成员为NMHDR , 这样一来,我们就可以利用指向NMHDR 的指针来传递结构地址,根据指针的特性,无论消息有没有附加信息,这个指针都适用,也能够很方便的进行强制转换。
分析ON_NOTIFY
类向导可以创建ON_NOTIFY 消息映射入口并提供一个处理函数的框架,来处理 WM_NOTIFY 类型的消息。ON_NOTIFY 消息映射宏有如下语法.
ON_NOTIFY(wNotifyCode,id,memberFxn)
其中:wNotifyCode: 要处理的通知消息通知码。比如上面我们提到的LVN_KEYDOWN ;Id: 控件标识ID ;MemberFxn: 处理此消息的成员函数。
此成员函数有如下的原型声明:
afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result);
比如: 假设你想成员函数OnKeydownList1 处理ClistCtrl( 标识ID=IDC_LIST1 )的 LVN_KEYDOWN 消息, 你可以使用类向导添加如下的消息映射:
ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 )
在上面的例子中, 类向导提供如下函数:
void CMessageReflectionDlg::OnKeydownList1(NMHDR * pNMHDR, LRESULT* pResult)
{
LV_KEYDOWN* pLVKey= (LV_KEYDOWN*)pNMHDR;
*pResult = 0;
}
这时类向导提供了一个适当类型的指针,你既可以通过pNMHDR ,也可以通过 pLVKey 来访问这个通知结构。
ON_NOTIFY_RANGE
有时我们可能需要为一组控件处理相同的WM_NOTIFY 消息。这时需要使用ON_NOTIFY_RANGE 而不是ON_NOTIFY 。不过,很不幸的是,VC6 的ClassWizard 并不支持这个消息,所以我们必须手工添加。方法和一般的手工添加的消息一样,不过需要注意的是:
(1) 当你使用 ON_NOTIFY_RANGE 时, 你需要指定控件的ID 范围. 其消息映射入口及函数原型如下:
ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn )
其中:wNotifyCode: 消息通知码. 比如:LVN_KEYDOWN 。id: 第一控件的标识ID 。
idLast: 最后一个控件的标识ID 。(标识值一定要连续)memberFxn: 消息处理函数。
(2) 成员函数必须有如下原型申明:afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result );