在介绍了Windows API的基本结构以后,我给大家介绍一下Windows下的消息机制。
我们知道,大多数Windows的应用程序都有自己的窗口,我们拖动鼠标,就能够改变窗口的大小、位置,而这一结果的实现过程,正是Windows的消息机制的体现。这一过程中,当用户改变窗口大小时,Windows给程序发送一条消息指出新窗口的大小。然后程序根据这条消息,适当调整窗口的大小,以反映大小变化。所谓的“Windows给程序发送消息”,是指Windows调用程序中的一个函数,该函数的参数描述了这个特定消息。这种位于Windows程序中的函数被称为“窗口过程”。
程序创建的每一个窗口都会有相关的窗口过程。这个窗口过程是一个函数,Windows是通过调用窗口过程来给应用程序发送消息的,应用程序就根据这一消息进行相应的处理,然后将控制反给Windows。Windows程序开始执行后,Windows为该程序建立一个“消息队列”。这个消息队列用来存放应用程序可能遇到的各种消息。程序中有一段代码,叫做“消息循环”,用来从“消息队列”中取出消息,并发送给相应的窗口过程。
下面我们看一个简单的例子,有助于大家更好的体会窗口消息:
/*------------------------------------------------------------HELLOWIN.C -- Displays "Hello, Windows 2000!" in client area(c) onefi, 2002------------------------------------------------------------*/#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;//声明函数int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){ static 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) ; //如果不是NT内核的系统,出现错误信息。 return 0 ; } hwnd = CreateWindow (szAppName, //程序的名称 TEXT ("The Hello Program"), //程序的标题 WS_OVERLAPPEDWINDOW, //程序的样式 CW_USEDEFAULT, //默认初始X的坐标 CW_USEDEFAULT, //默认初始Y的坐标 CW_USEDEFAULT, //默认下窗口的宽度 CW_USEDEFAULT, //默认下窗口的长度 NULL, //父窗口句柄 NULL, //菜单句柄 hInstance, //本程序的实例句柄 NULL) ; //附加项 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_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText (hdc, TEXT ("Hello, Windows 2000!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); //在客户区绘制“Hello,Windows2000!”的标志 EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ;}下面具体说明消息循环的那段代码:
while (GetMessage (&msg, NULL, 0, 0)){ TranslateMessage (&msg) ; DispatchMessage (&msg) ;}msg变量是类型为MSG结构,在第一章中给大家介绍过,其中的POINT也是一种结构,定义如下:
typedef struct tagPOINT{ LONG x; LONG y;} POINT , *POINT;消息循环以GetMessage调用开始,它从消息队列中提取一个消息:
GetMessage(&msg,NULL,0,0);这一调用传给Windows一个指向名字为msg的MSG结构的指针,然后用所接收来的消息填充到msg的各个域中。
WM_PAINT消息当客户区的一部分或者全部变为无效时,以至于必须将客户区重新绘制时,发送此消息。
在最初建立的客户区内,整个区域都是无效的,所以必须要画一些东西才行。而在移动或者改变大小时,客户区也变得无效,所以还是需要绘制整个区域。对于这个消息的处理,一般来讲,都是从 hdc = BeginPaint (hwnd, &ps) 开始的,又是以 EndPaint (hwnd, &ps) 结束的。在这两个函数中,第一个参数是客户区的句柄,第二个是指向结构为POINT类型的指针。在调用完BeginPaint函数后,需要调用 GetClientRect(hwnd,&rect) ,同样,第一个参数是窗口句柄,第二个参数是指向RECT结构的指针。这个函数的作用是将 rect 结构内所指的区域设置为客户区,自然大家就会想到,客户区是我们可以执行操作的地方。那么,我们就调用 DrawText 函数,在客户区内“绘制”一句话。
WM_DESTROY消息当用户单击Close按钮或者执行任意可以关闭程序的操作时,发送此消息。
本程序通过调用 PostQuitMessage 一标准的方式响应 WM_DESTROY 消息: PostQuitMessage(0); 这个函数在程序的消息队列中插入一个 WM_QUIT 消息,而 GetMessage 函数对于除 WM_QUIT 之外的所有消息均返回非0,所以只要接收到 WM_QUIT 消息,将退出 switch 循环。然后执行 return msg.wParam; ,msg是MSG结构的一个指针,其 wParam 域是传递给 PostQuitMessage 函数的值(这里通常是0)。最后返回语句退出 WinMain。