您的位置:首页 > 其它

MFC技术系列(二)--窗口消息(4)

2007-06-26 23:13 330 查看
接上篇, 窗口消息(3)

5. MFC中的消息分发

消息的Hook:标准的消息循环被MFC封装在AfxPumpMessage方法中。消息一旦被Dispatch,那么接下来如何进入到窗口的WndProc方法中呢?MFC在窗口创建时,使用AfxHookWindowCreate方法(使用SetWindowsHookEx API方法+WH_CBT参数),使得消息能够输出到一个全局方法AfxWndProcBase中(如果是静态链接到MFC,则为AfxWndProc)。以此为出发点,MFC通过消息的窗口句柄获得对应的窗口对象,然后调用其WndProc方法(在AfxCallWndProc中)。
在消息被分发前,有两点值得注意:
1. WM_KICKIDLE消息不会被分发。该消息为MFC私有消息。其处理方式请参见《对话框》一文。
2. AfxPreTranslateMessage先被调用。如果返回TRUE,那么消息将不会被进一步处理(分发)

在MFC中,根据分发机制的不同,可将消息分为两类,一类为命令消息,即:WM_COMMAND消息,包括:菜单响应消息,一些基础控件的通知消息(例如:BN_CLICKED,EN_UPDATE等)。它的分发路径为由顶向下,即:从应用主窗口向下传递。另一类为直接分发到窗口的消息,包括:鼠标消息、键盘消息。
通知消息还有一种形式,即:WM_NOTIFY消息。
实际上,从消息的角度来看,消息都是直接到窗口的。只有在MFC的框架下,才引申出消息分发和路由的概念。对于所谓的命令消息的传递,是由命令消息的接收窗口发起的,结合MFC的各种消息映射宏,能够方便地处理消息,最直接地就是避免了消息参数格式的转换,另外,消息可以灵活地被路由。

1.1 WM_COMMAND消息

