持之以恒
分类: WINDOWS
2009-05-18 10:43:36
建立窗口
本文内容来自于<Windows 程序设计>一书
作者:Charles Petzold
译 者:余孟学
校 阅 者:林昭仁
窗口类定义了窗口的一般特征,因此可以使用同一窗口类别建立许多不同的窗口。实际调用CreateWindow建立窗口时,可能指定有关窗口的更详细的信息。
Windows程序设计新手有时会混淆窗口类别和窗口之间的区别,以及为什么一个窗口的所有特征不能被一次设定好。实际上,以这种方式分开这些样式信息是非常方便的。例如,所有的按钮窗口都可以依据同样的窗口类别来建立,与这个窗口类别相关的窗口消息处理程序位于Windows内部。由窗口类别来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕上的外观形象。从这一点看来,所有的按钮都是以同样的方式工作的。但是并非所有的按钮都是一样的。它们可以有不同的大小,不同的屏幕位置,以及不同的字符串。后面的这样一些特征是窗口定义的一部分,而不是窗口类定义的。
传递给RegisterClass函数的信息会在一个资料结构中设定好,而传递给CreateWindow函数的信息会在函数单独的参数中设定好。下面是HELLOWIN.C中的CreateWindows调用,每一个栏位都做了完整的说明:
hwnd =
CreateWindow (szAppName, // window class name
TEXT ( "The
Hello Program"), // window
caption
WS_OVERLAPPEDWINDOW,
// window style
CW_USEDEFAULT, // initial x
position
CW_USEDEFAULT,
// initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT,
// initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL)
; // creation parameters
标记为「window class name」的参数是szAppName,它含有字符串「HelloWin」-这是程序注册的窗口类别名称。这就是我们建立的窗口联结窗口类别的方式。
此程序建立的窗口是一个普通的重叠式窗口。它含有一个标题列,标题列左边有一个系统菜单按钮,标题列右边有缩小、放大和关闭图标,四周还有一个表示窗口大小的边框。这是标准样式的窗口,名为WS_OVERLAPPEDWINDOW,出现在CreateWindow的「窗口样式」参数中。如果看一下WINUSER.H,您将会发现此样式是几种位旗标的组合:
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \
WS_CAPTION
| \
WS_SYSMENU
| \
WS_THICKFRAME | \
WS_MINIMIZEBOX
| \
WS_MAXIMIZEBOX)
「窗口标题」是显示在标题列中的文字。
注释著「initial x position」和「initial y position」的参数指定了窗口左上角相对于屏幕左上角的初始位置。由于这些参数使用CW_USEDEFAULT识别字,指示Windows使用重叠窗口的内定位置。(CW_USEDEFAULT定义为0x80000000。)内定情况下,Windows依次对新建立的窗口定位,使各窗口左上角的垂直和水平距离在屏幕上按一定的大小递增。与此类似,注释著「initial x size」和「initial y size」的参数分别指定窗口的宽度和高度。同样使用了CW_USEDEFAULT识别字,表明希望Windows使用内定尺寸。
在建立一个「最上层」窗口,如应用程序窗口时,注释为「父窗口句柄」的参数设定为NULL。通常,如果窗口之间存在有父子关系,则子窗口总是出现在父窗口的上面。应用程序窗口出现在桌面窗口的上面,但不必为调用CreateWindow而找出桌面窗口的句柄。
因为窗口没有菜单,所以「窗口菜单句柄」也设定为NULL。「程序执行实体句柄」设定为执行实体句柄,它是作为WinMain的参数传递给这个程序的。最后,「建立参数」指针设定为NULL,可以用这个参数访问稍后程序中可能引用到的资料。
CreateWindow传回被建立的窗口的句柄,该句柄存放在变数hwnd中,后者被定义为HWND型态(「窗口句柄型态」)。Windows中的每个窗口都有一个句柄,程序用句柄来使用窗口。许多Windows函数需要使用hwnd作为参数,这样,Windows才能知道函数是针对哪个窗口的。如果一个程序建立了许多窗口,则每个窗口均有一个句柄。窗口句柄是Windows程序所处理最重要的句柄之一。
显示窗口
在CreateWindow调用传回之后,Windows内部已经建立了这个窗口。这就是说,Windows已经配置了一块内存,用来保存在CreateWindow调用中指定窗口的全部信息跟一些其它信息,而Windows稍后就是依据窗口句柄找到这些信息的。
然而,光是这样子,窗口并不会出现在显示器上。您还需要两个函数调用,一个是:
ShowWindow (hwnd,
iCmdShow) ;
第一个参数是刚刚用CreateWindow建立的窗口句柄。第二个参数是作为参数传给WinMain的iCmdShow。它确定最初如何在屏幕上显示窗口,是一般大小、最小化还是最大化。在开始菜单中安装程序时,使用者可能做出最佳选择。如果窗口按一般大小显示,那么WinMain接收到后传递给ShowWindow的就是SW_SHOWNORMAL;如果窗口是最大化显示的,则为SW_SHOWMAXIMIZED。而如果窗口只显示在工作列上,则是SW_SHOWMINNOACTIVE。
ShowWindow函数在显示器上显示窗口。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的显示区域就会被窗口类别中定义的背景画刷所覆盖。函数调用
UpdateWindow
(hwnd) ;
会重画显示区域。它经由发送给窗口消息处理程序(即HELLOWIN.C中的WndProc函数)一个WM_PAINT消息做到这一点。后面,我们将说明WndProc如何处理这个消息。
消息循环
调用UpdateWindow之后,窗口就出现在显示器上。程序现在必须准备读入使用者用键盘和鼠标输入的资料。Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。
程序通过执行一块称之为「消息循环」的程序码从消息队列中取出消息:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
msg变数是型态为MSG的结构,型态MSG在WINUSER.H中定义如下:
typedef struct
tagMSG
{
HWND hwnd
;
UINT message ;
WPARAM wParam ;
LPARAM lParam ;
DWORD time ;
POINT
pt ;
}
MSG, * PMSG ;
POINT资料型态也是一个结构,它在WINDEF.H中定义如下:
typedef struct
tagPOINT
{
LONG
x ;
LONG
y ;
}
POINT, * PPOINT;
消息循环以GetMessage调用开始,它从消息队列中取出一个消息:
GetMessage
(&msg, NULL, 0, 0)
这一调用传给Windows一个指针,指向名为msg的MSG结构。第二、第三和第四个参数设定为NULL或者0,表示程序接收它自己建立的所有窗口的所有消息。Windows用从消息队列中取出的下一个消息来填充消息结构的各个栏位,结构的各个栏位包括:
·
hwnd 接收消息的窗口句柄。在HELLOWIN程序中,这一参数与CreateWindow传回的hwnd值相同,因为这是该程序拥有的唯一窗口。
·
message 消息识别字。这是一个数值,用以标识消息。对于每个消息,均有一个对应的识别字,这些识别字定义于Windows表头文件(其中大多数在WINUSER.H中),以字首WM(「window message」,窗口消息)开头。例如,使用者将鼠标游标放在HELLOWIN显示区域之内,并按下鼠标左按钮,Windows就在消息队列中放入一个消息,该消息的message栏位等于WM_LBUTTONDOWN。这是一个常数,其值为0x0201。
·
wParam 一个32位的「message parameter(消息参数)」,其含义和数值根据消息的不同而不同。
·
lParam 一个32位的消息参数,其值与消息有关。
·
time 消息放入消息队列中的时间。
·
pt 消息放入消息队列时的鼠标座标。
只要从消息队列中取出消息的message栏位不为WM_QUIT(其值为0x0012),GetMessage就传回一个非零值。WM_QUIT消息将导致GetMessage传回0。
叙述
TranslateMessage
(&msg) ;
将msg结构传给Windows,进行一些键盘转换。(关于这一点,我们将在第六章中深入讨论。)
叙述
DispatchMessage
(&msg) ;
又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。这也就是说,Windows将调用窗口消息处理程序。在HELLOWIN中,这个窗口消息处理程序就是WndProe函数。处理完消息之后,WndProc传回到Windows。此时,Windows还停留在DispatchMessage调用中。在结束DispatchMessage调用的处理之后,Windows回到HELLOWIN,并且接著从下一个GetMessage调用开始消息循环。
窗口消息处理程序
以上我们所讨论的都是必要的开销:注册窗口类别,建立窗口,然后在屏幕上显示窗口,程序进入消息循环,然后不断从消息队列中取出消息来处理。
实际的动作发生在窗口消息处理程序中。窗口消息处理程序确定了在窗口的显示区域中显示些什么以及窗口怎样回应使用者输入。
在HELLOWIN中,窗口消息处理程序是命名为WndProc的函数。窗口消息处理程序可任意命名(只要求不和其它名字发生冲突)。一个Windows程序可以包含多个窗口消息处理程序。一个窗口消息处理程序总是与调用RegisterClass注册的特定窗口类别相关联。CreateWindow函数根据特定窗口类别建立一个窗口。但依据一个窗口类别,可以建立多个窗口。
窗口消息处理程序总是定义为如下形式:
LRESULT CALLBACK
WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
注意,窗口消息处理程序的四个参数与MSG结构的前四个栏位是相同的。第一个参数hwnd是接收消息的窗口的句柄,它与CreateWindow函数的传回值相同。对于与HELLOWIN相似的程序(只建立一个窗口),这个参数是程序所知道的唯一窗口句柄。如果程序是依据同一窗口类别(同时也是同一窗口消息处理程序)建立多个窗口,则hwnd标识接收消息的特定窗口。
第二个参数与MSG结构中的message栏位相同,它是标识消息的数值。最后两个参数都是32位的消息参数,提供关于消息的更多信息。这些参数包含每个消息型态的详细信息。有时消息参数是两个存放在一起的16位值,而有时消息参数又是一个指向字符串或资料结构的指针。
程序通常不直接调用窗口消息处理程序,窗口消息处理程序通常由Windows本身调用。通过调用SendMessage函数,程序能够直接调用它自己的窗口消息处理程序。我们将在后面的章节讨论SendMessage函数。
处理消息
窗口消息处理程序所接受的每个消息均是用一个数值来标识的,也就是传给窗口消息处理程序的message参数。Windows表头文件WINUSER.H为每个消息参数定义以「WM」(窗口消息)为字首开头的识别字。
一般来说,Windows程序写作者使用switch和case结构来确定窗口消息处理程序接收的是什么消息,以及如何适当地处理它。窗口消息处理程序在处理消息时,必须传回0。窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数。从DefWindowProc传回的值必须由窗口消息处理程序传回。
在HELLOWIN中,WndProc只选择处理三种消息:WM_CREATE、WM_PAINT和WM_DESTROY。窗口消息处理程序的结构如下:
switch (iMsg)
{
case WM_CREATE :
处理WM_CREATE消息
return 0 ;
case WM_PAINT :
处理WM_PAINT消息
return 0 ;
case WM_DESTROY :
处理WM_DESTROY消息
return 0 ;
}
return
DefWindowProc (hwnd, iMsg, wParam, lParam) ;
调用DefWindowProc来为窗口消息处理程序不予处理的所有消息提供内定处理,这是很重要的。不然一般动作,如终止程序,将不会正常执行。
播放音效文件
窗口消息处理程序接收的第一个消息-也是WndProc选择处理的第一个消息-是WM_CREATE。当Windows在WinMain中处理CreateWindow函数时,WndProc接收这个消息。就是说,在HELLOWIN调用CreateWindow时,Windows将做一些它必须做的工作。在这些工作中,Windows调用WndProc,将第一个参数设定为窗口句柄,第二个参数设定为WM_CREATE。WndProc处理WM_CREATE消息并将控制传回给Windows。 Windows然后可以从CreateWindow调用中传回到HELLOWIN中,继续在WinMain中进行下一步的处理。
通常,窗口消息处理程序在WM_CREATE处理期间进行一次窗口初始化。HELLOWIN对这个消息的处理中播放一个名为HELLOWIN.WAV的音效文件。
WndProc通过从窗口消息处理程序中传回0,结束了整个WM_CREATE的处理。
WM_PAINT消息
WndProc处理的第二个消息为WM_PAINT。这个消息在Windows程序设计中是很重要的。当窗口显示区域的一部分显示内容或者全部变为「无效」,以致于必须「更新画面」时,将由这个消息通知程序。
显示区域的显示内容怎么会变得无效呢?在最初建立窗口的时候,整个显示区域都是无效的,因为程序还没有在窗口上画什么东西。第一条WM_PAINT消息(通常发生在WinMain中调用UpdateWindow时)指示窗口消息处理程序在显示区域上画一些东西。
在使用者改变HELLOWIN窗口的大小后,显示区域的显示内容重新变得无效。读者应该还记得,HELLOWIN中wndclass结构的style栏位设定为标志CS_HREDRAW和CS_VREDRAW,这样的格式设定指示Windows,在窗口大小改变后,就把整个窗口显示内容当成无效。然后,窗口消息处理程序将收到一条WM_PAINT消息。
当使用者将HELLOWIN最小化,然后再次将窗口恢复为以前的大小时,Windows将不会保存显示区域的内容。在图形环境下,窗口显示区域涉及的资料量很大。因此,Windows令窗口无效,窗口消息处理程序接收一条WM_PAINT消息,并自动恢复其窗口的内容。
在移动窗口以致其相互重叠时,Windows不保存一个窗口中被另一个窗口所遮盖的内容。在这一部分不再被遮盖之后,它就被标志为无效。窗口消息处理程序接收到一条WM_PAINT消息,以更新窗口的内容。
对WM_PAINT的处理几乎总是从一个BeginPaint调用开始:
hdc = BeginPaint
(hwnd, &ps) ;
而以一个EndPaint调用结束:
EndPaint (hwnd,
&ps) ;
在这两个调用中,第一个参数都是程序的窗口句柄,第二个参数是指向型态为PAINTSTRUCT的结构指针。PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容。我们将在下一章中讨论该结构的各个栏位。现在我们只在BeginPaint和EndPaint函数中用到它。
在BeginPaint调用中,如果显示区域的背景还未被删除,则由Windows来删除。它使用注册窗口类别的WNDCLASS结构的hbrBackground栏位中指定的画刷来删除背景。在HELLOWIN中, 这是一个白色备用画刷。这意味著,Windows将通过把窗口背景设定为白色来删除窗口背景。BeginPaint调用令整个显示区域有效,并传回一个「设备上下文句柄」。设备上下文是指实体输出设备(如显示器)及其设备驱动程序。在窗口的显示区域显示文字和图形需要设备上下文句柄。但是从BeginPaint传回的设备上下文句柄不能在显示区域之外绘图,读者可以试一试。EndPaint释放设备上下文句柄,使之不再有效。
如果窗口消息处理程序不处理WM_PAINT消息(这是很罕见的),它们必须被传送给DefWindowProc。DefWindowProc只是依次调用BeginPaint和EndPaint,以使显示区域有效。
调用完BeginPaint之后,WndProc接著调用GetClientRect:
GetClientRect (hwnd, &rect) ;
第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT型态的rectangle结构。该结构有四个LONG栏位,分别为left、top、right和bottom。GetClientRect将这四个栏位设定为窗口显示区域的尺寸。left和top栏位通常设定为0,right和bottom栏位设定为显示区域的宽度和高度(图元点数)。
WndProc除了将该RECT结构指针作为DrawText的第四个参数传递外,不再对它做其它处理:
DrawText ( hdc, TEXT ("Hello, Windows
98!"), -1, &rect,
DT_SINGLELINE
| DT_CENTER | DT_VCENTER) ;
DrawText可以输出文字(正如其名字所表明的一样)。由于该函数要输出文字,第一个参数是从BeginPaint传回的设备上下文句柄,第二个参数是要输出的文字,第三个参数是 -1,指示字符串是以字节0终结的。
DrawText最后一个参数是一系列位旗标,它们均在WINUSER.H中定义(虽然由于其显示输出的效果,使得DrawText像一个GDI函数调用,但它确实因为相当高级的画图功能而成为User模组的一部分。此函数在/Platform SDK/Graphics and Multimedia Services/GDI/Fonts
and Text中说明)。旗标指示了文字必须显示在一行上,水平方向和垂直方向都位于第四个参数指定的矩形中央。因此,这个函数调用将让字符串「Hello, Windows 98!」显示在显示区域的中央。
一旦显示区域变得无效(正如在改变大小时所发生的情况一样),WndProc就接收到一个新的WM_PAINT消息。WndProc通过调用GetClientRect取得变化后的窗口大小,并在新窗口的中央显示文字。
WM_DESTROY消息
WM_DESTROY消息是另一个重要消息。这一个消息指示,Windows正在根据使用者的指示关闭窗口。该消息是使用者单击Close按钮或者在程序的系统菜单上选择 Close时发生的(在本章的后面,我们将详细讨论WM_DESTROY消息是如何生效的)。
HELLOWIN通过调用PostQuitMessage以标准方式回应WM_DESTROY消息:
PostQuitMessage
(0) ;
该函数在程序的消息队列中插入一个WM_QUIT消息。前面提到过,GetMessage对于除了WM_QUIT之外的从消息队列中取出的所有消息都传回非0值。而当GetMessage得到一个WM_QUIT消息时,它传回0。这将导致WinMain退出消息循环,并终止程序。然后程序执行下面的叙述:
return msg.wParam
;
结构的wParam栏位是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程序。
程序设计的难点
即使有了对HELLOWIN的说明,读者对程序的结构和原理可能仍然觉得神秘。在为传统环境编写简单的C程序时,整个程序可能包含在main函数中。而在HELLOWIN中,WinMain只包含了注册窗口类别,建立窗口,从消息队列中取出消息和发送消息所必须的程序码。
程序的所有实际动作均在窗口消息处理程序中发生。在HELLOWIN中,这些动作不多,WndProc只是简单地播放了一个音效文件并在窗口中显示一个字符串。但是在后面的章节中,读者将发现,Windows程序所作的一切,都是回应发送给窗口消息处理程序的消息。这是概念上的主要难点之一,在开始写作Windows程序之前,必须先搞清楚。
别调用我,我会调用您
前面我们提到过,程序写作者已经熟悉了使用操作系统调用的做法。例如,C程序写作者使用fopen函数打开文件。fopen函数最终通过调用操作系统来打开文件,这一点问题也没有。
但是Windows不同,尽管Windows有1000个以上的函数可供程序调用,但Windows也调用使用者程序(这就是一种典型的回调的形式),比如前面定义的窗口消息处理程序WndProc。窗口消息处理程序与窗口类别相关,窗口类别是程序调用RegisterClass注册的。依据该类别建立的窗口使用这个窗口消息处理程序来处理窗口的所有消息。Windows通过调用窗口消息处理程序对窗口发送消息。
在第一次建立窗口时,Windows调用WndProc。在窗口关闭时,Windows也调用WndProc。窗口改变大小、移动或者变成图标时,从菜单中选择某一项目、挪动卷动列、按下鼠标按钮或者从键盘输入字符时,以及窗口显示区域必须被更新时,Windows都要调用WndProc。
所有这些WndProc调用都以消息的形式进行。在大多数Windows程序中,程序的主要部分都用来处理消息。Windows可以发送给窗口消息处理程序的消息通常都以WM开头的名字标识,并且都在WINUSER.H表头文件中定义。
但在Windows中,这种概念扩展为包括一切事件。窗口中发生的一切都以消息的形式传给窗口消息处理程序。然后,窗口消息处理程序以某种方式回应这个消息,或者将消息传给DefWindowProc,进内联定处理。
在HELLOWIN中,窗口消息处理程序的wParam和lParam参数除了作为传递给DefWindowProc的参数外,不再有其它用处。这些参数给出了关于消息的其它信息,参数的含义与具体消息相关。
让我们来看一个例子。一旦窗口的显示区域大小发生了改变,Windows就调用窗口的窗口消息处理程序。窗口消息处理程序的hwnd参数是改变大小的窗口的句柄(请记住,一个窗口消息处理程序能处理依据同一个窗口类别建立的多个窗口的消息。参数hwnd让窗口消息处理程序知道是哪个窗口在接收消息)。参数message是WM_SIZE。消息WM_SIZE的参数wParam的值是SIZE_RESTORED、SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW或SIZE_MAXHIDE (在WINUSER.H表头文件中分别定义为数字0到4)。也就是说,参数wParam表明窗口是非最小化还是非最大化,是最小化、最大化,还是隐藏。
lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32位的lParam。WINDEF.H中提供了帮助程序写作者从lParam中取出这两个值的宏,我们将在下一章说明这个宏。
有时候,DefWindowProc处理完消息后会产生其它的消息。例如,假设使用者执行HELLOWIN,并且使用者最终单击了 Close 按钮,或者假设用键盘或鼠标从系统菜单中选择了 Close , DefWindowProc处理这一键盘或者鼠标输入,在检测到使用者选择了 Close 选项之后,它给窗口消息处理程序发送一条WM_SYSCOMMAND消息。WndProc将这个消息传给DefWindowProc。DefWindowProc给窗口消息处理程序发送一条WM_CLOSE消息来回应之。WndProc再次将它传给DefWindowProc。调用DestroyWindow来回应这条WM_CLOSE消息。DestroyWindow导致Windows给窗口消息处理程序发送一条WM_DESTROY消息。WndProc再调用PostQuitMessage,将一条WM_QUIT消息放入消息队列中,以此来回应此消息。这个消息导致WinMain中的消息循环终止,然后程序结束。
队列化消息与非队列化消息
我们已经谈到过,Windows给窗口发送消息,这意味著Windows调用窗口消息处理程序。但是,Windows程序也有一个消息循环,它调用GetMessage从消息队列中取出消息,并且调用DispatchMessage将消息发送给窗口消息处理程序。
那么,Windows程序是依次等待消息(类似于普通程序中相同的键盘输入),然后将消息送到某地方去的吗?或者,它是直接从程序外面接收消息的吗?实际上,两种情况都存在。
消息能够被分为「队列化的」和「非队列化的」。队列化的消息是由Windows放入程序消息队列中的。在程序的消息循环中,重新传回并分配给窗口消息处理程序。非队列化的消息在Windows调用窗口时直接送给窗口消息处理程序。也就是说,队列化的消息被「发送」给消息队列,而非队列化的消息则「发送」给窗口消息处理程序。任何情况下,窗口消息处理程序都将获得窗口所有的消息--包括队列化的和非队列化的。窗口消息处理程序是窗口的「消息中心」。
队列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。队列化消息还包含时钟消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。
非队列化消息则是其它消息。在许多情况下,非队列化消息来自调用特定的Windows函数。例如,当WinMain调用CreateWindow时,Windows将建立窗口并在处理中给窗口消息处理程序发送一个WM_CREATE消息。当WinMain调用ShowWindow时,Windows将给窗口消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain调用UpdateWindow时,Windows将给窗口消息处理程序发送WM_PAINT消息。键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。例如,用键盘或鼠标选择了一个菜单项时,键盘或鼠标消息就是队列化的,而说明菜单项已选中的WM_COMMAND消息则可能就是非队列化的。
这一过程显然很复杂,但幸运的是,其中的大部分是由Windows解决的,不关我们的程序的事。从窗口消息处理程序的角度来看,这些消息是以一种有序的、同步的方式进出的。窗口消息处理程序可以处理它们,也可以不处理。
当我说消息是以一种有序的同步的方式进出时,我是说首先消息与硬件的中断不同。在一个窗口消息处理程序中处理消息时,程序不会被其它消息突然中断。
虽然Windows程序可以多线程执行,但每个线程的消息队列只为窗口消息处理程序在该线程中执行的窗口处理消息。换句话说,消息循环和窗口消息处理程序不是并发执行的。当一个消息循环从其消息队列中接收一个消息,然后调用DispatchMessage将消息发送给窗口消息处理程序时,直到窗口消息处理程序将控制传回给Windows,DispatchMessage才能结束执行。
当然,窗口消息处理程序能调用给窗口消息处理程序发送另一个消息的函数。这时,窗口消息处理程序必须在函数调用传回之前完成对第二个消息的处理。那时窗口消息处理程序将处理最初的消息。例如,当窗口程序调用UpdateWindow时,Windows将调用窗口消息处理程序来处理WM_PAINT消息。窗口消息处理程序处理WM_PAINT消息结束以后,UpdateWindow调用将把控制传回给窗口消息处理程序。
这也就是说窗口消息处理程序必须是可重入。在大多数情况下,这不会带来问题,但是程序写作者应该意识到这一点。例如,假设您在窗口消息处理程序中处理一个消息时设置了一个静态变数,然后调用了一个Windows函数。在这个函数传回时,您还能保证那个变数的值还是原来那个吗?难说--很可能您调用的Windows函数产生了另外一个消息,并且窗口消息处理程序在处理这个消息时改变了该变数的值。这也是在编译Windows程序时,有些编译最佳化选项必须关闭的原因之一。
在许多情况下,窗口消息处理程序必须保存它从消息中取得的信息,并在处理另一个消息时使用这些信息。这些信息可以储存在窗口的静态(static)变数或整体变数中。
当然,读者将在下面几章对此有一个更清楚的了解,因为窗口消息处理程序将处理更多的消息。