窗口应用程序是消息驱动的。用户对窗口的输入最终以消息的形式传递给窗口,窗口间的通信也依赖于消息。所谓“传递给窗口”是指系统调用了我们提供的、被称为窗口过程的函数,窗口过程负责对消息作出响应。窗口过程和窗口类绑定,创建窗口前必须注册窗口类。
1、注册窗口类
调用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;
必须指定窗口类名称,创建窗口时正是通过名称来和窗口类绑定。
2、创建窗口
调用CreateWindow创建窗口。
CreateWindow(
_In_opt_ LPCWSTR lpClassName, // 窗口类名称
_In_opt_ LPCWSTR lpWindowName, // 窗口标题
_In_ DWORD dwStyle, // 窗口样式
_In_ int X, // 窗口位置x坐标
_In_ int Y, // 窗口位置y坐标
_In_ int nWidth, // 窗口宽
_In_ int nHeight, // 窗口高
_In_opt_ HWND hWndParent, // 父窗口句柄
_In_opt_ HMENU hMenu, // 窗口菜单句柄
_In_opt_ HINSTANCE hInstance, // 应用程序句柄
_In_opt_ LPVOID lpParam); // 创建参数
在CreateWindow内部,lpParam参数赋值给一个CREATESTRUCTA结果体的lpCreateParams成员,然后CREATESTRUCTA指针作为WM_CREATE的lParam参数一起被发送到窗口过程。
3、显示窗口
ShowWindow负责把窗口显示在屏幕上。如果需要重绘窗口,还需要调用UpdateWindow,该函数会发送一条WM_PAINT消息到窗口。
4、消息循环
窗口应用程序都应该包含一个“消息循环”代码段。
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage从消息队列取出消息,TranslateMessage进行一些键盘消息的转换,DispatchMessage把消息发送给合适的窗口过程。
BOOL
WINAPI
GetMessage(
_Out_ LPMSG lpMsg,
_In_opt_ HWND hWnd,
_In_ UINT wMsgFilterMin,
_In_ UINT wMsgFilterMax);
lpMsg是一个MSG指针
typedef struct tagMSG {
HWND hwnd; // 接收该消息的窗口过程的窗口句柄
UINT message; // 消息标识符
WPARAM wParam; // 消息参数
LPARAM lParam; // 消息参数
DWORD time; // 消息进入消息队列时间
POINT pt; // 消息进入消息队列时鼠标位置
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
hWnd、wMsgFilterMin、wMsgFilterMax负责消息过滤。GetMessage可以取回[wMsgFilterMin, wMsgFilterMax]范围、目标窗口句柄为hWnd的消息。
当hWnd为NULL时,GetMessage可以取回当前线程的所有窗口消息和线程消息。
当hWnd为-1时,GetMessage可以只能取回当前线程的线程消息。
当GetMessage从消息队列取回WM_QUIT消息时返回0值,反则返回非0值。
5、窗口过程
定义格式:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
6、WM_PAINT
窗口出现无效区域时,系统发送WM_PAINT消息到窗口。WM_PAINT消息处理中,重绘工作总是从BeginPaint开始,到EndPaint结束。
如果背景未擦除,BeginPaint负责使用注册窗口时指定的画刷擦除背景,并使整个客户区有效,返回一个客户区的设备环境句柄。
EndPaint负责释放设备环境句柄。
7、关闭窗口
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
- 单击窗口的关闭按钮时,系统向窗口过程发送WM_SYSCOMMAND消息,窗口过程把 该消息传给DefWindowProc。
- DefWindowProc又给窗口过程发送WM_CLOSE消息,窗口过程再次把该消息传给DefWindowProc 。
- DefWindowProc调用DestroyWindow,给窗口过程发送WM_DESTROY消息。
- 最后我们窗口过程处理WM_DESTROY消息,PostQuitMessage会发送一条WM_QUIT消息,传参作为其wParam参数。
- GetMessage检索到WM_QUIT后退出消息循环。
8、队列消息和非队列消息
队列消息先被插入到消息队列中,经过消息循环代码段,由GetMessage取出,再由DispatchMessage调用窗口过程。
非队列消息由windows直接调用窗口过程。
队列消息主要由用户输入产生:按键消息(WM_KEYDOWN、WM_KEYUP)、字符消息(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)、鼠标点击(WM_LBUTTONDOWN)等。此外定时器消息(WM_TIMER)、重绘消息(WM_PAINT)和退出消息(WM_QUIT)也是。
非队列消息通常由windows函数产生。
需要注意的是,消息循环和窗口过程不是并发运行的,消息处理是以有序、同步方式进行的。但是,窗口过程处理消息过程中,可能产生另一个非队列消息,窗口过程将第二条消息处理完毕后才能继续处理第一条消息。所以,窗口过程必须是可重入的。