现在的位置: 首页 > 综合 > 正文

MFC技术系列(二)–窗口消息(1)

2013年09月12日 ⁄ 综合 ⁄ 共 7917字 ⁄ 字号 评论关闭

本文将以实际的典型场景来解释发生的消息,从而能够将窗口的消息具体化,并能连贯起来。文中的内容绝大部分为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"解释了这些消息.

1          Mouse动作

1.1           MouseTitle bar上移动

下面的片段是MouseDialogCaption上移动时产生的片段:

<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可以用来设置光标的形状。其nHittestWM_NCHITTEST的代码,wMouseMsg参数表明了此时鼠标的动作

3.       WM_NCMOUSEMOVEpost消息,而且只有当窗口拥有光标时,系统才会发送该消息。如果一个窗口capture了光标,那么该消息不会被发送。

4.       WM_KICKIDLE为消息队列空闲时,系统发送的消息,以便程序可以在此时处理一些事情,比如:同步状态等。应用可以控制系统是否发送该消息。对对话框而言,就是设置DS_NOIDLEMSG风格。如果是主消息循环,那么CWinApp提供了OnIdle虚方法(实际为CWinThread的方法),其中lIdleCount可以用来度量消息队列空闲的时间长短。对于模态对话框,则需要响应WM_KICKIDLE消息。

1.2           MouseClient区域的移动

在鼠标进入客户区前,通常要经过非客户区,比如:窗口的Border。因此,在该场景中的消息序列中,前面的部分表明了这个过程,它重复了上面的场景,只是由于是从Border进入的,WM_NCHITTEST代码为HTBORDER。值得注意的是,如果是Resize窗口(WS_THICKFRAME或者WS_SIZEBOX),那么Border代码将具化成精确的含义的代码(HTSIZEFIRSTHTSIZELAST)。

接下来的消息序列相当简单,

<00035> 000C0BDC S WM_SETCURSOR hwnd:000C0BDC nHittest:HTCLIENT wMouseMsg:WM_MOUSEMOVE

<00036> 000C0BDC R WM_SETCURSOR fHaltProcessing:False

<00037> 000C0BDC P WM_MOUSEMOVE fwKeys:0000 xPos:376 yPos:149

注意,WM_MOUSEMOVEpost消息。对于几乎所有的鼠标消息,如果mouse没有被捕获(capture),那么将被发送到包含光标的窗口,否则发送到捕获鼠标的窗口。只有前景窗口可以Capture鼠标。使用SetCapture捕获鼠标,且一个时刻只能有一个窗口Capture鼠标。

1.3           Mouse的其它行为

鼠标的其它行为包括左、右、中键的按下(ButtonDown)和抬起(ButtonUp)。同时,又分为客户区和非客户区(NC开头)鼠标消息。这里暂且就不详细描述了(有时间,我将用专门的篇幅来解释这些行为)。补充一点,在任何鼠标动作时,都会产生WM_SETCURSOR消息,其参数之一会指明此时的鼠标动作。

2          窗口的状态

2.1           窗口销毁

以对话框为例:鼠标左键在对话框的Close按钮上点击。消息序列如下:

<00115> 001D0A94 S WM_SETCURSOR hwnd:001D0A94 nHittest:HTCLOSE wMouseMsg:WM_LBUTTONDOWN

<00116> 001D0A94 R WM_SETCURSOR fHaltProcessing:False

<00117> 001D0A94 P WM_NCLBUTTONDOWN nHittest:HTCLOSE xPos:439 yPos:238

<00118> 001D0A94 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:396 yPos:-12

<00119> 001D0A94 P WM_MOUSEMOVE fwKeys:MK_LBUTTON xPos:396 yPos:-12

<00120> 001D0A94 P WM_LBUTTONUP fwKeys:0000 xPos:396 yPos:-12

<00121> 001D0A94 S WM_CAPTURECHANGED hwndNewCapture:00000000

<00122> 001D0A94 R WM_CAPTURECHANGED

<00123> 001D0A94 S WM_SYSCOMMAND uCmdType:SC_CLOSE xPos:439 yPos:238

<00124> 001D0A94 S WM_CLOSE

<00125> 001D0A94 R WM_CLOSE

<00126> 001D0A94 R WM_SYSCOMMAND

<00127> 001D0A94 P WM_NCMOUSELEAVE

<00128> 001D0A94 P WM_COMMAND wNotifyCode:BN_CLICKED wID:IDCANCEL hwndCtl:00030AA0

<00129> 001D0A94 S WM_CTLCOLORBTN hdcButton:38010C99 hwndButton:00040A9E

<00130> 001D0A94 R WM_CTLCOLORBTN hBrush:0110005A

<00133> 001D0A94 S WM_SETFOCUS hwndLoseFocus:00040A9E

<00134> 001D0A94 R WM_SETFOCUS