下面的场景是在一个对话框上点击一个按钮后,程序调用堆栈的情况:
1.Dialog1.exe!CDialog1Dlg::OnBnClickedButton1() Line 157 C++
2.mfc71d.dll!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x0012fe38, unsigned int nID=1000, int nCode=0, void (void)* pfn=0x004114dd, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 89 C++
3.mfc71d.dll!CCmdTarget::OnCmdMsg(unsigned int nID=1000, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 396 + 0x27 C++
4.mfc71d.dll!CDialog::OnCmdMsg(unsigned int nID=1000, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 88 + 0x18 C++
5.mfc71d.dll!CWnd::OnCommand(unsigned int wParam=1000, long lParam=264964) Line 2550 C++
6.mfc71d.dll!CWnd::OnWndMsg(unsigned int message=273, unsigned int wParam=1000, long lParam=264964, long * pResult=0x0012f81c) Line 1759 + 0x1c C++
7.mfc71d.dll!CWnd::WindowProc(unsigned int message=273, unsigned int wParam=1000, long lParam=264964) Line 1745 + 0x1e C++
8.mfc71d.dll!AfxCallWndProc(CWnd * pWnd=0x0012fe38, HWND__ * hWnd=0x00030aba, unsigned int nMsg=273, unsigned int wParam=1000, long lParam=264964) Line 241 + 0x1a C++
9.mfc71d.dll!AfxWndProc(HWND__ * hWnd=0x00030aba, unsigned int nMsg=273, unsigned int wParam=1000, long lParam=264964) Line 389 C++
10.mfc71d.dll!AfxWndProcBase(HWND__ * hWnd=0x00030aba, unsigned int nMsg=273, unsigned int wParam=1000, long lParam=264964) Line 209 + 0x15 C++
上述内容中,除了前面已经提到的几个MFC中所有消息的入口及相关方法外,已经用红色标出了消息链条上更进一步的重要的方法,这些方法也是命令消息传递最基本的路线。其中,
CWnd::WndProc:对话框没有重载WndProc,所以这里调用的是基类CWnd的方法。该方法相当简单,先交给OnWndMsg虚方法,如果应用没有处理该消息,则交给DefWindowProc来处理。
CWnd::OnWndMsg:该方法为主要的消息分发方法。它先处理如下的特殊场景:
3. 如果是WM_COMMAND消息,则交给OnCommand虚方法处理
4. 如果是WM_NOTIFY消息,则交给OnNotify虚方法处理
5. 如果是WM_ACTIVATE消息,则交给_AfxHandleActivate全局方法处理
6. 如果是WM_SETCURSOR消息,则交给_AfxHandleSetCursor全局方法处理
7. 如果是ActiveX容器,且包含了无窗口ActiveX控件,那么交给该容器的HandleWindowlessMessage方法处理
8. 其余消息,则通过消息映射表找到应用中对应的处理函数,然后调用它。
CWnd::OnCommand:该方法只处理WM_COMMAND消息。在这个过程中,ReflectLastMsg方法将被调用,以便给子窗口一个处理该消息的机会。既然该消息是该子窗口发向父窗口的,现在又回到子窗口,因此被称为消息反射。对于WM_COMMAND消息的反射,MFC中的宏为ON_CONTROL_REFLECT。如果ReflectLastMsg被子窗口处理(返回TRUE),那么消息分发就此结束。
CDialog::OnCmdMsg:如果上述消息没有被子窗口处理,则进入该方法。该方法共有四个参数,对于WM_COMMAND消息,后两个参数均为零。它最先调用基类的方法,即CCmdTarget::OnCmdMsg,该方法中会遍历消息映射表,找到对应的处理函数,然后调用它(_AfxDispatchCmdMsg)。至此,消息分发结束。
上述堆栈为Dialog下的情况,下面是一个Doc-View的情况下堆栈:(在主菜单上执行一个菜单项,在View中响应)
SDI.exe!CSDIView::OnTest() Line 129 C++
SDI.exe!_AfxDispatchCmdMsg(CCmdTarget * pTarget=0x00ca55d0, unsigned int nID=32772, int nCode=0, void (void)* pfn=0x004b6474, void * pExtra=0x00000000, unsigned int nSig=53, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 89 C++
SDI.exe!CCmdTarget::OnCmdMsg(unsigned int nID=32772, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 396 + 0x27 C++
SDI.exe!CView::OnCmdMsg(unsigned int nID=32772, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 160 + 0x18 C++
SDI.exe!CFrameWnd::OnCmdMsg(unsigned int nID=32772, int nCode=0, void * pExtra=0x00000000, AFX_CMDHANDLERINFO * pHandlerInfo=0x00000000) Line 893 + 0x21 C++
SDI.exe!CWnd::OnCommand(unsigned int wParam=32772, long lParam=0) Line 2550 C++
SDI.exe!CFrameWnd::OnCommand(unsigned int wParam=32772, long lParam=0) Line 320 C++
SDI.exe!CWnd::OnWndMsg(unsigned int message=273, unsigned int wParam=32772, long lParam=0, long * pResult=0x0012fc50) Line 1759 + 0x1c C++
SDI.exe!CWnd::WindowProc(unsigned int message=273, unsigned int wParam=32772, long lParam=0) Line 1745 + 0x1e C++
SDI.exe!AfxCallWndProc(CWnd * pWnd=0x00ca5088, HWND__ * hWnd=0x00090778, unsigned int nMsg=273, unsigned int wParam=32772, long lParam=0) Line 241 + 0x1a C++
SDI.exe!AfxWndProc(HWND__ * hWnd=0x00090778, unsigned int nMsg=273, unsigned int wParam=32772, long lParam=0) Line 389 C++
绝大部分都与CDialog相同,只是CFrameWnd重载了一些方法,以完成命令消息到View和Document的路由,初始顺序是Active View-->this Frame window-->app(CWinApp),由此可见,默认的情况下,命令消息是不会在Inactive的视图中响应的。在View中,又重载了OnCmdMsg方法,其顺序是this view-->document of this view;在Document中,也重载了OnCmdMsg方法,其路由顺序是this document-->document template。由此可见,在上述这些类中,均可以响应命令消息。
OnCmdMsg为MFC中主要的命令消息路由虚方法,它由作为大多数MFC类的基类的CCmdTarget提供,当然也包括CDocument类。它的第二个参数nCode标识消息类型,其中,对于菜单响应消息,nCode均为CN_COMMAND,而相应的菜单状态响应消息,nCode则为CN_UPDATE_COMMAND_UI。
在CFrameWnd的OnCommand方法中,提供了应用响应Help消息(WM_COMMANDHELP)的机会,否则,会按照正常路径进入基类的OnCommand方法。关于MFC中的Help机制,可参见MSDN的“TN028: Context-Sensitive Help Support”。

1.2 WM_NOTIFY消息

上面的描述中已经提到,这类消息将在OnNotify虚方法中处理。该方法的逻辑同OnCommand逻辑类似,也会调用ReflectLastMsg方法,此时的反射消息宏为ON_NOTIFY_REFLECT。最后会调用OnCmdMsg方法。(注意:并不存在什么OnNotifyMsg方法!)。不同的是,OnCmdMsg的参数,其中,nCode组合了WM_NOTIFY,而pExtra则为一个指向AFX_NOTIFY结构的指针,其包含了NMHDR和处理的返回值。
由此可见,OnCmdMsg虚方法并不只是处理WM_COMMAND消息的。只有OnCommand虚方法用来专门处理WM_COMMAND消息。

1.3 直接分发的消息

鼠标消息将被发送到拥有光标的窗口,这通常是指光标上方的窗口,或者Capture了Mouse的窗口。键盘消息将被发送到有焦点的窗口。这些消息均是直接分发的消息。它们的消息传递路径相当简单,直接在OnWndMsg中就被处理了(遍历消息映射表,找到对应的处理函数)。

1.4 ON_UPDATE_COMMAND_UI

在菜单状态的更新上,该宏经常被用到。它的调用路径有所不同,需要说明一下。在“窗口消息的菜单消息”一节中,详细解释了菜单过程相关的各种消息。其中,菜单显示前会发送WM_INITMENUPOPUP消息。从上面的描述中,我们知道这个消息将被OnWndMsg的一般逻辑处理。菜单所在的窗口在MFC中由CFrameWnd封装,其OnInitMenuPopup方法响应该消息。也就是在这个方法中,增加了菜单状态的更新。MFC使用CCmdUI封装了菜单状态的更新,DoUpdate方法被用来触发更新。在该方法中,nCode为CN_UPDATE_COMMAND_UI的OnCmdMsg方法被调用,pExtra参数则为CCmdUI对象,而ON_UPDATE_COMMAND_UI则将这些基本参数和应用提供的菜单状态响应方法联系起来。应用通过CCmdUI类提供的方法来更改菜单的状态。另外,在DoUpate方法中,如果一个菜单项没有响应方法,则该菜单项将会被Disable。

1.5 PreTranslateMessage

前面提到,MFC将标准消息循环封装在了AfxPumpMessage中。在每个消息被Translate前,都将会调用一个全局方法AfxPreTranslateMessage,如果该方法返回TRUE,消息将不再下发。
该方法的基本步骤如下:
1. 如果是线程消息,则调用DispatchThreadMessageEx直接分发
2. 从消息的目标窗口向上到应用主窗口,依次调用每个窗口的PreTranslateMessage方法,直到返回TRUE或者到达主窗口。如果是FrameWnd,会在该方法中处理accelerator,即:调用TranslateAccelerator API完成加速键到命令消息的转换。
3. 如果上述窗口都返回FALSE(没有处理),那么处理如果主窗口是一个非模态对话框,则调用它的PreTranslateMessage。
因此,如果要对消息做预处理,则可以重载CWnd的PreTranslateMessage方法。

1.6 消息映射表

侯捷的《深入浅出MFC》中详细讲解了这部分内容,这里不再描述。

1.7 消息反射

在1.1节和1.2节中提到,消息在经过OnCommand,或者OnNotify过程中,将会被反射,即:ReflectLastMsg方法将被调用,该方法为静态方法。它先判断.如果是OLE控件,那么将调用该控件窗口的OnChildNotify方法;否则,直接调用OnChildNotify。(OnChildNotify被包装在了SendChildNotifyLastMsg方法中,通过_afxThreadState结构对象获得当前消息)。
OnChildNotify为虚方法。进入OnChildNotify后,如果是OLE控件,那么直接发送消息。注意此时,消息ID增加了一个基数,即:OCM_BASE(定义在olectl.h中),然后,对于WM_CTLCOLOR系列消息,如果返回值为NULL,表示该消息未处理,需要继续路由,否则,返回TRUE。如果是正常子窗口,直接调用ReflectChildNotify方法。
进入ReflectChildNotify方法后,对于需要处理的消息,其ID均被增加了WM_REFLECT_BASE基数(定义在afxmsg_.h中,该头文件定义了所有的MFC的消息映射宏)。具体分成四种情况:
1. 如果是某些特殊的消息,那么直接由CWnd::OnWndMsg接管
2. 如果是WM_COMMAND消息,由CWnd::OnCmdMsg接管
3. 如果是WM_NOTIFY消息,也由CWnd::OnCmdMsg接管
4. 如果是WM_CTLCOLOR系列消息,填充AFX_CTLCOLOR结构,然后直接由CWnd::OnWndMsg接管
注意:
特殊消息包括:
1.WM_HSCROLL, WM_VSCROLL,WM_PARENTNOTIFY,
其中,WM_PARENTNOTIFY只有当子窗口创建完成之前,或者销毁之前,或者鼠标键被按下时,系统将给父窗口发送此消息。
2.List box和combo box的一些消息,包括:
(WM_DRAWITEM,WM_MEASUREITEM,WM_DELETEITEM,WM_COMPAREITEM)
3.当list box具有LBS_WANTKEYBOARDINPUT风格时,当WM_KEYDOWN发生时,WM_VKEYTOITEM将被发送到其Owner窗口;当WM_CHAR发生时,WM_CHARTOITEM被发送.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: