第一个Windows窗口应用程序
-
创建一个窗口应用程序有4个步骤:
- 注册窗口类
- 创建窗口
- 显示窗口
- 更新窗口
-
窗口类 WNDCLASS
-
窗口类不是C++中的类,可以理解为“类别”
-
窗口类是用来创建窗口的模板
-
每一个窗口类都有一个窗口过程(WndProc),负责处理发送该类窗口的所有消息
-
在创建某个类型的窗口前,必须先注册该窗口类
-
窗口类有3种
- 系统窗口类
- 系统内部使用的
- 应用程序全局窗口类
- 注册窗口时以 CS_GLOBALCLASS 标志注册的窗口类(就是当 style 属性中有 CS_GLOBALCLASS 时)
- 这个窗口类在这个进程中,所有模块都可以使用(模块一般是动态库模块),一些程序的皮肤库很多都是以这种形式实现的
- 应用程序局部窗口类
- 注册窗口时没有以 CS_GLOBALCLASS 标志注册的窗口类(就是当 style 属性中没有 CS_GLOBALCLASS 时)
- 这个窗口类只能在模块范围内使用,对其他模块不可见
- 系统窗口类
-
// 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;
-
窗口类的 style 属性可选择的值。如果要同时使用多个,可以用 | 连接
- CS_HREDRAW 当水平长度改变或移动窗口时,重画整个窗口
- CS_VREDRAW 当垂直长度改变或移动窗口时,重画整个窗口
- CS_NOCLOSE 禁止系统菜单的关闭选项
- CS_DBLCLKS 允许向窗口发送双击鼠标键的信息
- CS_DROPSHADOW 开启边框阴影
-
-
使用 RegisterClass 函数注册窗口类
- 他的参数只有一个,就是一个 WNDCLASS 结构体的指针
- 如果函数成功,返回值是一个 ATOM 类型(本质上就是unsigned short类型),是一个唯一标识
- 如果函数失败,返回0,可以调用 GetLastError 函数获取失败的原因
-
ATOM WINAPI RegisterClass( const WNDCLASS *lpWndClass //指向WNDCLASS结构的长指针 );
-
使用 CreateWindow 函数创建窗口
-
HWND WINAPI CreateWindow ( LPCTSTR lpClassName, //RegisterClass 注册的窗口类名称 LPCTSTR lpWindowName, //窗口名称 DWORD dwStyle, //窗口的样式 int x, //初始x坐标(左上角的起始点) int y, //初始y坐标(左上角的起始点) int nWidth, //窗口的宽度, CW_USEDEFAULT 表示默认宽度和高度 int nHeight, //窗口的宽度, CW_USEDEFAULT 表示默认宽度和高度 HWND hWndParent, //父窗口句柄 HMENU hMenu, //窗口菜单的句柄(如果窗口有菜单栏) HINSTANCE hInstance, //模块实例的句柄 LPVOID lpParam //通过 WM_CREATE 消息的 IParam 参数指向的 CREATESTRUCT 结构(还没学到,暂时传NULL) )
- 第一个参数表示窗口类的名称,跟 WNDCLASS 的最后一个参数一致
- 第二个参数是窗口的标题
- 第三个参数 dwStyle 是窗口的样式,可取值有
- WS_BORDER 创建具有边框的窗口
- WS_CAPTION 创建有标题栏的窗口
- WS_CHILD 创建一个子窗口
- WS_VISIBLE 创建初始可见的窗口
- WS_MAXIMIZEBOX 创建一个窗口,具有一个最大化按钮
- WS_MINIMIZEBOX 创建一个窗口,具有一个最小化按钮
- WS_OVERLAPPEDWINDOW 创建一个具有标题栏、最大化、最小化、关闭按钮、可调整大小的窗口。WS_TILEDWINDOW 是 WS_OVERLAPPEDWINDOW 的别名
- 最后一个参数还没学到,暂时传NULL
- 如果函数成功了,返回值是一个 HWND 类型,是一个窗口句柄。如果失败则返回NULL,可以通过 GetLastError 函数获取更多失败信息
- 在函数返回之前,CreateWindow 会发送 WM_CREATE 消息给窗口过程
-
-
使用 ShowWindow 函数显示窗口
-
BOOL WINAPI ShowWindow ( HWND hWnd, //窗口的句柄 int nCmdShow //控制如何显示窗口 )
- 第一个参数是窗口的句柄
- 第二个参数表示如何显示窗口,是int类型
- 第一次调用 ShowWindow 时,应该使用 WinMain 函数的 nCmdShow 参数值作为其参数值
- 随后对 ShowWindow 的调用必须使用给定列表中的一个值,而不是 WinMain 函数的 nCmdShow 参数值(而事实上可以用WinMain函数的nCmdShow,不是硬性要求)
- 如果窗口先前可见,则返回值不为0。如果窗口先前被隐藏,则返回值为0
-
-
使用 UpdateWindow 函数更新窗口
-
BOOL WINAPI UpdateWindow ( HWND hWnd //窗口的句柄 )
- 只有一个参数,是窗口句柄
- 如果函数成功,则返回值为非0,如果失败则返回值为0
- UpdateWindow 通过发送 WM_PAINT 到指定窗口的窗口过程来更新(绕过消息队列)。如果更新区域为空则不发送消息
-
-
窗口过程 WNDPROC
- windows应用程序采用的是消息机制:用户的任何操作呢都会发送相应的消息,然后相应的消息呢丢给窗口过程函数来进行处理
- WNDPROC 是一个函数指针,指向窗口过程函数
- 第一个参数为窗口句柄
- 第二个参数为消息ID, UINT 类型(typedef unsigned int UINT;)
- 第三个参数为附加消息类型,取决于 uMsg 参数的值, WPARAM 类型
- 第四个参数为附加消息信息,取决于 uMsg 参数的值, LPARAM 类型
- 返回值为 LRESULT 类型(typedef long LRESULT;)
- 默认窗口过程函数
- 在Windows操作系统里,当窗口显示之后,用户在窗口上操作(比如移动鼠标、单击窗口、关闭窗口)时,系统会向该窗口源源不断地发送消息,然后窗口就需要处理这些消息,因此就需要一个函数来处理这些消息。Win32API里定义了一个系统默认的窗口处理函数 DefWindowProc ,我们可以把不关心的消息都丢给他来处理,如果我们需要自定义处理相关的消息,则需要实现自己的窗口过程函数
- 返回值是消息处理的结果
-
消息循环(消息队列)
-
Windows 中有一个"系统消息队列",以及分别为每个GUI线程维护一个各自的"线程消息队列"(应用程序消息队列)
- GUI线程是指线程中调用了user32.dll或者gdi32.dll中的函数,就叫它GUI线程
- 只有在调用了user32.dll或者gdi32.dll中的函数后,系统才会自动为线程创建消息队列。每个线程只能有一个消息队列
-
一个点击鼠标的消息从产生到被窗口过程处理,有以下步骤
- 鼠标驱动程序根据用户事件,转换成消息并自动放在Windows的"系统消息队列"
- Windows系统会自动将"系统消息队列"中的消息取出,并投掷于消息对应的"线程消息队列"
- 需要你从"线程消息队列"中取消息,然后翻译消息、分发消息给窗口过程
- 窗口过程函数响应这个消息并进行处理(窗口过程函数可以自定义,也可以使用默认的)
-
所以需要在你的程序中写一个while循环,用 GetMessage 函数来一直从"线程消息队列"中取消息
- 如果返回值为0,说明接收到了 WM_QUIT 消息(代表收到了结束信号,所以应该跳出while循环)
- 如果返回值不为0,则说明检索到的是 WM_QUIT 以外的消息
- 如果有错误,返回值是-1,可以调用 GetLastError 函数获取失败的原因
-
接收到了消息之后还要用 TranslateMessage 函数来翻译消息,将虚拟键的消息转为字符消息
- 如果消息已被翻译,则返回值是非零值
- 如果消息是 WM_KEYDOWN,WM_KEYUP,WM_SYSKEYDOWN,WM_SYSKEYUP,不管翻译如何,返回值都是非零值
- 如果消息未被转换,则返回值为零
-
翻译完之后还要用 DispatchMessage 函数把消息发送给窗口过程函数来处理
-
BOOL WINAPI GetMessage( LPMSG lpMsg, //指向MSG结构的指针 HWND hWnd, //要检索的窗口句柄 UNIT wMsgFilterMin, //要检索的最低消息值的整数值,一般传0 UINT wMsgFilterMax //要检索的最高消息值的整数值,一般传0 ) //消息循环(消息队列) MSG msg; while(GetMessage(&msg, NULL, 0, 0)){ TranslateMessage(&msg); //消息的翻译 DispatchMessage(&msg); //消息的分发 }
-
-
SendMessage 函数可以直接发送消息到窗口过程,绕过线程消息队列
-
BOOL WINAPI SendMessage( HWND hWnd, //接收消息的窗口句柄 UINT Msg, //消息ID WPARAM wParam, //附加的消息特定的信息 LPARAM lParam //附加的消息特定的信息 )
- SendMessage 将指定的消息发送到一个或多个窗口,直到窗口过程处理该消息前会一直阻塞等待
- SendMessage 函数可以直接发送消息到窗口过程,绕过线程消息队列
-
#include<windows.h>
#include <iostream>
// 如果你想自定义窗口过程函数,四个参数必须严格一致,返回值也必须为LRESULT,调用约定也必须是 CALLBACK( __stdcall 的别名)
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE:
//这个是由 CreateWindow 发出来的
return 0;
case WM_PAINT:
//这个是由 UpdateWindow 发出来的
return 0;
case WM_DESTROY:
//这个是由 DestroyWindow 发出来的(点击右上角关闭按钮)
PostQuitMessage(0); //发送 WM_QUIT 消息来退出程序
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
//第一步:注册窗口类
WNDCLASS wnd;
wnd.style = CS_DROPSHADOW; //样式
wnd.lpfnWndProc = MyWndProc; //窗口过程函数,用于处理消息
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hInstance = hInstance;
wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); //icon图标
wnd.hCursor = LoadCursor(NULL, IDC_ARROW); //鼠标光标采用箭头光标
wnd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景色
wnd.lpszClassName = L"MrHuang"; //窗口类名
wnd.lpszMenuName = NULL; //菜单名称
RegisterClass(&wnd);
//第二步:创建窗口(WM_CREATE)
HWND hWnd = CreateWindow(L"MrHuang", L"my first windows app", WS_OVERLAPPED, 100, 100, 300, 300, NULL, NULL, hInstance, NULL);
//第三步:显示窗口
ShowWindow(hWnd, nShowCmd);
//第四步:更新窗口(WM_PAINT)
UpdateWindow(hWnd);
//反复从线程消息队列中取消息,然后转发给窗口过程
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); //翻译消息,将虚拟键消息转换为字符消息
DispatchMessage(&msg); //把消息分发给窗口过程函数
}
return 0;
}