入门
一个简单的 Win32 程序
让我们先看看一个最简单的 Win32 程序的例子:
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 | #include <Windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
int Win32 wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PTSTR cmdLine, int cmdShow) {
const TCHAR CLASS_NAME[] = TEXT("WindowClass");
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(0, CLASS_NAME, TEXT("WindowName"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
return 0;
ShowWindow(hwnd, cmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
TranslateMessage(&msg), DispatchMessage(&msg);
return 0;
}
|
看起来非常复杂,让我们先拆解来看。
头文件
Win32 用到的绝大多数东西都在 Windows.h
和 CommCtrl.h
这两个头文件里。
Warning
虽然 CommCtrl.h
里用到了很多 Windows.h
的东西,但是它并没有 #include <Windows.h>
,因此在使用 CommCtrl.h
时,一定要先 #include <Windows.h>
再 #include <CommCtrl.h>
。
主函数
| int Win32 wWinMain(
HINSTANCE hInstance, // 实例的句柄,这个数据类型及其实际存储的信息不用理解,只需要直接使用这个值
HINSTANCE hPrevInstance, // 没有任何意义,始终为 0
PTSTR cmdLine, // 为命令行参数,在初学阶段不会使用
int cmdShow // 为指示窗口状态(最小化、最大化、正常显示)的值,其具体值我们不用理解
)
|
不同于控制台程序,窗口程序以 wWinMain
为入口,其中「w」表示这个程序使用 Unicode 字符(宽字符 wchar_t
),若没有这个「w」则使用 ANSI 字符(char
)。Win32
修饰函数,具体含义不用理解。
创建窗口
注册窗口类
在 Win32 中,所有的组件都是窗口,每种窗口都有对应的窗口类(具体的解释见「窗口类」章节),对于主窗口,我们需要注册窗口类(可以理解为定义一个类)。
| const TCHAR CLASS_NAME[] = TEXT("WindowClass"); // TCHAR 即为 wchar_t
WNDCLASS wc = { }; // 使用一个结构体存储窗口类的参数
wc.lpfnWndProc = WindowProc; // 窗口过程函数(后文会讲)
wc.hInstance = hInstance; // 实例的句柄
wc.lpszClassName = CLASS_NAME; // 窗口类名称,以一个字符串为标识
RegisterClass(&wc); // 注册窗口类
|
创建窗口
使用 CreateWindowEx
从来没有见过这么多参数的函数 创建窗口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | HWND hwnd = CreateWindowEx(
0, // 扩展窗口样式,这里不使用扩展窗口,因此设置为 0
CLASS_NAME, // 标识窗口类的字符串
TEXT("WindowName"), // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式,此处为一个重叠的窗口(带有标题栏和边框、图标、最小化、最大化、退出按钮,可调整大小的窗口)
CW_USEDEFAULT, // 窗口的最左侧位置,以屏幕左上角为 0,这里使用默认值
CW_USEDEFAULT, // 窗口的最上侧位置,以屏幕左上角为 0,这里使用默认值
CW_USEDEFAULT, // 窗口的宽度,这里使用默认值
CW_USEDEFAULT, // 窗口的高度,这里使用默认值
NULL, // 父窗口的句柄,因为主窗口没有父窗口,所以为 NULL
NULL, // 这里表示不使用菜单
hInstance, // 实例的句柄
NULL // 在初学阶段不会使用,设置为 NULL
)
|
CreateWindowEx
返回值类型为 HWND
,表示创建窗口的句柄(可以理解为一个指针)。如果创建失败,将返回 NULL
,此时需要退出程序;否则显示窗口。
| if (hwnd == NULL)
return 0;
ShowWindow(hwnd, cmdShow);
|
消息处理
对 Win32 窗口的操作(比如点击按钮、按下键盘等)将以消息队列的形式发送给程序,程序需要使用消息循环来处理消息。
消息循环
| MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
TranslateMessage(&msg), DispatchMessage(&msg);
|
消息以 MSG
类型存储,我们不需要知道这玩意到底是怎么存储的,只需要使用它处理消息。GetMessage
函数的四个参数含义如下:
| GetMessage(
&msg, // 将读取到的消息存储在这个对象中
NULL, // 这里表示从所有窗口里检索消息
0, 0 // 这里表示不进行范围筛选,返回所有可用消息
)
|
对于一般的消息 GetMessage
返回一个大于 \(0\) 的值,但如果收到 WM_QUIT
消息,则返回 \(0\),此时应该退出消息循环,因此使用上面的 while
结构。
对于一条消息,我们只需要使用 TranslateMessage
和 DispatchMessage
两个函数即可完成消息处理。同样,它们的实现方式我们不需要理解,只需要知道消息处理过程中会调用窗口过程。
窗口过程
窗口过程是处理窗口消息的函数,在注册窗口类的时候需要作为参数传入(一般来说,为了更好看,会先声明,再在主函数后面定义,这里为了更好理解并没有选择这种方式)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
|
返回值 LRESULT
和函数的修饰符 CALLBACK
不用理解,只需要知道 LRESULT
大概就是个整型即可。WindowProc
的四个参数含义如下:
| LRESULT CALLBACK WindowProc(
HWND hwnd, // 窗口的句柄
UINT uMsg, // 消息代码
WPARAM wParam, LPARAM lParam // 消息参数,其含义取决于消息类型
)
|
消息代码一般使用 switch case
语句处理。WM_DESTROY
为销毁窗口的消息,对于此消息来说,wParam
和 lParam
均没有用处。在处理此消息时,我们一般会使用 PostQuitMessage(0)
,这个语句会向消息队列中发送 WM_QUIT
消息,如上文介绍,它会结束消息循环(如果不这样处理,会出现窗口关闭而程序未结束的情况),其中 \(0\) 为退出代码(并不代表收到 WM_QUIT
消息时 GetMessage
的返回值),初学时不使用退出代码,直接设置为 \(0\)。
WM_PAINT
为绘制窗口的消息,对于此消息来说,wParam
和 lParam
均没有用处。这里的处理方式为使用背景色填充窗口,具体实现方式现在不需要理解。
这个窗口还可能出现其他的消息,对于那些不需要特殊处理的消息,我们不需要一一列出,只需要调用默认窗口过程 DefWindowProc
,并将参数传入即可实现默认处理。