WindowsAPI学习笔记

Windows窗口程序基础

在屏幕上显示一个窗口的过程一般包括以下步骤,也就是入口函数WinMain的执行流程:

  1. 注册窗口类

    在注册之前,要先填写RegisterClassEx函数的参数WNDCLASSEX结构的各个字段。

  2. 创建窗口

  3. 显示窗口刷新窗口客户区

  4. 运行消息循环

    获取消息、转换消息、将消息分发到回调函数WindowProc处理。

接下来分别介绍每一个步骤:

注册窗口类

RegisterClassEx函数用于注册窗口类,其函数原型如下:

1
ATOM RegisterClassEx(_In_const WNDCLASSEX* lpwcx);

其中参数lpwcx是一个指向WNDCLASSEX结构的指针,调用RegisterClassEx函数必须先初始化此结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct tagWNDCLASSEX {
UINT cbSize; // 结构体大小
UINT style; // 窗口类的样式
WNDPROC lpfnWndProc; // 窗口过程函数
int cbClsExtra; // 窗口类附加内存
int cbWndExtra; // 窗口附加内存
HINSTANCE hInstance; // 程序实例句柄
HICON hIcon; // 窗口图标句柄,用于生成可执行文件图标
HCURSOR hCursor; // 窗口鼠标指针句柄
HBRUSH hbrBackground; // 窗口背景画刷句柄
LPCWSTR lpszMenuName; // 菜单资源名称
LPCWSTR lpszClassName; // 窗口类名称
HICON hIconSm; // 小图标句柄
} WNDCLASSEX, * PWNDCLASSEX;

每一项可使用的取值可查看《深入浅出WindowsAPI程序设计》对应此处的内容。

创建窗口

创建窗口的函数是CreateWindowEx,其函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HWND CreateWindowEx(
_In_ DWORD dwExStyle,// 窗口扩展样式
_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_ HINSTANCE hInstance,// 程序实例句柄
_In_opt_ LPVOID lpParam// 附加参数
);

显示窗口

ShowWindow函数用于设置指定窗口的显示状态:

1
2
3
4
BOOL ShowWindow(
_In_ HWND hWnd, // 窗口句柄
_In_ int nCmdShow // 窗口显示状态
);

ShowWindow函数执行前,窗口已经在Windows内部创建了,但此时还未显示。其作用是设置指定窗口的显示状态,其通过指定nCmdShow参数来设置。

刷新窗口客户区

UpdateWindow函数通过向窗口发送WM_PAINT消息来刷新窗口客户区,该函数将WM_PAINT消息发送给指定的窗口过程,其绕过消息队列,直接调用窗口过程。适用于修改窗口内容后立即刷新内容。

1
BOOL UpdateWindow(_In_ HWND hWnd);  // 填写要更新的窗口句柄

消息循环

程序运行后会发生很多事件,Windows为每个程序维护消息队列,事件发生后系统将其转化为消息放置在队列中,程序会通过以下的消息循环对其进行处理:

  1. GetMessage函数用于从调用线程的消息队列中获取消息:
1
2
3
4
5
6
BOOL GetMessage(
_Out_ LPMSG lpMsg, // MSG结构用于存放消息的具体信息
_In_opt_ HWND hWnd, // 要获取哪个窗口的消息
_In_ UINT wMsgFilterMin, // 要获取的消息的最小值
_In_ UINT wMsgFilterMax // 要获取的消息的最大值
);

MSG结构体用于存放函数获取的消息的具体信息,其定义如下:

1
2
3
4
5
6
7
8
typedef struct tagMSG{
HWND hwnd; // 窗口句柄
UINT message; // 消息类型
WPARAM wParam; // 消息附加信息
LPARAM lParam; // 消息附加信息
DWORD time; // 消息产生的时间
POINT pt; // 鼠标光标位置
}

pt字段是一个POINT结构,表示消息发生时的光标位置,其结构在windef.h中定义如下:

1
2
3
4
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, * PPOINT,NEAR *NPPOINT, FAR *LPPOINT;

窗口过程

窗口过程是程序的消息处理中心,无论是队列还是非队列消息窗口过程都会为程序处理。窗口过程的定义如下:

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd, // 窗口句柄
_In_ UINT uMsg, // 消息类型
_In_ WPARAM wParam, // 消息附加信息
_In_ LPARAM lParam // 消息附加信息
);

窗口过程的名称可以任意命名,只要不与其他函数名称冲突即可,WNDPROC是窗口过程指针类型

