第三章 窗口与消息
1,windows窗口过程:应用程序所创建的每一个窗口都有一个与之关联的窗口过程,用于处理传递给窗口的消息。
2,窗口依据窗口类来创建。窗口类标识了用于处理传递给窗口的消息的窗口过程。窗口类的使用允许多个窗口共享一个窗口类,因而多个窗口可以使用相同的窗口过程。
3,Windows程序开始执行的时候,Windows首先为该程序创建一个消息队列。该消息队列中存放着应用程序可能创建的所有窗口的消息。Windows应用程序中一般包含一小段称为"消息循环"的代码,该段代码用于从消息队列中检索消息,并将其分发给相应的窗口过程。其他消息则不经过消息队列直接发送给窗口过程。
4,创建显示窗口的代码如下:
/*---------------------------------------------- HelloWindow.c Displays "Hello, Window." in client area code and compile it with visual studio 2008
----------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow) { static TCHAR szWndClassName[] = TEXT("Hello Window"); HWND hwnd; MSG msg; WNDCLASS wndClass;
//define property of window class wndClass.style = CS_HREDRAW|CS_VREDRAW; wndClass.lpfnWndProc = WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; wndClass.hIcon = LoadIcon(NULL, IDI_QUESTION); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = szWndClassName;
//register window class if(!RegisterClass(&wndClass)) { MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("Hint"), MB_ICONERROR|MB_OKCANCEL); return 0; }
//create the window based on the previous registered window class hwnd = CreateWindow(szWndClassName, //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
//show and refresh the window ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd);
//set message loop while(GetMessage(&msg, NULL, 0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
return msg.wParam; }
//window process function 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, Window."), -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); } |
5,一些windows api 函数
LoadIcon:加载图标
LoadCursor:加载鼠标光标
GetStockObject:获取图形对象
RegisterClass:为窗口注册窗口类
CreateWindow:创建窗口
ShowWindow:在屏幕中显示窗口
UpdateWindow:指示窗口对其自身进行重绘
GetMessage:从消息队列中获得消息
TranslateMessage:翻译一些键盘消息
DispatchMessage:将消息发送给窗口过程
BeginPaint:标明窗口绘制开始
EndPaint:结束窗口绘制
DrawText:显示文本字符串
PostQuitMessage:将退出消息插入消息队列
DefWindowProc:执行默认的消息处理
6,数值常量前缀:
CS | 类风格选项 |
CW | 创建窗口选项 |
DT | 文本绘制选项 |
IDI | 图标ID号 |
IDC | 光标的ID号 |
MB | 消息框选项 |
SND | 声音选项 |
WM | 窗口消息 |
WS | 窗口风格 |
7,一些数据类型
UINT:unsigned int
WPARAM: word param, 16位程序为unsigned short ,32位程序为unsigned int
LPARAM:long
LRESULT:long
WINAPI, CALLBACK: __stdcall
HINSTANCE: 实例句柄,程序本身
HWND:窗口句柄
HDC:设备环境句柄
句柄本质上是引用某个对象的数值,通常为32位。
8,窗口的注册
(1)窗口类结构WNDCLASS
参考资料:https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms633576(v=vs.85).aspx
WNDCLASS 的lpszClassName最长为256
(2)窗口类注册使用RegisterWindow或者RegisterWindowEx
参考资料:https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms633586(v=vs.85).aspx
RegisterWindow有RegisterWindowA和RegisterWindowW两个版本,程序使用哪个版本注册窗口类,决定着传递给窗口的消息是包含ASCII文本还是Unicode文本
关于Windows窗口类:
https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms633574(v=vs.85).aspx#class_styles
9,窗口的创建
由于窗口类只是定义了窗口的一般特征,因此基于同一窗口类可以创建许多不同的窗口。
关于窗口类和窗口的区别:
例:所有下压按钮都是基于相同的窗口类。与该窗口类关联的窗口过程位于Windows内部,并负责处理鼠标和键盘对按钮的输入,以及定义按钮在屏幕上的视觉外观。但是按钮位置尺寸不同,文字不同,这些就属于窗口定义。
窗口的WS_OVERLAPPEDWINDOW:层叠窗口,包含标题栏,标题左边的系统菜单按钮,一个窗口尺寸调整边框以及位于标题栏右方的三个按钮。
连续创建的窗口,Windows会沿水平和垂直做步长为1的偏移。
若新建的窗口为顶级窗口,父窗口句柄为NULL。通常,两个窗口存在父子关系时,子窗口总是位于父窗口的前方。应用程序窗口总是位于桌面窗口的前方。
10,窗口的显示
当CreateWindow返回时,窗口已在Windows内部被创建,即Windows已为窗口分配了一块内存。
11,消息循环
Windows为当前在其中运行的每一个Windows程序(进程)都维护了一个消息队列,
GetMessage:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
GetMessage(&msg, NULL, 0,0)
获取由该程序创建的所有窗口的消息。Windows用从消息队列中得到下一条消息来填充消息结构的各个字段。如果从消息队列中检索到的消息message字段不是WM_QUIT,返回非零,否则返回0.
TranslateMessage(&msg):将消息返回给Windows进行键盘消息的转换,
DispathMessage(&msg):将消息再次返回给Windows。
接下来,Windows将此消息发送给合适的窗口过程来处理。
12,窗口过程
一个Windows程序可以包含多个窗口过程,但是一个窗口过程总是与一个通过调用RegisterClass注册的特定窗口类相关联。
窗口过程由Windows进行调用。
13,消息的处理
通过switch-case结构来确定窗口过程受到的消息的类型以及相应的处理办法。窗口过程对消息进行处理后,应返回0。所有窗口过程不进行处理的消息都必须传给DefWindowProc的Widows函数。
14,WM_PAINT消息
当窗口的客户区部分或者全部无效且必须更新时,应用程序将得到此通知。这也意味着窗口必须被重绘。
当窗口创建后,客户区为无效的,通过调用ShowWindow使窗口重绘,窗口尺寸发生变化,也会重绘。最大最小化也会重绘。窗口被重叠后再次显示,也会发送WM_PAINT消息。
WM_PAINT以调用BeginPaint开始,EndPaint结束。
在调用BeginPaint期间,若客户区背景未被擦除,Windows会调用窗口类中的背景对其进行擦除。BeginPaint返回设备环境句柄。
若一个窗口过程不对WM_PAINT消息进行处理,必须交给DeWindowProc进行处理
15,Windows编程的若干难点
- WinMain函数只负责定义注册窗口类、创建窗口、从消息队列中检索和分发消息。
- WndProc会在如下情况被调用:新建窗口时,窗口被最终销毁时,用户从键盘输入字符时,用户从菜单中选择某个项时,用户用鼠标操作或者单击滚动条时,窗口的客户区需要重绘时。
16,队列消息和非队列消息
队列消息:Windows放入程序消息队列中的消息。在程序的消息循环中,消息被检索,然后被投递(post)到窗口过程中。主要是用户的输入产生,如WM_KEYDOWN,WM_KEYUP,WM_CHAR,WM_TIMER,WM_LBUTTONDOWN,WM_PAINT,WM_QUIT。
非队列消息:由Windows直接对窗口过程进行调用产生的,是被发送到窗口过程中的。包括消息队列以外的其它消息,如WM_CREATE,WM_SIZE,WM_SHOWWINDOW。处理菜单项选择的WM_COMMAND。
Windows程序可有多个执行线程。但每个线程的消息队列仅为那些其窗口过程在该线程内执行的窗口进行消息处理。即,消息循环和窗口过程不是并发运行的,当一个消息循环从其自身的消息队列中检索消息,并调用DispatchMessage函数将检索到的消息发送给窗口过程时,只有在窗口过程将控制权返回给Windows后,DispatchMessage才会返回。