<00135> 001D0A94 S WM_WINDOWPOSCHANGING lpwp:0012F7F0

<00136> 001D0A94 R WM_WINDOWPOSCHANGING

<00137> 001D0A94 S WM_WINDOWPOSCHANGED lpwp:0012F7F0

<00138> 001D0A94 R WM_WINDOWPOSCHANGED

<00139> 001D0A94 S WM_WINDOWPOSCHANGING lpwp:0012FC88

<00140> 001D0A94 R WM_WINDOWPOSCHANGING

<00141> 001D0A94 S WM_DESTROY

<00142> 001D0A94 R WM_DESTROY

<00143> 001D0A94 S WM_NCDESTROY

<00144> 001D0A94 R WM_NCDESTROY

其中,我们看到在按下TitleBar右上角的Close按钮后,系统还产生了WM_MOUSEMOVEWM_LBUTTONUP两个原本在客户区的鼠标消息。

接下来的消息中,<00127>比较有意思,WM_NCMOUSELEAVE,或许是因为窗口位置的改变造成的。<00128>消息实际为对对话框上的Cancel按钮的点击(00030AA0为该按钮的句柄)<00129>为针对对话框上有焦点控件的CTLCOLOR消息,此时对话框上焦点在OK按钮上。接着,就是对话框得到焦点WM_SETFOCUS消息,可以看出前一个焦点是就是CTLCOLOR消息的控件。

然后是窗口位置改变消息-->为客户区被销毁-->非客户区被销毁。WM_DESTROY发生时,表示窗口从屏幕消失,将要被销毁,此时,系统还会发送该消息到各个子窗口,此时子窗口还未被销毁。然而,WM_NCDESTROY发生时,子窗口已经被销毁,同时表示非客户区正在被销毁。该消息的响应函数中,可以释放任何已经分配的与该窗口相关的内存。

2.2           窗口激活

以对话框为例:对话框没有被激活(当前活动应用为另一个程序),鼠标左键在客户区空白区域点击。消息序列如下:

<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:0012F920

<00126> 002209E0 R WM_ACTIVATETOPLEVEL

<00127> 002209E0 S WM_CTLCOLORBTN hdcButton:5C010C54 hwndButton:001E0A80

<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:5C010C54 hwndButton:001E0A80

<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消息。

2.3           窗口创建

2.4           窗口调整尺寸

3          绘图消息

3.1           窗口被其它窗口覆盖,其它窗口移开时

当窗口为对话框时,消息序列如下:

<00004> 000C0BDC S WM_SYNCPAINT

<00005> 000C0BDC S WM_NCPAINT hrgn:E804073A

<00006> 000C0BDC R WM_NCPAINT

<00007> 000C0BDC S WM_ERASEBKGND hdc:18010CB0

<00008> 000C0BDC S WM_CTLCOLORDLG hdcDlg:18010CB0 hwndDlg:000C0BDC

<00009> 000C0BDC R WM_CTLCOLORDLG hBrush:0110005A

<00010> 000C0BDC R WM_ERASEBKGND fErased:True

<00011> 000C0BDC R WM_SYNCPAINT

<00012> 000C0BDC P WM_PAINT hdc:00000000

<00013> 000C0BDC S WM_CTLCOLORBTN hdcButton:2A010CCB hwndButton:000E0BDA

<00014> 000C0BDC R WM_CTLCOLORBTN hBrush:0110005A

<00015> 000C0BDC S WM_CTLCOLORBTN hdcButton:2A010CCB hwndButton:001C0BBA

<00016> 000C0BDC R WM_CTLCOLORBTN hBrush:0110005A

其中,

1WM_SYNCPAINT为操作系统同步不同线程的top-level窗口的绘制而发送的。应用不需要处理该消息,根据当前窗口的非客户区是否需要被绘制和背景是否必须被擦除,系统会将该消息转换成WM_NCPAINTWM_ERASEBKGND,应用可以处理这两个消息。

2. WM_CTLCOLORDLG:在系统决定绘制对话框客户区前(从消息序列可以看出,该消息在WM_ERASEBKGND之后发出),会发送该消息,应用可以使用传入的dc,设置文本的前景和背景色。在WM_CTLCOLOR开头的消息中,唯独该消息被发送到窗口本身,其它消息均发送到控件的Owner窗口,通常也就是所在的对话框。在本消息序列最后,能看到WM_CTLCOLORBTN消息,共有两个按钮。

3WM_PAINT:客户区绘制消息,为post消息。系统将在消息队列空闲,且存在无效区域时,发送该消息。既然是post消息,那么意味着是一种异步绘画。关于绘图,还存在一种直接(同步)绘图,请参见《DC和绘图》

4          菜单消息

5          MFC中的消息分发

 

(未完,待续......)

 

抱歉!评论已关闭.