Windows 消息驱动
- 当Windows 向程序发送消息时,它调用程序中的一个函数,这个函数的参数精确地描述了Windows 发送的消息。在程序中称这个函数为窗口函数(Window Procedure)或消息处理函数。它是一个自定义的回调函数,原形如下。
- LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
- CALLBACK 宏是__stdcall 的意思,
- hWnd 参数标识了消息到达的窗口;
- uMsg 参数是一个被命名的常量(消息ID 号)
- wParam 和lParam 是消息的两个参数,其值取决于uMsg
- LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
在桌面上显示一个窗口的具体步骤,这就是主程序的结构流程。
- (1)注册窗口类(RegisterClassEx)
- (2)创建窗口(CreateWindowEx)
- (3)在桌面显示窗口(ShowWindow)
- (4)更新窗口客户区(UpdateWindow)
- (5)进入无限的消息获取和处理的循环。首先是获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则 GetMessage 函数返回 FALSE,整个消息循环结束。消息具体的处理过程是在 MainWndProc 函数中进行的。
一个 Windows 基本窗口的实现
// 窗口函数的函数原形
LRESULT CALLBACK MainWndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
char szClassName[] = "MainWClass";
WNDCLASSEX wndclass;
// 用描述主窗口的参数填充WNDCLASSEX结构
wndclass.cbSize = sizeof(wndclass); // 结构的大小
wndclass.style = CS_HREDRAW|CS_VREDRAW; // 指定如果大小改变就重画
wndclass.lpfnWndProc = MainWndProc; // 窗口函数指针
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 = szClassName ; // 窗口类的名称
wndclass.hIconSm = NULL; // 没有类的小图标
// 注册这个窗口类
::RegisterClassEx(&wndclass);
// 创建主窗口
HWND hwnd = ::CreateWindowEx(
0, // dwExStyle,扩展样式
szClassName, // lpClassName,类名
"My first Window!", // lpWindowName,标题
WS_OVERLAPPEDWINDOW, // dwStyle,窗口风格
CW_USEDEFAULT, // X,初始 X 坐标
CW_USEDEFAULT, // Y,初始 Y 坐标
CW_USEDEFAULT, // nWidth,宽度
CW_USEDEFAULT, // nHeight,高度
NULL, // hWndParent,父窗口句柄
NULL, // hMenu,菜单句柄
hInstance, // hlnstance,程序实例句柄
NULL) ; // lpParam,用户数据
if(hwnd == NULL)
{
::MessageBox(NULL, "创建窗口出错!", "error", MB_OK);
return -1;
}
// 显示窗口,刷新窗口客户区
::ShowWindow(hwnd, nCmdShow);
::UpdateWindow(hwnd);
// 从消息堆中取出消息
MSG msg;
while(::GetMessage(&msg, NULL, 0, 0))
{
// 转化键盘消息
::TranslateMessage(&msg);
// 将消息发送到相应的窗口函数
::DispatchMessage(&msg);
}
// 当GetMessage返回0时程序结束
return msg.wParam;
}
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
char szText[] = "最简单的窗口程序!";
switch (message)
{
case WM_PAINT: // 窗口客户区需要重画
{
HDC hdc;
PAINTSTRUCT ps;
// 使无效的客户区变的有效,并取得设备环境句柄
hdc = ::BeginPaint (hwnd, &ps) ;
// 显示文字
::TextOut(hdc, 10, 10, szText, strlen(szText));
::EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY: // 正在销毁窗口
// 向消息队列投递一个WM_QUIT消息,促使GetMessage函数返回0,结束消息循环
::PostQuitMessage(0) ;
return 0 ;
}
// 将我们不处理的消息交给系统做默认处理
return ::DefWindowProc(hwnd, message, wParam, lParam);
}
详细说明:
- 注册窗口类
- 注册窗口类的API 函数是RegisterClassEx,最后的“Ex”是扩展的意思,因为它是Win16 的RegisterClass 函数的扩展。一个窗口类定义了窗口的一些主要属性,如:图标、光标、背景色和负责处理消息的窗口函数等。这些属性定义在WNDCLASSEX 结构中。
- typedef struct _WNDCLASSEX {UINT cbSize; // WNDCLASSEX 结构的大小
- UINT style; // 从这个窗口类派生的窗口具有的风格
- WNDPROC lpfnWndProc;// 即 window procedure, 窗口消息处理函数指针
- int cbClsExtra; // 指定紧跟在窗口类结构后的附加字节数
- int cbWndExtra; // 指定紧跟在窗口事例后的附加字节数
- HANDLE hInstance; // 本模块的实例句柄
- HICON hIcon; // 窗口左上角图标的句柄
- HCURSOR hCursor; // 光标的句柄
- HBRUSH hbrBackground;// 背景画刷的句柄
- LPCTSTR lpszMenuName;// 菜单名
- LPCTSTR lpszClassName;// 该窗口类的名称
- HICON hIconSm; // 小图标句柄
- } WNDCLASSEX;
- wndclass.lpfnWndProc = MainWndProc; // 窗口函数指针
- lpfnWndProc 指定了基于此类的窗口的窗口函数,当窗口收到消息时Windows 即自动调用这个函数通知应用程序。
- hIcon 和hCursor 为要装载的图标和光标的句柄。
- wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); // 使用白色背景画刷
- WHITE_BRUSH 是一个Windows 预定义的画刷对象类型,也可以自定义
- WHITE_BRUSH 是一个Windows 预定义的画刷对象类型,也可以自定义
- ::RegisterClassEx(&wndclass); // 注册窗口类,失败返回0
- 创建窗口
- 用注册的窗口类的类名调用 CreateWindowEx 函数即可,失败返回NULL
- 第四个参数dwStyle 的值是 WS_OVERLAPPEDWINDOW,即重叠式窗口(Overlapped Window)。其他类型:
- WS_BORDER 创建一个单边框的窗口
- WS_CAPTION 创建一个有标题框的窗口(包括WS_BODER 风格)
- WS_CHIlD 创建一个子窗口。这个风格不能与WS_POPVP 风格合用
- WS_DISABLED 创建一个初始状态为禁止的子窗口。一个禁止状态的窗日不能接受来自用户的输人信息
- WS_DLGFRAME 创建一个带对话框边框风格的窗口。这种风格的窗口不能带标题条
- WS_HSCROLL 创建一个有水平滚动条的窗口
- WS_VSCROLL 创建一个有垂直滚动条的窗口
- WS_ICONIC 创建一个初始状态为最小化状态的窗口。与WS_MINIMIZE 风格相同
- WS_MAXIMIZE 创建一个具有最大化按钮的窗口。该风格不能和WS_EX_CONTEXTHELP 风格同时出现,同时必须指定WS_SYSMENU 风格
- WS_OVERLAPPED 产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。与WS_TILED风格相同
WS_OVERLAPPEDWINDOW 创建一个具有 WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU ,WS_THICKFRAME,WS_MINIMIZEBOX,WS_MAXMIZEBOX风格的层叠窗口 - WS_POPUP 创建一个弹出式窗口。该风格不能与WS_CHLD 风格同时使用
- WS_POPUPWINDOW 创建一个具有WS_BORDER,WS_POPUP,WS_SYSMENU 风格的窗口,WS_CAPTION 和WS_POPUPWINDOW 必须同时设定才能使窗口某单可见
- WS_SIZEBOX 创建一个可调边框的窗口,与WS_THICKFRAME 风格相同
- WS_SYSMENU 创建一个在标题条上带有窗口菜单的窗口,必须同时设定 WS_CAPTION 风格
- WS_THICKFRAME 创建一个具有可调边框的窗口,与WS_SIZEBOX 风格相同
- WS_VISIBLE 创建一个初始状态为可见的窗口
- 用注册的窗口类的类名调用 CreateWindowEx 函数即可,失败返回NULL
- 在桌面显示窗口
- ::ShowWindow(hwnd, nCmdShow);
- ::ShowWindow(hwnd, nCmdShow);
- 更新窗口客户区
- ::UpdateWindow(hwnd);
- 如果指定窗口的更新区域不为空的话,UpdateWindow 函数通过向这个窗口发送一个 WM_PAINT 消息更新它的客户区。当窗口显示在屏幕上时,窗口的客户区被在 WNDCLASSEX 中指定的刷子擦去了,调用 UpdateWindow 函数将促使客户区重画,以显示其内容。
- 如果指定窗口的更新区域不为空的话,UpdateWindow 函数通过向这个窗口发送一个 WM_PAINT 消息更新它的客户区。当窗口显示在屏幕上时,窗口的客户区被在 WNDCLASSEX 中指定的刷子擦去了,调用 UpdateWindow 函数将促使客户区重画,以显示其内容。
- ::UpdateWindow(hwnd);
- 进入无限的消息循环
- 在调用UpdateWindow 函数之后,整个窗口已经显示在桌面上,程序必须准备从用户接收键盘和鼠标输入了。Windows 为每个线程维护了一个消息队列,每当有一个输入发生,Windows 就把用户的输入翻译成消息放在消息队列中。利用 GetMessage 函数可以从调用线程的消息队列中取出一个消息来填充MSG 结构。如果消息队列中没有消息(即没有用户输入),这个函数会一直等待下去,直到有消息进入到消息队列为止。
- ::GetMessage(&msg, NULL, 0, 0);
- GetMessage 函数从消息队列中取得的消息如果不是WM_QUIT,则返回非零值。一个 WM_QUIT 消息会促使GetMessage 函数返回 0,从而结束消息循环。
- GetMessage 函数从消息队列中取得的消息如果不是WM_QUIT,则返回非零值。一个 WM_QUIT 消息会促使GetMessage 函数返回 0,从而结束消息循环。
- MSG 结构类型
- typedef struct tagMSG {
- HWND hwnd; // 消息要发向的窗口句柄
- UINT message; // 消息标识符,以WM_ 开头的预定义值(意为Window Message)
- WPARAM wParam; // 消息的参数之一
- LPARAM lParam; // 消息的参数之二
- DWORD time; // 消息放入消息队列的时间
- POINT pt; // 这是一个POINT 数据结构,表示消息放入消息队列时的鼠标位置
- } MSG, *PMSG ;
- typedef struct tagMSG {
- DispatchMessage 函数分发一个消息到对应窗口的窗口函数。在上面的例子中,窗口函数是MainWndProc。MainWndProc 处理消息后把控制权交给Windows,此时 DispatchMessage 函数仍然在继续工作,当它返回时,消息循环从调用GetMessage 函数开始进入下一轮循环。
- 在调用UpdateWindow 函数之后,整个窗口已经显示在桌面上,程序必须准备从用户接收键盘和鼠标输入了。Windows 为每个线程维护了一个消息队列,每当有一个输入发生,Windows 就把用户的输入翻译成消息放在消息队列中。利用 GetMessage 函数可以从调用线程的消息队列中取出一个消息来填充MSG 结构。如果消息队列中没有消息(即没有用户输入),这个函数会一直等待下去,直到有消息进入到消息队列为止。