win32窗口编程
Published in:2024-07-31 |

准备

操作系统:Windows10或以上

编译器:Microsoft Visual Studio
在这里插入图片描述
在这里插入图片描述

创建一个最简单的窗口

Just as every C application and C++ application must have a main function as its starting point, every Windows desktop application must have a WinMain function. WinMain has the following syntax.

1
2
3
4
5
6
int WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow
);

WinMain函数是Windows窗口应用程序的主函数,就像控制台console程序一样都有一个主函数main();。WinMain函数有四个参数:

  • hInstance:A handle to the current instance of the application. 当前应用实例的一个操作。
  • hPrevInstance:A handle to the previous instance of the application. This parameter is always NULL. 这个参数不是很常用。
  • lpCmdLine:The command line for the application, excluding the program name. To retrieve the entire command line, use the GetCommandLine function. 用来接收命令行的参数。
  • nCmdShow:Controls how the window is to be shown. This parameter can be any of the values that can be specified in the nCmdShow parameter for the ShowWindow function. 这个参数决定着窗口将以怎样的形式展现出来。

使用 Visual Studio 创建一个空项目:
在这里插入图片描述
确定好项目名称以及所在位置:
在这里插入图片描述
Source Files(源文件)右击->Add(添加)->New Item:
在这里插入图片描述
在这里插入图片描述

以下便是一个最简单的窗口应用程序代码:
在这里插入图片描述

运行以上代码之前记得修改项目的属性:Solution Explorer栏下右击项目名称:Properties—>Configuration Properties—>Linker—>System—>SubSystem改成Windows。
在这里插入图片描述
在这里插入图片描述
温馨提示:如果您开发的不是窗口应用程序(console控制台程序),记得改为Console,这里设置不好是不能成功编译运行的。
在这里插入图片描述


编译成功后运行以上代码,你会发现什么都没有出现,这是因为它是最简单的窗口应用程序,里面还没有任何内容。

注册窗口类、创建窗口实例并显示窗口

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
#include <Windows.h>

int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
const LPCWSTR pClassName = L"pClassName"; //指定的字符串常数,前面需要加一个大写字母 L

//register window class 注册窗口类
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(wc); //初始化成员变量
wc.style = CS_OWNDC;
wc.lpfnWndProc = DefWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = pClassName;
wc.hIconSm = nullptr;
RegisterClassEx(&wc);

//create window instance
HWND hWnd = CreateWindowEx(
0, pClassName,
L"Happy Hard Window", //窗口标题
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
200, 200, 640, 480, //窗口位置(200,200),大小(640,480)。
nullptr, nullptr, hInstance, nullptr
);

//展示窗口
ShowWindow(hWnd, SW_SHOW);

while (true) {}; //让程序进入死循环,让窗口一直显示

return 0;
}

在这里插入图片描述
编译并运行,会发现有一个窗口,这时并不能对窗口进行任何操作。

消息循环

对以上窗口不仅能进行任何操作,是因为我们还没写代码对事件进行处理。这里的事件指的就是鼠标操作或者键盘输入。

Windows is about windows. And Windows is about Messages.

在没有任何操作的时候,窗口的画面会一直不变;一旦用户输入了操作,窗口也会做相应的改变。对于不同的事件,会产生不同的响应,并重新刷新图形界面窗口,反馈给使用者。游戏也是这样,只不过游戏的画面的刷新是实时的,即使没有鼠标键盘操作,游戏画面也在刷新,包括子弹的运动、NPC的动作等。

在这里插入图片描述
在这里插入图片描述
将上一步的代码中的 while(true) 死循环改成以下代码:

1
2
3
4
5
MSG msg;     //msg结构体
while (GetMessage(&msg, nullptr, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

这时候就可以移动窗口和最小化以及关闭窗口,但是在关闭窗口的时候,虽然窗口是关闭了,但是程序的进程依然在运行着。这是因为当单击关闭窗口的按钮的时候,程序并不知道你是想要关闭窗口还是关闭整个进程。

  • msg 结构体中的成员变量:
    在这里插入图片描述

在前面的代码中,我们设置了 wc.lpfnWndProc = DefWindowProc; default Window procedure,意味着所有的消息都将做出默认行为,包括关闭窗口。默认的关闭窗口,不连同进程一起关闭。因为一个进程可能会有多个窗口,父窗口、子窗口等,当我们关闭子窗口的时候,我们并不希望把整个进程以及父窗口都关闭。

想要让窗口对于不同的消息,做出我们想要的行为,就需要自定义一个消息处理机制:

1
2
3
4
5
6
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)    //WndProc函数名随意起
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
... ...
wc.lpfnWndProc = WndProc;

在函数体中添加 switch 语句,对 msg 进行switch,对于不同的情况case,做出不同的反应:

1
2
3
4
5
6
switch (msg)
{
case WM_CLOSE:
PostQuitMessage(69);
break;
}

全部代码:

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
#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
PostQuitMessage(69);
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}

int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
const LPCTSTR pClassName = L"pClassName";
//register window class 注册窗口类
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = pClassName;
wc.hIconSm = nullptr;
RegisterClassEx(&wc);
//create window instance
HWND hWnd = CreateWindowEx(
0, pClassName,
L"Happy Hard Window",
WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
200, 200, 640, 480,
nullptr, nullptr, hInstance, nullptr
);
ShowWindow(hWnd, SW_SHOW);

//message bump
MSG msg;
BOOL gResult;
while ((gResult = GetMessage(&msg, nullptr, 0, 0)) > 0)
{
TranslateMessage(&msg); //后文会讲到这个函数的作用
DispatchMessage(&msg); //这个函数用于派遣消息,让窗口对消息做出快速反应
}

if (gResult == -1)
{
return -1;
}
else
{
return msg.wParam; //The exit code given in the PostQuitMessage function.
}
}
  • DispatchMessage:Dispatches a message to a window procedure. It is typically used to dispatch a message retrieved by the GetMessage function.
  • PostQuitMessage:Indicates to the system that a thread has made a request to terminate (quit). It is typically used in response to a WM_DESTROY message.

这时候再来编译运行,当关闭窗口的时候,进程也会被关闭,并返回 69值:
在这里插入图片描述

窗口消息

窗口消息除了 WM_CLOSE 以外,还有很多:(大概有好几千条)
在这里插入图片描述

为了方便编程,可以专门进行以下操作:
创建一个头文件 WindowsMessageMap.h 和 一个 .cpp 的源文件 WindowsMessageMap.cpp

在这里插入图片描述
WindowsMessageMap.h 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include <unordered_map>
#include <Windows.h>
#include <string>

class WindowsMessageMap
{
public:
WindowsMessageMap() noexcept;
std::string operator()(DWORD msg, LPARAM lp, WPARAM wp) const noexcept;
private:
std::unordered_map<DWORD, std::string> map;
};

WindowsMessageMap.cpp的内容如下:在这里可以做一个实验,通过网络查找,对经常出现的消息列举在map中,为了实时显示窗口的操作,将消息打印出来。

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#include "WindowsMessageMap.h"
#include <string>
#include <sstream>
#include <iomanip>

// secret messages
#define WM_UAHDESTROYWINDOW 0x0090
#define WM_UAHDRAWMENU 0x0091
#define WM_UAHDRAWMENUITEM 0x0092
#define WM_UAHINITMENU 0x0093
#define WM_UAHMEASUREMENUITEM 0x0094
#define WM_UAHNCPAINTMENUPOPUP 0x0095

#define REGISTER_MESSAGE(msg){msg,#msg}

WindowsMessageMap::WindowsMessageMap() noexcept
:
map({
REGISTER_MESSAGE(WM_CREATE),
REGISTER_MESSAGE(WM_DESTROY),
REGISTER_MESSAGE(WM_MOVE),
REGISTER_MESSAGE(WM_SIZE),
REGISTER_MESSAGE(WM_ACTIVATE),
REGISTER_MESSAGE(WM_SETFOCUS),
REGISTER_MESSAGE(WM_KILLFOCUS),
REGISTER_MESSAGE(WM_ENABLE),
REGISTER_MESSAGE(WM_SETREDRAW),
REGISTER_MESSAGE(WM_SETTEXT),
REGISTER_MESSAGE(WM_GETTEXT),
REGISTER_MESSAGE(WM_GETTEXTLENGTH),
REGISTER_MESSAGE(WM_PAINT),
REGISTER_MESSAGE(WM_CLOSE),
REGISTER_MESSAGE(WM_QUERYENDSESSION),
REGISTER_MESSAGE(WM_QUIT),
REGISTER_MESSAGE(WM_QUERYOPEN),
REGISTER_MESSAGE(WM_ERASEBKGND),
REGISTER_MESSAGE(WM_SYSCOLORCHANGE),
REGISTER_MESSAGE(WM_ENDSESSION),
REGISTER_MESSAGE(WM_SHOWWINDOW),
REGISTER_MESSAGE(WM_CTLCOLORMSGBOX),
REGISTER_MESSAGE(WM_CTLCOLOREDIT),
REGISTER_MESSAGE(WM_CTLCOLORLISTBOX),
REGISTER_MESSAGE(WM_CTLCOLORBTN),
REGISTER_MESSAGE(WM_CTLCOLORDLG),
REGISTER_MESSAGE(WM_CTLCOLORSCROLLBAR),
REGISTER_MESSAGE(WM_CTLCOLORSTATIC),
REGISTER_MESSAGE(WM_WININICHANGE),
REGISTER_MESSAGE(WM_SETTINGCHANGE),
REGISTER_MESSAGE(WM_DEVMODECHANGE),
REGISTER_MESSAGE(WM_ACTIVATEAPP),
REGISTER_MESSAGE(WM_FONTCHANGE),
REGISTER_MESSAGE(WM_TIMECHANGE),
REGISTER_MESSAGE(WM_CANCELMODE),
REGISTER_MESSAGE(WM_SETCURSOR),
REGISTER_MESSAGE(WM_MOUSEACTIVATE),
REGISTER_MESSAGE(WM_CHILDACTIVATE),
REGISTER_MESSAGE(WM_QUEUESYNC),
REGISTER_MESSAGE(WM_GETMINMAXINFO),
REGISTER_MESSAGE(WM_ICONERASEBKGND),
REGISTER_MESSAGE(WM_NEXTDLGCTL),
REGISTER_MESSAGE(WM_SPOOLERSTATUS),
REGISTER_MESSAGE(WM_DRAWITEM),
REGISTER_MESSAGE(WM_MEASUREITEM),
REGISTER_MESSAGE(WM_DELETEITEM),
REGISTER_MESSAGE(WM_VKEYTOITEM),
REGISTER_MESSAGE(WM_CHARTOITEM),
REGISTER_MESSAGE(WM_SETFONT),
REGISTER_MESSAGE(WM_GETFONT),
REGISTER_MESSAGE(WM_QUERYDRAGICON),
REGISTER_MESSAGE(WM_COMPAREITEM),
REGISTER_MESSAGE(WM_COMPACTING),
REGISTER_MESSAGE(WM_NCCREATE),
REGISTER_MESSAGE(WM_NCDESTROY),
REGISTER_MESSAGE(WM_NCCALCSIZE),
REGISTER_MESSAGE(WM_NCHITTEST),
REGISTER_MESSAGE(WM_NCPAINT),
REGISTER_MESSAGE(WM_NCACTIVATE),
REGISTER_MESSAGE(WM_GETDLGCODE),
REGISTER_MESSAGE(WM_NCMOUSEMOVE),
REGISTER_MESSAGE(WM_NCLBUTTONDOWN),
REGISTER_MESSAGE(WM_NCLBUTTONUP),
REGISTER_MESSAGE(WM_NCLBUTTONDBLCLK),
REGISTER_MESSAGE(WM_NCRBUTTONDOWN),
REGISTER_MESSAGE(WM_NCRBUTTONUP),
REGISTER_MESSAGE(WM_NCRBUTTONDBLCLK),
REGISTER_MESSAGE(WM_NCMBUTTONDOWN),
REGISTER_MESSAGE(WM_NCMBUTTONUP),
REGISTER_MESSAGE(WM_NCMBUTTONDBLCLK),
REGISTER_MESSAGE(WM_KEYDOWN),
REGISTER_MESSAGE(WM_KEYUP),
REGISTER_MESSAGE(WM_CHAR),
REGISTER_MESSAGE(WM_DEADCHAR),
REGISTER_MESSAGE(WM_SYSKEYDOWN),
REGISTER_MESSAGE(WM_SYSKEYUP),
REGISTER_MESSAGE(WM_SYSCHAR),
REGISTER_MESSAGE(WM_SYSDEADCHAR),
REGISTER_MESSAGE(WM_KEYLAST),
REGISTER_MESSAGE(WM_INITDIALOG),
REGISTER_MESSAGE(WM_COMMAND),
REGISTER_MESSAGE(WM_SYSCOMMAND),
REGISTER_MESSAGE(WM_TIMER),
REGISTER_MESSAGE(WM_HSCROLL),
REGISTER_MESSAGE(WM_VSCROLL),
REGISTER_MESSAGE(WM_INITMENU),
REGISTER_MESSAGE(WM_INITMENUPOPUP),
REGISTER_MESSAGE(WM_MENUSELECT),
REGISTER_MESSAGE(WM_MENUCHAR),
REGISTER_MESSAGE(WM_ENTERIDLE),
REGISTER_MESSAGE(WM_MOUSEWHEEL),
REGISTER_MESSAGE(WM_MOUSEMOVE),
REGISTER_MESSAGE(WM_LBUTTONDOWN),
REGISTER_MESSAGE(WM_LBUTTONUP),
REGISTER_MESSAGE(WM_LBUTTONDBLCLK),
REGISTER_MESSAGE(WM_RBUTTONDOWN),
REGISTER_MESSAGE(WM_RBUTTONUP),
REGISTER_MESSAGE(WM_RBUTTONDBLCLK),
REGISTER_MESSAGE(WM_MBUTTONDOWN),
REGISTER_MESSAGE(WM_MBUTTONUP),
REGISTER_MESSAGE(WM_MBUTTONDBLCLK),
REGISTER_MESSAGE(WM_PARENTNOTIFY),
REGISTER_MESSAGE(WM_MDICREATE),
REGISTER_MESSAGE(WM_MDIDESTROY),
REGISTER_MESSAGE(WM_MDIACTIVATE),
REGISTER_MESSAGE(WM_MDIRESTORE),
REGISTER_MESSAGE(WM_MDINEXT),
REGISTER_MESSAGE(WM_MDIMAXIMIZE),
REGISTER_MESSAGE(WM_MDITILE),
REGISTER_MESSAGE(WM_MDICASCADE),
REGISTER_MESSAGE(WM_MDIICONARRANGE),
REGISTER_MESSAGE(WM_MDIGETACTIVE),
REGISTER_MESSAGE(WM_MDISETMENU),
REGISTER_MESSAGE(WM_CUT),
REGISTER_MESSAGE(WM_COPYDATA),
REGISTER_MESSAGE(WM_COPY),
REGISTER_MESSAGE(WM_PASTE),
REGISTER_MESSAGE(WM_CLEAR),
REGISTER_MESSAGE(WM_UNDO),
REGISTER_MESSAGE(WM_RENDERFORMAT),
REGISTER_MESSAGE(WM_RENDERALLFORMATS),
REGISTER_MESSAGE(WM_DESTROYCLIPBOARD),
REGISTER_MESSAGE(WM_DRAWCLIPBOARD),
REGISTER_MESSAGE(WM_PAINTCLIPBOARD),
REGISTER_MESSAGE(WM_VSCROLLCLIPBOARD),
REGISTER_MESSAGE(WM_SIZECLIPBOARD),
REGISTER_MESSAGE(WM_ASKCBFORMATNAME),
REGISTER_MESSAGE(WM_CHANGECBCHAIN),
REGISTER_MESSAGE(WM_HSCROLLCLIPBOARD),
REGISTER_MESSAGE(WM_QUERYNEWPALETTE),
REGISTER_MESSAGE(WM_PALETTEISCHANGING),
REGISTER_MESSAGE(WM_PALETTECHANGED),
REGISTER_MESSAGE(WM_DROPFILES),
REGISTER_MESSAGE(WM_POWER),
REGISTER_MESSAGE(WM_WINDOWPOSCHANGED),
REGISTER_MESSAGE(WM_WINDOWPOSCHANGING),
REGISTER_MESSAGE(WM_HELP),
REGISTER_MESSAGE(WM_NOTIFY),
REGISTER_MESSAGE(WM_CONTEXTMENU),
REGISTER_MESSAGE(WM_TCARD),
REGISTER_MESSAGE(WM_MDIREFRESHMENU),
REGISTER_MESSAGE(WM_MOVING),
REGISTER_MESSAGE(WM_STYLECHANGED),
REGISTER_MESSAGE(WM_STYLECHANGING),
REGISTER_MESSAGE(WM_SIZING),
REGISTER_MESSAGE(WM_SETHOTKEY),
REGISTER_MESSAGE(WM_PRINT),
REGISTER_MESSAGE(WM_PRINTCLIENT),
REGISTER_MESSAGE(WM_POWERBROADCAST),
REGISTER_MESSAGE(WM_HOTKEY),
REGISTER_MESSAGE(WM_GETICON),
REGISTER_MESSAGE(WM_EXITMENULOOP),
REGISTER_MESSAGE(WM_ENTERMENULOOP),
REGISTER_MESSAGE(WM_DISPLAYCHANGE),
REGISTER_MESSAGE(WM_STYLECHANGED),
REGISTER_MESSAGE(WM_STYLECHANGING),
REGISTER_MESSAGE(WM_GETICON),
REGISTER_MESSAGE(WM_SETICON),
REGISTER_MESSAGE(WM_SIZING),
REGISTER_MESSAGE(WM_MOVING),
REGISTER_MESSAGE(WM_CAPTURECHANGED),
REGISTER_MESSAGE(WM_DEVICECHANGE),
REGISTER_MESSAGE(WM_PRINT),
REGISTER_MESSAGE(WM_PRINTCLIENT),
REGISTER_MESSAGE(WM_IME_SETCONTEXT),
REGISTER_MESSAGE(WM_IME_NOTIFY),
REGISTER_MESSAGE(WM_NCMOUSELEAVE),
REGISTER_MESSAGE(WM_EXITSIZEMOVE),
REGISTER_MESSAGE(WM_UAHDESTROYWINDOW),
REGISTER_MESSAGE(WM_DWMNCRENDERINGCHANGED),
REGISTER_MESSAGE(WM_ENTERSIZEMOVE),
})
{}

std::string WindowsMessageMap::operator()(DWORD msg, LPARAM lp, WPARAM wp) const noexcept
{
constexpr int firstColWidth = 25;
const auto i = map.find(msg);

std::ostringstream oss;
if (i != map.end())
{
oss << std::left << std::setw(firstColWidth) << i->second << std::right;
}
else
{
std::ostringstream padss;
padss << "Unknown message: 0x" << std::hex << msg;
oss << std::left << std::setw(firstColWidth) << padss.str() << std::right;
}
oss << " LP: 0x" << std::hex << std::setfill('0') << std::setw(8) << lp;
oss << " WP: 0x" << std::hex << std::setfill('0') << std::setw(8) << wp << std::endl;

return oss.str();
}

通过include将WindowsMessageMap.h头文件引进WinMain()函数所在的源文件中:
在这里插入图片描述

如果输出的string类型出现了乱码,需要自己转换以下 ConvertToLPWSTR()。如果没有乱码问题,省去LPWSTR ConvertToLPWSTR()函数即可。
在这里插入图片描述

当按键按下的时候来修改窗口的标题,以确定程序是否运行正常:
在这里插入图片描述

运行结果如下:
在这里插入图片描述

在Output栏下可以看到打印的消息,我们可以看到 WM_KEYDOWN(按键按下)、WM_NCMOUSEMOVE(鼠标移动)等消息被实时地打印出来。

当大写字母 F 键被按下的时候,窗口的标题发生了相应的变化。
在这里插入图片描述

WM_KEYDOWN 有个类似的消息是 WM_CHAR,他们两个主要的区别就是,前者对大小写不敏感,接收一切按键的按下,可以用于游戏角色的移动;而后者对于大小写敏感,用于文本输入。 前文代码中的 TranslateMessage() 的其中一个作用就是将 WM_KEYDOWN 的消息转换成 WM_CHAR 的消息。

添加以下代码,通过输入文本来改变窗口标题:
在这里插入图片描述

添加以下代码,当鼠标左键点击的时候,将窗口标题改为当前鼠标的坐标:(注意添加#include <sstream>)。
在这里插入图片描述
在这里插入图片描述
本期文章到此结束,后面随缘更新后续。

Prev:
win32-注册窗口类、创建窗口
Next:
Windows开发基础