这是一篇关于MFC里消息的路由问题,由于水平有限,难免有错误,如有发现,请指出。感激之至。
我打算从OnWndMsg()开始讲,这是窗口真正处理消息的函数。在这函数里对三种消息进行了处理。
1.标准Window消息,正常处理就行了(具体怎样请参考<<深入浅出MFC>>)在这里不是重点
2.WM_COMMAND消息,会调用OnCommand()
3.WM_NOTIFY消息,会调用OnNotify().
然后开始WM_NOTIFY消息:
控件通知消息,是指这样一种消息,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及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
用到的宏是:ON_COMMAND
第二控件通知格式
第二控件通知格式与命令消息共享,它的特征格式如下:WM_COMMAND。
在WM_COMMAND中,lParam用来区分是命令消息还是控件通知消息:如果lParam为NULL,则这是个命令消息,否则lParam里面放的必然就是控件的句柄,是一个控件通知消息。对于wParam则是低位放的是控件ID,高位放的是相应的消息事件。
用到的宏是:ON_CONTROL,按钮的点击通知消息就是用到了此宏,ON_BN_CLICKED(ID,FUNC)就是ON_CONROL(ID,BN_CLICKED,FUNC).
第三控件通知格式
这个才真正涉及到我们要讲的内容,同时他也是最为灵活的一种格式。它的特征格式如下:WM_NOTIFY。
在WM_NOTIFY中,lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。
用到的宏是:ON_NOTIFY
具体通知消息采用哪种显示,是MFC内部的事,我们在手工添加的时候一定要注意。
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)
};
类向导可以创建ON_NOTIFY消息映射入口并提供一个处理函数的框架,来处理 WM_NOTIFY类型的消息。ON_NOTIFY消息映射宏有如下语法:ON_NOTIFY(wNotifyCode,id,memberFxn)
有时我们可能需要为一组控件处理相同的WM_NOTIFY消息。这时需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY。
下面介绍发射机制:
由上面的介绍,在命令消息中也有可以是通知消息
如果在OnCommnd中要进行判断。判断的方法:如果lParam是有效的控件句柄就是通知消息,如果不是命令消息,现在不考虑是命令消息的情况。如果是通知消息,则调用ReflectLastMsg()给子控件一次处理的机会。如果没处理,则会调用把通知消息转换成命令消息并调用OnCmdMsg。
如果在OnNotify()中:首先调用ReflectLastMsg()给子控件一次处理的机会。如果没处理,则会调用把通知消息转换成命令消息并调用OnCmdMsg。
以下就是重点了。
1.ReflectLastMsg
BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult) { // get the map, and if no map, then this message does not need reflection CHandleMap* pMap = afxMapHWND(); //........ CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild); //...... return pWnd->SendChildNotifyLastMsg(pResult); }
主要是调用子类的SendChildNotifyLastMsg(...)。
2.SendChildNotifyLastMsg
BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); return OnChildNotify(pThreadState->m_lastSentMsg.message, pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult); }
调用子窗口的OnChildNotify函数进行处理,此函数是虚函数。
3.OnChildNotify()
BOOL CWnd::OnChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { //..... return ReflectChildNotify(uMsg, wParam, lParam, pResult); }
调用子类的ReflectChildNotify()
4.ReflectChildNotify()
BOOL CWnd::ReflectChildNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { switch (uMsg) { // normal messages (just wParam, lParam through OnWndMsg) case WM_HSCROLL: case WM_VSCROLL: case WM_PARENTNOTIFY: case WM_DRAWITEM: case WM_MEASUREITEM: case WM_DELETEITEM: case WM_VKEYTOITEM: case WM_CHARTOITEM: case WM_COMPAREITEM: // reflect the message through the message map as WM_REFLECT_BASE+uMsg return CWnd::OnWndMsg(WM_REFLECT_BASE+uMsg, wParam, lParam, pResult); // special case for WM_COMMAND case WM_COMMAND: { // reflect the message through the message map as OCM_COMMAND int nCode = HIWORD(wParam); if (CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_COMMAND), NULL, NULL)) { if (pResult != NULL) *pResult = 1; return TRUE; } } break; // special case for WM_NOTIFY case WM_NOTIFY: { // reflect the message through the message map as OCM_NOTIFY NMHDR* pNMHDR = (NMHDR*)lParam; int nCode = pNMHDR->code; AFX_NOTIFY notify; notify.pResult = pResult; notify.pNMHDR = pNMHDR; return CWnd::OnCmdMsg(0, MAKELONG(nCode, WM_REFLECT_BASE+WM_NOTIFY), ¬ify, NULL); } // other special cases (WM_CTLCOLOR family) default: if (uMsg >= WM_CTLCOLORMSGBOX && uMsg <= WM_CTLCOLORSTATIC) { // fill in special struct for compatiblity with 16-bit WM_CTLCOLOR AFX_CTLCOLOR ctl; ctl.hDC = (HDC)wParam; ctl.nCtlType = uMsg - WM_CTLCOLORMSGBOX; //ASSERT(ctl.nCtlType >= CTLCOLOR_MSGBOX); ASSERT(ctl.nCtlType <= CTLCOLOR_STATIC); // reflect the message through the message map as OCM_CTLCOLOR BOOL bResult = CWnd::OnWndMsg(WM_REFLECT_BASE+WM_CTLCOLOR, 0, (LPARAM)&ctl, pResult); if ((HBRUSH)*pResult == NULL) bResult = FALSE; return bResult; } break; } return FALSE; // let the parent handle it }
ReflectChildNotify()把消息转换成REFLECT的形式如:
WM_CTLCOLOR 经过处理后变成了ON_WM_CTLCOLOR_REFLECT;WM_MEASUREITEM则变成ON_WM_MEASUREITEM_REFLECT。
(1) WM_COMMAND 转换成 ON_CONTROL_REFLECT;
(2) WM_NOTIFY 转换成 ON_NOTIFY_REFLECT;
(3) ON_UPDATE_COMMAND_UI 转换成 ON_UPDATE_COMMAND_UI_REFLECT;
5.再调用子控件的OnCmdMsg对消息进行处理。
利用方面:
如果不是自定义的控件,在对话框中,可以右键点击增加通知消息,这时子控件没有处理通知消息。
如果是自定义控件,我们任然可以是父对话框里处理通知消息,但是我们也可以在子控件里通知,在增加消息
处理的地方,有的消息前面有个“=”就表示是通知消息。