Windows 程序是以消息为基础,以事件驱动的 (message based, event driven) ,用户的操作如移动鼠标,敲击键盘等动作可看成是事件 Event, 这些事件会产生相应的消息,这些消息为硬件设备产生的消息,会被放在 System queue 中。除此之外, Windows 系统或其它 Windows 程序也有可能传送消息到 Application queue 中,当然也有 no queued message, 没有经过队列,直接发送到对应的窗口处理函数中
每个 Windows 程序都要有一个消息侦测回路:
---------------------------------------------------------------
Win32 环境下,消息侦测回路一般以如下形式放于 WinMain 函数中
MSG msg ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
GetMessage determines which message to dispatch.
DispatchMessage determines where to send the message and sends the message to the appropriate window procedure.
只要程序在运行, Windows 就会不断侦测,检测到相关消息后, Dispatch 到相应的窗口函数中,由窗口函数执行相应的动作。
每个窗口需要一个对应的窗口函数
-------------------------------------------------
这个窗口函数负责接收侦测回路 Dispatch 的消息,并执行相应的动作,该窗口函数也就是程序员需要负责设计的一个回调函数,之所以称之为回调是因为它完全是由 Windows 负责调用的。
那么为什么需要将窗口函数设计成回调函数呢?为什么不在 GetMessage 之后,直接调用窗口函数执行相关动作呢?这是因为除了你需要调用该函数,很多时候操作系统也需要调用(例如当一个窗口建立时, Windows 会产生 WM_CREATE 消息,直接送到窗口函数),把窗口函数设计成 Callback, 才能给操作系统提供一个接口
/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
------------------------------------------------------------*/
#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) ;
return 0 ;
}
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
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 ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -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) ;
}
1> RegisterClass 注册窗口类
——从 C++ 的角度考虑,相当于定义一个类;说明需要创建什么样的窗口
2> CreateWindow 生成一个窗口
——类定义完成后,相当于定义一个对象,即按照注册的窗口类型,实例化了一个窗口,CreateWindow 运行后,会产生一个 WM_CREATE 消息,不经过队列,直接给窗口函数,一般我们可以在窗口响应该消息时,做一些初始化的动作
3> ShowWindow 显示生成的窗口
——The first time an application calls ShowWindow , it should use the WinMain function's nCmdShow nCmdShow parameter. Subsequent calls to ShowWindow must use one of the values in the given list, instead of the one specified by the WinMain function's nCmdShow parameter. parameter as its
- Applications create their main window by calling CreateWindow with the WS_VISIBLE flag set.
- Applications create their main window by calling CreateWindow with the WS_VISIBLE flag cleared, and later call ShowWindow with the SW_SHOW flag set to make it visible.
当CreateWindow 的参数dwStyle中包含 WS_VISIBLE 时,不需要调用 ShowWindow 函数,程序运行后也可以显示窗口
当CreateWindow 的参数dwStyle中不包含 WS_VISIBLE 时,需要调用 ShowWindow 函数,且调用时包含SW_SHOW,才可以显示窗口
可以在上述代码中做如下实验:
屏蔽ShowWindow ,在CreateWindow 中添加 WS_VISIBLE ,程序运行后正常显示
屏蔽ShowWindow ,在CreateWindow 未包含 WS_VISIBLE ,程序运行后在任务管理器中可以看到运行的进程,但无法显示
4> UpdateWindow 发送 WM_PAINT 给窗口函数
The UpdateWindow function updates the client area of the specified window by sending a WM_PAINT message to the window if the window's update region is not empty. The function sends a WM_PAINT message directly to the window procedure of the specified window, bypassing the application queue. If the update region is empty, no message is sent.
The system sends message WM_PAINT when there are no other messages in the application's message queue. GetMessage returns the WM_PAINT message when there are no other messages in the application's message queue,
从 MSDN 上关于消息循环和 WM_PAINT 的描述中看, WM_PAINT 的优先级其实很低,在消息队列中先响应完其它消息后,再响应 WM_PAINT 的
UpdateWindow 发送的 WM_PAINT 消息,不经过消息队列,会直接发送到对应的窗口函数。因此,如果需要马上更新窗口时,就可以考虑调用UpdateWindow 函数
5> 程序运行过程中,消息回路不断 GetMessage ,如果该消息为 WM_QUIT, GetMessage 会返回 0 ,退出循环,结束整个程序
6> DispatchMessage 分派消息到窗口函数,窗口函数执行相关动作;
为什么点关闭的时候不直接发送 WM_QUIT ?而要绕一个大圈呢( WM_CLOSE -> WM_DESTORY -> WM_QUIT )这是因为操作系统与应该程序两者各施其职,彼此使用消息进行通信 :
7> 当用户关闭程序时,系统就会送出 WM_CLOSE 消息,通常程序的窗口函数不拦截此消息,由 DefWindowProc 去处理它
8> DefWindowProc 收到消息 WM_CLOSE 后,调用 DestoryWindow 把窗口清除,并送出 WM_DESTROY —— 此时界面没了
9> WM_DESTROY 的标准反应是调用 PostQuitMessage ;
10> PostQuitMessage 发出在 WM_QUIT ,当该消息被 GetMessage 取得时,也就跳出了循环,程序结束——此时整个应用程序进程结束
如果在第 9 步处理 WM_DESTORY 时,不让程序调用 PostQuitMessage ,则会出现程序界面关闭,但应用程序进程没关闭的现象(主程序没退出消息循环)