1
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
  1. WM_CREATE消息:

    WinMain调用CreateWindowEx函数创建窗口后,系统会向窗口发送WM_CREATE消息。其是窗口过程较早收到的的消息之一,程序常常会在此处做一些初始化工作。

  2. WM_CLOSE消息:

    当用户关闭窗口时,系统会向窗口发送WM_CLOSE消息,DefWindowsProc函数会对其处理,即调用DestroyWindow函数销毁窗口,DestroyWindow函数会向窗口发送WM_DESTROY消息。DefWindowProc函数不会处理该消息,需要我们自己处理:在WM_DESTROY消息中调用PostQuitMessage函数,该函数向消息队列中发送WM_QUIT消息,GetMessage函数收到该消息后返回0,从而结束消息循环退出程序。

  3. 其他消息处理

DefWindowProc函数用于调用Windows提供的默认窗口过程,可确保程序的每个消息都得到处理。

1
2
3
4
5
LRESULT CALLBACK WindowProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam);

4. WM_PAINT重绘消息

WinMain调用UpdateWindow函数时,系统会向窗口发送WM_PAINT消息。这是Windows编程中很重要的一条消息,当窗口客户区的部分或全部变为无效(首次创建、调整大小、最小化等)时,系统会向窗口发送WM_PAINT消息,要求窗口重绘客户区。

若一个窗口不对WM_PAINT消息进行处理,那么应交由DefWindowProc函数处理,其依次调用BeginPaint和EndPaint函数,BeginPaint函数用于获取设备上下文,EndPaint函数用于释放设备上下文。

BeginPaint函数原型如下:

1
2
3
4
HDC BeginPaint(
_In_ HWND hWnd, // 窗口句柄
_Out_ LPPAINTSTRUCT lpPaint // PAINTSTRUCT结构指针
);

PAINTSTRUCT结构用于存放BeginPaint函数获取的设备上下文,其定义如下:
1
2
3
4
5
6
7
8
typedef struct tagPAINTSTRUCT {
HDC hdc; // 设备上下文句柄
BOOL fErase; // 是否擦除背景
RECT rcPaint; // 需要重绘的区域
BOOL fRestore; // 是否恢复设备上下文
BOOL fIncUpdate; // 是否进行增量更新
BYTE rgbReserved[32]; // 保留字段
} PAINTSTRUCT, *PPAINTSTRUCT;

EndPaint函数原型如下:
1
2
3
4
BOOL EndPaint(
_In_ HWND hWnd, // 窗口句柄
_In_ const PAINTSTRUCT* lpPaint // PAINTSTRUCT结构指针
);

一个完整的Windows窗口程序实例:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <Windows.h>
#include <tchar.h> // _tcslen函数需要该头文件
#pragma comment(lib, "Winmm.lib") // 播放声音的PlaySound函数需要Winmm导入库

// 函数声明,窗口过程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wndclass; // RegisterClassEx函数用的WNDCLASSEX结构
TCHAR szClassName[] = TEXT("MyWindow"); // 注册的窗口类名称
TCHAR szAppName[] = TEXT("HelloWindows"); // 窗口标题
HWND hwnd; // 窗口句柄
MSG msg; // 消息循环

wndclass.cbSize = sizeof(WNDCLASSEX);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WindowProc;
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 = CreateWindowEx(
0,
szClassName,
szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
300, 180,
NULL, NULL,
hInstance,
NULL
);

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0) != 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
TCHAR szStr[] = TEXT("你好,Windows程序设计");

switch (uMsg)
{
case WM_CREATE:
PlaySound(TEXT("成都(两会版).wav"), NULL, SND_FILENAME | SND_ASYNC);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 10, 10, szStr, _tcslen(szStr));
EndPaint(hwnd, &ps);
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}

return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

杂项

播放音乐

播放音乐的函数是PlaySound,其函数原型如下:

1
2
3
4
5
BOOL PlaySound(
_In_opt_ LPCWSTR lpszSound, // 声音资源名称
_In_opt_ HMODULE hmod, // 声音资源模块句柄
_In_ UINT fdwSound // 播放模式
);

其中fdwSound参数可以取以下值:

  • SND_ASYNC:异步播放(开始播放后立即返回)
  • SND_SYNC:同步播放(播放完音乐后函数才返回)
  • SND_LOOP:循环播放

显示字符串

TextOut函数用于在指定位置显示一个字符串,其函数原型如下:

1
2
3
4
5
6
7
BOOL TextOut(
_In_ HDC hdc, // 设备上下文句柄
_In_ int nXStart, // 字符串左上角x坐标
_In_ int nYStart, // 字符串左上角y坐标
_In_ LPCTSTR lpString, // 字符串指针(因为有cchString参数,所以不要求以零结尾)
_In_ int cchString // 字符串长度
);