1 自己的视窗
1.1 总体结构
进入Windows程式设计,实际上是在进行一种物件导向的程式设计(OOP)。桌面上最明显的视窗就是应用程式视窗。这些视窗含有显示程式名称的标题列、功能表甚至可能还有工具列和卷动列。另一类视窗是对话方块,它可以有标题列也可以没有标题列。装饰对话方块表面的还有各式各样的按键、单选按钮、核取方块、清单方块、卷动列和文字输入区域。其中每一个小的视觉物件都是一个视窗。更确切地说,这些都称为【子视窗】或【控制项视窗】或【子视窗控制项】。视窗以【讯息】的形式接收视窗的输入,视窗也用讯息与其他视窗通讯。对讯息的理解将是学习如何写作Windows程式所必须越过的障碍之一。 Windows会给程式发送讯息。
这里,Windows给程式发送讯息是指Windows呼叫程式中的一个函式,该函式的参数描述了这个特定的讯息。这种位于Windows程式中的函式称为【视窗讯息处理程式】。程式建立的每一个视窗都有相关的视窗讯息处理程式。这个讯息视窗处理程式是一个函式,既可以在程式中,也可以在动态连接程式库中。Windows通过呼叫视窗讯息处理程式来给视窗发送讯息。视窗讯息 处理程式通过此讯息进行处理,然后将控制传回Windows。
更确切地说,视窗通常是在【视窗类别】的基础上建立的。视窗类别标识了处理视窗讯息处理程式。使用视窗类别使多个视窗能够属于同一个视窗类别,并使用同一个视窗类别处理程式。
Windows程式开始执行后,Windows为该程式建立一个信息伫列。这个讯息伫列用来存放该程式可能建立的各种不同视窗讯息。程式中有一个小段程式码,叫做【讯息回圈】,用来从伫列中取出讯息,并且将它们发送给相应的视窗讯息处理程式。有些讯息直接发送给视窗讯息处理程式,不用放入讯息伫列中。
1.2 HelloWin程式
#include <windows.h>
#pragma comment(lib, "winmm.lib")
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
TCHAR szAppName[] = TEXT("HelloWin");
HWND hWnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}
hWnd = CreateWindow(szAppName, // windows class name
szAppName, // windows caption
WS_OVERLAPPEDWINDOW, // windows 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
ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
PlaySound(TEXT("Faded.wav"), NULL, SND_FILENAME | SND_ASYNC);
return 0;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rect);
DrawText(hdc, TEXT("Hello Windows NT!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
Windows函式呼叫
HelloWin至少呼叫了18个Windows函式。下面以它们出现的次序列出了这些函式以及各自的简明描述:
函式名称 | 函式功能 |
---|---|
LoadIcon | 载入图示供程式使用 |
LoadCursor | 载入滑鼠游标提供程式使用 |
GetStockObject | 取得一个图形物件(在这个例子中,是取得绘制视窗背景的画刷物件) |
RegisterClass | 为程式视窗注册窗口类别 |
MessageBox | 显示讯息方块 |
CreateWindow | 根据视窗类别建立一个视窗 |
ShowWindow | 在荧幕上显示视窗 |
UpdateWindow | 指示视窗自我更新 |
GetMessage | 从讯息伫列中获取讯息 |
TranslateMessage | 转译某些键盘讯息 |
DispatchMessage | 将讯息发送给视窗讯息处理程式 |
PlaySound | 播放一个音效档案 |
BeginPaint | 开始绘制视窗 |
GetClientRect | 取得显示区域的大小 |
DrawText | 显示字串 |
EndPaint | 结束绘制视窗 |
PostQuitMessage | 在讯息伫列中添加一个【退出程式】讯息 |
DefWindowProc | 执行内定讯息处理 |
大写字母识别字
HelloWin中有几个大写的识别字,这些识别字是在Windows表头档案中定义的。有些识别字含有两个字母或者三个字母的字首,这些字首后头接着一个底线:
- | - | - |
---|---|---|
CS_HREDRAW | DT_VECENTER | SND_FILENAME |
CS_VREDRAW | IDC_ARROW | WM_CREATE |
CW_USEDEFAULT | IDI_APPLICATION | WM_DESTROY |
DT_CENTER | WB_ICONERROR | WM_PAINT |
DT_SINGLELINE | SND_ASYNC | WS_OVERLAPPEDWINDOW |
这些都是简单的数值常数。字首指示该常数所属的类别。
字首 | 类别 |
---|---|
CS | 视窗类别样式 |
CW | 建立视窗 |
DT | 绘制文字 |
IDI | 图标ID |
IDC | 游标ID |
MB | 讯息方块 |
SND | 声音 |
WM | 视窗讯息 |
WS | 视窗样式 |
HelloWin还使用了Windows表头档案中定义的四种资料结构。
结构 | 含义 |
---|---|
MSG | 讯息结构 |
WNDCLASS | 视窗类别结构 |
PAINTSTRUCT | 绘图结构 |
RECT | 矩形结构 |
代号简介
最后,还有三个大写识别字,用于不同型态的【代号】:
识别字 | 含义 |
---|---|
HINSTANCE | 执行实体代号 |
HWND | 视窗代号 |
HDC | 装置内容代号 |
匈牙利表示法
变量名以一个或者多个小写字母开头,这些字母表示变量的资料型态。
字首 | 资料型态 |
---|---|
c | char或WCHAR或TCHAR |
by | BYTE(无正负号字元) |
n | short |
i | int |
x,y | int 分别用作x坐标和y坐标 |
cx,cy | int 分别用作x长度和y长度,c代表计数器 |
b或f | BOOL(int);f代表【旗标】 |
w | WORD(无正负号短整数) |
l | LONG(长整数) |
dw | DWORD(无正负号长整数) |
fn | function(函式) |
s | string(字串) |
sz | 以位元组值0结尾的字串 |
h | 代号 |
p | 指针 |
注册视窗类别
视窗依照某一视窗类别建立,视窗类别用以标识处理视窗讯息的视窗讯息处理程式。不同视窗可以依照同一种视窗类别建立。在建立是窗前,必须首先呼叫RegisterClass注册一个视窗类别。该函式只需要一个参数,即一个指向WNDCLASS的结构指针。此结构包括两个指向字串的栏位:
typedef struct tagWNDCLASSA
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
}WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR * LPWNDCLASSA;
其中lpfn字首代表【指向函式的长指针】(在Win32中短指针和长指针没有区别,这指示16位元Windows的遗物)。cb字首代表【位元组数】,而且通常作为一个常数来表示一个位元组的大小。h字首是一个代号,而hbr字首代表【一个画刷代号】。lpsz字首代表【指向以0结尾的字串指针】。
Unicode版的结构定义:
typedef struct tagWNDCLASSW
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;
与前者唯一的区别在于最后两个栏位定义为指向宽子串常数,而不是指向ASCII字串常数。
WINUSER.H定义了WNDCLASSA和WNDCLASSW结构以后,表头档案根据对UNICODE识别字的解释,定义了WNDCLASS和指向WNDCLASS的指针:
#ifdef UNICODE
typedef WNDCLASSW WNDCLASS;
typedef PWNDCLASSW PWNDCLASS;
typedef NPWNDCLASSW NPWNDCLASS;
typedef LPWNDCLASSW LPWNDCLASS;
#else
typedef WNDCLASSA WNDCLASS;
typedef PWNDCLASSA PWNDCLASS;
typedef NPWNDCLASS NPWNDCLASS;
typedef LPWNDCLASSA LPWNDCLASS;
#endif
本书后面列出结构时,将只列出功能相同的定义,对WNDCLASS就像这样:
typedef struct
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
}WNDCLASS, *PWNDCLASS;
在WNDCLASS结构中最重要的两个栏位是第二个和最后一个,第二个栏位(lpfnWndProc)是依据这个类别来建立的所有视窗讯息处理程式的位址。最后一个是视窗类别的文字名称,在只定义一个视窗的程式中,视窗类别名称通常定义为程式名称。
其他栏位依照下面的方法描述了视窗类别的一些特征:让我们依次看看WNDCLASS结构中的每个栏位:
wndclass.style = CS_HREDRAW | CS_VREDRAW;
使用C的位元【或】运算子结合了两个【视窗类别样式】识别字。在表头档案WINUSER.H中,已经定义了一整组以CS为字首的识别字:
#define CS_VREDRAW 0x0001
#define CS_HREDRAW 0x0002
#define CS_KEYCVTWINDOW 0x0004
#define CS_DBLCLKS 0x0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0x0080
#define CS_NOKEYCVT 0x0100
#define CS_NOCLOSE 0x0200
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000
#define CS_IME 0x00010000
由于每个识别字都可以在一个复合值中设置一个位元值,所以按这种方式定义的识别字通常称为【位元旗标】。
WNDCLASS结构的第二个栏位由以下叙述进行初始化:
wndclass.lpfnWndProc = WndProc;
下面两个栏位用于在视窗类别结构和Windows内部保存的视窗结构中预留一些额外空间:
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
程序可以根据需求来使用预留的空间。
下一个栏位是程式的执行代号(它也是WinMain的参数之一)
wndclass.hInstance = hInstance;
为应用程式设置一个图标语句:
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
图标是一个小的点阵图图像,后面将学习如何使用自定义的图标。
载入滑鼠游标语句:
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
设置窗口背景颜色:
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
下一个栏位指定视窗类别的功能表:
wndclass.lpszMenuName = NULL;
因为本应用程式中没有使用功能表,所以设置为NULL。最后给出一个类别名称:
wndclass.lpszClassName = szAppName;
现在有一个问题:如果用定义UNICODE识别字编译了程式,程式将呼叫RegisterClassW。该程式可以在Windows NT中执行良好,但是在Windows 98上,RegisterClassW并未真正执行到。函式有一个进入点,但是呼叫后只返回0,表明错误。对于Windows 98下执行的Unicode程式来说,这是一个通知使用者有问题的最佳时间。这是本书中多数程式处理RegisterClass函式呼叫的方式。
if(!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}
由于MessageBoxW是可以在Windows 98中执行的几个Unicode函式之一,所以以上代码在Windows 98中执行正常。
建立视窗
视窗类别定义了视窗的一般特征,因此可以使用同一个视窗类别建立许多不同的视窗。实际呼叫CreateWindow建立视窗时,可能指定有关视窗的更详细的信息。下面是CreateWindow呼叫:
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
);
在建立一个【最上层】视窗,如应用程式视窗时,注释为【父视窗代号】的参数设置为NULL。通常,如果视窗之间存在父子关系,则子视窗总是出现在父视窗的上面。应用程式视窗出现在桌面视窗的上面,但不必为呼叫CreateWindow而找出桌面视窗代号。
因为视窗没有功能表,所以【视窗功能表代号】也设定为NULL。【程式执行实体代号】设定为执行实体代号,它是作为WinMain的参数传递给这个程式的。最后,【建立参数】指针设置为NULL,可以用这个参数存取稍后程式中可能引用的资料。
CreateWindow传回被建立的视窗代号,该代号存放在变量hwnd中,后者被定义为HWND型态【视窗代号型态】。Windows中的每个视窗都有一个代号,程式用代号来使用视窗。许多Windows函式需要使用hwnd作为参数,这样,Windows才能知道函式是针对哪个视窗的。如果一个程式建立了许多视窗,则每个视窗均有一个代号。视窗代号是Windows程式所处理最重要的代号之一。
显示视窗
在CreateWindow呼叫传回之后,Windows内部已经建立了这个视窗。这就是说,Windows已经配置了一块记忆体,用来保存在CreateWindow呼叫中指定视窗的全部资讯跟一些其他资讯,而Windows稍后就是根据视窗代号找到这些咨询的。然而,光是这样,视窗并不会出现在显示器上。您还需要两个函式:
ShowWindow(hwnd, iCmdShow);
第一个参数是刚刚用CreateWindow建立的视窗代号。第二个参数是作为参数传给WinMain的iCmdShow。它确定是最初如何在荧幕上显示视窗,是一般大小、最小化还是最大化。ShowWindow函式在显示器上显示视窗。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则视窗的显示区就会被视窗类别中定义的背景画刷所覆盖。函式呼叫:
UpdateWindow(hwnd);
会重画显示区域。它经由发送给视窗讯息处理程式的WM_PAINT讯息做到这一点。
讯息回圈
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, *POINT;
讯息回圈以GetMessage开始,它从讯息伫列中取得一个讯息:
GetMessage(&msg, NULL, 0, 0);
这一呼叫创给Windows一个指针,执向名为msg的MSG结构。第二、第三和第四个参数设定为NULL或者0,表示程式接收它自己建立的所有视窗的所有讯息。Windows用从讯息伫列中取出的下一个讯息来填充讯息结构的各个栏位,结构的各个栏位包括:
- hwnd接收讯息的视窗代号。
- message讯息识别字。
- wParam是一个32位元的常数,其含义和数值根据讯息的不同而不同。
- lParam是一个32位元的讯息参数,其值与讯息有关。
- time:讯息放入讯息伫列中的时间。
- pt:讯息放入讯息伫列时的滑鼠坐标。
只要从讯息伫列中取出讯息的message栏位不为WM_QUIT,GetMessage就传回一个非零值。WM_QUIT讯息将导致GetMessage回传0.
TranslateMessage(&msg); // 将msg结构传给Windows,进行一些键盘转换。
DispatchMessage(&msg); // 将msg结构传回给Windows。
然后,Windows将讯息发送给适当的视窗讯息处理程式,让它进行处理。也就是说,Windows将呼叫视窗讯息处理程式,处理完讯息后,WndProc传回到Windows。此时,Windows还停留在DispatchMessage呼叫中。在结束DispatchMessage呼叫的处理之后,Windows回到主程式,并且从下一个GetMessage呼叫开始讯息回圈。
视窗讯息处理程式
视窗讯息处理程式可以任意命名(只要求不和其他名字发生冲突)。一个Windows程式可以包含多个视窗讯息处理程式。一个视窗讯息处理程式总是与呼叫RegisterClass注册的特定视窗类别相关联。CreateWindow函式根据特定视窗类别建立一个视窗。但依据一个视窗类别,可以建立多个视窗。
视窗讯息处理程式总是定义为如下:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
注意,视窗讯息处理程式的四个参数与MSG结构的前四个参数是相同的。
程式通常不直接呼叫视窗讯息处理程式,视窗讯息处理程式通常由Windows本身呼叫。通过呼叫SendMessage函式,程式能够直接呼叫它自己的视窗讯息处理程式。
处理讯息
视窗讯息处理程式在处理讯息时,必须传回0.视窗讯息处理程式不予处理的所有讯息应该被传给名为DefWindowProc的Windows函式。从DefWindowProc传回的值必须由视窗讯息处理程式传回。
在本章的程式中,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);
播放音效档案
视窗讯息处理程式接收到的第一个讯息-也是WndProc选择处理的第一个讯息:WM_CREATE。当Windows在WinMain中处理CreateWindow函式时,WndProc接收这个讯息。就是说,在HelloWin呼叫CreateWindow时,Windows将做一些它必须做的工作。在这些工作中,Windows呼叫WndProc,将第一个参数设定为视窗代号,第二个参数设定为WM_CREATE。WndProc处理WM_CREATE讯息并将控制传回给Windows。Windows然后可以从CreateWindow呼叫中传回到HelloWin中,继续在WinMain中进行下一步的处理。
PlaySound的第一个参数是音效档案的名称(它可能是在Control Panel的Sounds中定义的一种声音的别名,或者是一个程式资源)。第二个参数只有当音效档案是一种资源时才被使用。第三个参数指定一些选项。
WM_PAINT讯息
WndProc处理的第二个讯息为WM_PAINT。这个讯息在Windows程式设计中是很重要的。当视窗显示区域的一部分显示内容或者全部变为【无效】,以致于必须【更新画面】时,将由这个讯息通知程式。
显示区域的显示内容怎么会变得无效呢?在最初建立视窗的时候,整个显示区域都是无效的,因为程式还没有在视窗上画什么东西。第一个条WM_PAINT消息(通常发生在WinMain中呼叫UpdateWindow时)指示视窗讯息处理程式在显示区域上画一些东西。
在使用者改变HelloWin视窗大小后,显示区域的显示内容重新变得无效。HelloWin中wndclass结构的style栏位设定为标志CS_HREDRAW和CS_VREDRAW,这样的格式设置指示Windows,在视窗大小改变后,就把整个视窗显示内容当成无效。
当使用者将HelloWin最小化,然后再次将视窗恢复为以前大小时,Windows将不会保存显示区域的内容。在图形环境下,视窗显示区域涉及的资料量很大。因此,Windows令视窗无效,视窗讯息处理程式接收一条WM_PAINT讯息,并自动恢复其视窗内容。
在移动视窗以致其相互重叠时,Windows不保存一个视窗中被另一个视窗所遮盖的内容。在这一部分被遮盖之后,它就被标志为无效。视窗讯息处理程式接收到一条WM_PAINT讯息,以更新视窗的内容。
如果视窗讯息处理程式不处理WM_PAINT讯息,它们必须被传送给DefWinProc。DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使显示区域有效。
呼叫完BeginPaint之后,WndProc接着呼叫GetClientRect:
GetClientRect(hwnd, &rect);
第一个参数是程式视窗的代号。第二个参数是一个指针,指向一个RECT型态的rectangle结构。该结构有四个LONG栏位,分别为left、top、right和bottom。
WndProc除了将该RECT结构指标作为DrawText的第四个参数传递外,不再对它做其他处理:
DrawText(hdc, TEXT("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
DrawText可以输出文字。由于函式要输出文字,第一个参数是从BeginPaint传回的装置内容代号,第二个参数是要输出的文字,第三个参数是-1,指示字串是以位元组0终结的。最后一个参数是一系列位元旗标,指明字体显示效果。
WM_DESTROY消息
WM_DESTROY消息是一个重要讯息。这一讯息指示,Windows正在根据使用者的指示关闭视窗。该讯息是使用者单击Close按钮或者在程式中的系统功能表上选择Close时发生的。
HelloWin通过呼叫PostQuitMessage以标准方式回应WM_DESTROY讯息:
PostQuitMessage(0);
该函式在程式的讯息伫列中插入一个WM_QUIT讯息。
Windows程式设计的难点
程式的所有实际动作均是在视窗讯息处理程式中发生。Windows程式所做的一切,都是回应发送给视窗讯息处理程式的讯息。
有时候,DefWindowProc处理完讯息后会产生其他的讯息。例如,假设使用者执行HelloWin,并且使用者最终单击了Close按钮,或者假设使用键盘和滑鼠从系统功能表中选择了Close,DefWindowProc处理这一键盘或者滑鼠输入,在检测到使用者选择了Close选项后,它给视窗讯息处理程式发送一条WM_SYSCOMMAND讯息。WndProc将这个讯息传给DefWindowProc。DefWindowProc给视窗讯息处理程式发送一条WM_CLOSE讯息来回应之。WndProc再次将它传给DefWindowProc。DefWindowProc呼叫DestroyWindow来回应这条WM_CLOSE讯息。WndProc再呼叫PostQuitMessage,将一条WM_QUIT讯息放入讯息伫列中,以此来回应此讯息。这个讯息导致WinMain中的讯息回圈终止,然后程式结束。
伫列化讯息和非伫列化讯息
讯息被分为伫列化讯息和非伫列化讯息。伫列化的讯息是由Windows放入程式讯息伫列中的。在程式的讯息回圈中,重新传回并分配给视窗讯息处理程式。非伫列话的讯息在Windows呼叫视窗时直接送给视窗讯息处理程式。也就是说,伫列化的讯息被发送到讯息伫列,而非伫列化的信息被发送到视窗讯息处理程式。在任何情况下,视窗讯息处理程式都将获得视窗所有的讯息–包括伫列化和非伫列化的讯息。视窗讯息处理程式是视窗的讯息中心。
伫列化信息基本上都是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP讯息)、击键产生的字元(WM_CHAR)、滑鼠移动(WM_MOUSEMOVE)和滑鼠移动(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讯息则可能就是非伫列化的。
当一个讯息回圈从其讯息伫列中接收到一个讯息,然后呼叫DispatchMessage将讯息发送给视窗讯息处理程式时,直到视窗讯息处理程式将控制传回给Windows,DispatchMessage才能结束执行。当然,视窗讯息处理程式能呼叫给视窗讯息处理程式发送另一个讯息的函式。这时,视窗讯息处理程式必须在函式呼叫传回之前完成对第二个讯息的处理。那时视窗讯息处理程式处理最初的讯息。例如,当视窗程序呼叫UpdateWindow时,Windows将呼叫视窗讯息处理程式来处理WM_PAINT讯息。视窗讯息处理程式处理WM_PAINT讯息结束后,UpdateWindow呼叫将把控制传回给视窗讯息处理程式。