本文将以实际的典型场景来解释发生的消息,从而能够将窗口的消息具体化,并能连贯起来。文中的内容绝大部分为VC提供的Spy++工具获得,小部分为通过程序获得。文中显示的片段多数为一个窗口中的消息,对于发往父窗口的消息,将会附带说明。
Windows的标准消息定义位于winuser.h中(%Program Files%/Microsoft Visual Studio .NET 2003/Vc7/PlatformSDK/include/winuser.h).可是,使用Spy++能够捕获所有窗口消息,经常会看到一些MSDN中未提及的消息.这一少部分消息的定义位于afxpriv.h中(%Program Files%/Microsoft Visual Studio .NET 2003/Vc7/atlmfc/include/afxpriv.h),这部分为MFC的内部消息(0x0360 ~ 0x037f),因此在MSDN中通常也找不到它们的踪迹.不过,MSDN中的"TN024: MFC-Defined Messages and Resources"解释了这些消息.
下面的片段是Mouse在Dialog的Caption上移动时产生的片段:
<00001> 00050592 S WM_NCHITTEST xPos:254 yPos:170 <00002> 00050592 R WM_NCHITTEST nHittest:HTCAPTION <00003> 00050592 S WM_SETCURSOR hwnd:00050592 nHittest:HTCAPTION wMouseMsg:WM_MOUSEMOVE <00004> 00050592 R WM_SETCURSOR fHaltProcessing:False <00005> 00050592 P WM_NCMOUSEMOVE nHittest:HTCAPTION xPos:254 yPos:170 <00006> 00050592 S WM_KICKIDLE其中,
1. WM_NCHITTEST:当鼠标在窗体移动时,系统会产生该消息。对于窗口的不同的区域,会返回不同的代码,。对于上述场景,为HTCAPTION,即title bar。如果是客户区,则都为HTCLIENT。其它代码都是用来区分非客户区的不同部分。
2. WM_SETCURSOR可以用来设置光标的形状。其nHittest同WM_NCHITTEST的代码,wMouseMsg参数表明了此时鼠标的动作
3. WM_NCMOUSEMOVE为post消息,而且只有当窗口拥有光标时,系统才会发送该消息。如果一个窗口capture了光标,那么该消息不会被发送。
4. WM_KICKIDLE为消息队列空闲时,系统发送的消息,以便程序可以在此时处理一些事情,比如:同步状态等。应用可以控制系统是否发送该消息。对对话框而言,就是设置DS_NOIDLEMSG风格。如果是主消息循环,那么CWinApp提供了OnIdle虚方法(实际为CWinThread的方法),其中lIdleCount可以用来度量消息队列空闲的时间长短。对于模态对话框,则需要响应WM_KICKIDLE消息。
在鼠标进入客户区前,通常要经过非客户区,比如:窗口的Border。因此,在该场景中的消息序列中,前面的部分表明了这个过程,它重复了上面的场景,只是由于是从Border进入的,WM_NCHITTEST代码为HTBORDER。值得注意的是,如果是Resize窗口(WS_THICKFRAME或者WS_SIZEBOX),那么Border代码将具化成精确的含义的代码(HTSIZEFIRST到HTSIZELAST)。
接下来的消息序列相当简单,
<00035> 000C 0BDC S WM_SETCURSOR hwnd: 000C 0BDC nHittest:HTCLIENT wMouseMsg:WM_MOUSEMOVE <00036> 000C 0BDC R WM_SETCURSOR fHaltProcessing:False <00037> 000C 0BDC P WM_MOUSEMOVE fwKeys:0000 xPos:376 yPos:149 注意,WM_MOUSEMOVE为post消息。对于几乎所有的鼠标消息,如果mouse没有被捕获(capture),那么将被发送到包含光标的窗口,否则发送到捕获鼠标的窗口。只有前景窗口可以Capture鼠标。使用SetCapture捕获鼠标,且一个时刻只能有一个窗口Capture鼠标。鼠标的其它行为包括左、右、中键的按下(ButtonDown)和抬起(ButtonUp)。同时,又分为客户区和非客户区(NC开头)鼠标消息。这里暂且就不详细描述了(有时间,我将用专门的篇幅来解释这些行为)。补充一点,在任何鼠标动作时,都会产生WM_SETCURSOR消息,其参数之一会指明此时的鼠标动作。
以对话框为例:鼠标左键在对话框的Close按钮上点击。消息序列如下:
<00115> 001D 0A 94 S WM_SETCURSOR hwnd:001D 0A 94 nHittest:HTCLOSE wMouseMsg:WM_LBUTTONDOWN <00116> 001D 0A 94 R WM_SETCURSOR fHaltProcessing:False <00117> 001D 0A 94 P WM_NCLBUTTONDOWN nHittest:HTCLOSE xPos:439 yPos:238 <00118> 001D 0A 94 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:396 yPos:-12 <00119> 001D 0A 94 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:396 yPos:-12 <00120> 001D 0A 94 P WM_LBUTTONUP fwKeys:0000 xPos:396 yPos:-12 <00121> 001D 0A 94 S WM_CAPTURECHANGED hwndNewCapture:00000000 <00122> 001D 0A 94 R WM_CAPTURECHANGED <00123> 001D 0A 94 S WM_SYSCOMMAND uCmdType:SC_CLOSE xPos:439 yPos:238 <00124> 001D 0A 94 S WM_CLOSE <00125> 001D 0A 94 R WM_CLOSE <00126> 001D 0A 94 R WM_SYSCOMMAND <00127> 001D 0A 94 P WM_NCMOUSELEAVE <00128> 001D 0A 94 P WM_COMMAND wNotifyCode:BN_CLICKED wID:IDCANCEL hwndCtl:00030AA0 <00129> 001D 0A 94 S WM_CTLCOLORBTN hdcButton: 38010C 99 hwndButton: 00040A 9E <00130> 001D 0A 94 R WM_CTLCOLORBTN hBrush: 0110005A <00133> 001D 0A 94 S WM_SETFOCUS hwndLoseFocus: 00040A 9E <00134> 001D 0A 94 R WM_SETFOCUS <00135> 001D 0A 94 S WM_WINDOWPOSCHANGING lpwp: 0012F 7F 0 <00136> 001D 0A 94 R WM_WINDOWPOSCHANGING <00137> 001D 0A 94 S WM_WINDOWPOSCHANGED lpwp: 0012F 7F 0 <00138> 001D 0A 94 R WM_WINDOWPOSCHANGED <00139> 001D 0A 94 S WM_WINDOWPOSCHANGING lpwp:0012FC88 <00140> 001D 0A 94 R WM_WINDOWPOSCHANGING <00141> 001D 0A 94 S WM_DESTROY <00142> 001D 0A 94 R WM_DESTROY <00143> 001D 0A 94 S WM_NCDESTROY <00144> 001D 0A 94 R WM_NCDESTROY其中,我们看到在按下TitleBar右上角的Close按钮后,系统还产生了WM_MOUSEMOVE和WM_LBUTTONUP两个原本在客户区的鼠标消息。
接下来的消息中,<00127>比较有意思,WM_NCMOUSELEAVE,或许是因为窗口位置的改变造成的。<00128>消息实际为对对话框上的Cancel按钮的点击(00030AA0为该按钮的句柄)。<00129>为针对对话框上有焦点控件的CTLCOLOR消息,此时对话框上焦点在OK按钮上。接着,就是对话框得到焦点WM_SETFOCUS消息,可以看出前一个焦点是就是CTLCOLOR消息的控件。
然后是窗口位置改变消息-->为客户区被销毁-->非客户区被销毁。WM_DESTROY发生时,表示窗口从屏幕消失,将要被销毁,此时,系统还会发送该消息到各个子窗口,此时子窗口还未被销毁。然而,WM_NCDESTROY发生时,子窗口已经被销毁,同时表示非客户区正在被销毁。该消息的响应函数中,可以释放任何已经分配的与该窗口相关的内存。
以对话框为例:对话框没有被激活(当前活动应用为另一个程序),鼠标左键在客户区空白区域点击。消息序列如下:
<00112> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00113> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00114> 002209E0 S WM_MOUSEACTIVATE hwndTopLevel:002209E0 nHittest:HTCLIENT uMsg:WM_LBUTTONDOWN <00115> 002209E0 R WM_MOUSEACTIVATE fuActivate:MA_ACTIVATE <00116> 002209E0 S WM_WINDOWPOSCHANGING lpwp:0012FC34 <00117> 002209E0 R WM_WINDOWPOSCHANGING <00118> 002209E0 S WM_WINDOWPOSCHANGED lpwp:0012FC34 <00119> 002209E0 R WM_WINDOWPOSCHANGED <00120> 002209E0 S WM_ACTIVATEAPP fActive:True dwThreadID:00000000 <00121> 002209E0 R WM_ACTIVATEAPP <00122> 002209E0 S WM_NCACTIVATE fActive:True <00123> 002209E0 R WM_NCACTIVATE <00124> 002209E0 S WM_ACTIVATE fActive:WA_CLICKACTIVE fMinimized:False hwndPrevious:(null) <00125> 002209E0 S WM_ACTIVATETOPLEVEL fActive:True dwThreadID: 0012F 920 <00126> 002209E0 R WM_ACTIVATETOPLEVEL <00127> 002209E0 S WM_CTLCOLORBTN hdcButton: 5C 010C 54 hwndButton:001E 0A 80 <00128> 002209E0 R WM_CTLCOLORBTN hBrush: 0110005A <00129> 002209E0 R WM_ACTIVATE <00130> 002209E0 S WM_SETCURSOR hwnd:002209E0 nHittest:HTCLIENT wMouseMsg:WM_LBUTTONDOWN <00131> 002209E0 R WM_SETCURSOR fHaltProcessing:False <00132> 002209E0 P WM_LBUTTONDOWN fwKeys:MK_LBUTTON xPos:289 yPos:111 <00133> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00134> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00135> 002209E0 S WM_SETCURSOR hwnd:002209E0 nHittest:HTCLIENT wMouseMsg:WM_MOUSEMOVE <00136> 002209E0 R WM_SETCURSOR fHaltProcessing:False <00137> 002209E0 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:289 yPos:111 <00138> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00139> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00140> 002209E0 S WM_SETCURSOR hwnd:002209E0 nHittest:HTCLIENT wMouseMsg:WM_MOUSEMOVE <00141> 002209E0 R WM_SETCURSOR fHaltProcessing:False <00142> 002209E0 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:289 yPos:111 <00143> 002209E0 S WM_CTLCOLORBTN hdcButton: 5C 010C 54 hwndButton:001E 0A 80 <00144> 002209E0 R WM_CTLCOLORBTN hBrush: 0110005A <00145> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00146> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00147> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00148> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00149> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00150> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00151> 002209E0 S WM_NCHITTEST xPos:345 yPos:253 <00152> 002209E0 R WM_NCHITTEST nHittest:HTCLIENT <00153> 002209E0 S WM_SETCURSOR hwnd:002209E0 nHittest:HTCLIENT wMouseMsg:WM_LBUTTONUP <00154> 002209E0 R WM_SETCURSOR fHaltProcessing:False <00155> 002209E0 P WM_LBUTTONUP fwKeys:0000 xPos:289 yPos:111其中,
1. 窗口因为鼠标点击由Inactive变为active,会产生WM_MOUSEACTIVATE。紧接着,窗口位置消息被发送。
2. 窗口激活消息组:
WM_ACTIVATEAPP:分别发送给被激活的窗口和另一个应用中失去激活状态的窗口。
WM_NCACTIVATE:指示非客户区需要响应窗口的激活。
WM_ACTIVATE:分别发送给应用中被激活和失去激活状态的窗口。激活的方式分为鼠标激活和其它方式激活,包括:键盘,该参数在该消息的wParam的低字节中。上述场景为鼠标激活。
WM_ACTIVATETOPLEVEL:激活top-level窗口,但却在另一个非主线程中。(MFC内部消息, 类似WM_ACTIVATEAPP,但适用于属于不同进程的窗口混合在一个单一的窗口层次中,在OLE应用中较为普遍)
3. 接着,OK按钮为默认焦点控件,需要绘制。因此,系统会发送一个WM_CTLCOLORBTN消息。如果为别的焦点控件,将会是别的CTLCOLOR消息。
4. 接着是我们熟悉的鼠标系列消息
(WM_SETCURSOR,WM_LBUTTONDOWN,WM_NCHITTEST,WM_LBUTTONUP)
其间,系统还会发送一个WM_CTLCOLORBTN消息。
当窗口为对话框时,消息序列如下:
<00004> 000C 0BDC S WM_SYNCPAINT <00005> 000C 0BDC S WM_NCPAINT hrgn:E 804073A <00006> 000C 0BDC R WM_NCPAINT <00007> 000C 0BDC S WM_ERASEBKGND hdc:18010CB0 <00008> 000C 0BDC S WM_CTLCOLORDLG hdcDlg:18010CB0 hwndDlg: 000C 0BDC <00009> 000C 0BDC R WM_CTLCOLORDLG hBrush: 0110005A <00010> 000C 0BDC R WM_ERASEBKGND fErased:True <00011> 000C 0BDC R WM_SYNCPAINT <00012> 000C 0BDC P WM_PAINT hdc:00000000 <00013> 000C 0BDC S WM_CTLCOLORBTN hdcButton: 2A 010CCB hwndButton:000E0BDA <00014> 000C 0BDC R WM_CTLCOLORBTN hBrush: 0110005A <00015> 000C 0BDC S WM_CTLCOLORBTN hdcButton: 2A 010CCB hwndButton: 001C 0BBA <00016> 000C 0BDC R WM_CTLCOLORBTN hBrush: 0110005A 其中, 1.WM_SYNCPAINT为操作系统同步不同线程的top-level窗口的绘制而发送的。应用不需要处理该消息,根据当前窗口的非客户区是否需要被绘制和背景是否必须被擦除,系统会将该消息转换成WM_NCPAINT和WM_ERASEBKGND,应用可以处理这两个消息。 2. WM_CTLCOLORDLG:在系统决定绘制对话框客户区前(从消息序列可以看出,该消息在WM_ERASEBKGND之后发出),会发送该消息,应用可以使用传入的dc,设置文本的前景和背景色。在WM_CTLCOLOR开头的消息中,唯独该消息被发送到窗口本身,其它消息均发送到控件的Owner窗口,通常也就是所在的对话框。在本消息序列最后,能看到WM_CTLCOLORBTN消息,共有两个按钮。 3.WM_PAINT:客户区绘制消息,为post消息。系统将在消息队列空闲,且存在无效区域时,发送该消息。既然是post消息,那么意味着是一种异步绘画。关于绘图,还存在一种直接(同步)绘图,请参见《DC和绘图》
(未完,待续......)