第一个Windows窗口程序

创建一个窗口程序的空项目,或者创建一个控制台空项目,调整下面的选项:

1571399180973

Windows 是一个消息(事件)驱动的操作系统:用户操作、系统变化、其他程序的通知都属于事件,抽象成一个个消息,程序接收到消息后,就要处理消息,从而程序具有一定的功能

消息是发送给窗口的,但处理消息的位置是消息的回调函数,根据发送者是否等待消息处理完毕可分为队列消息和非队列消息

消息本身不区分是否是队列的,是否是队列消息由函数决定

队列消息 PostMessage:每个窗口程序都有一个消息队列,系统首先得到用户的操作,系统会区分这个操作在哪一个程序,然后给那个程序的窗口发消息,但发送时是发到消息队列中,因为操作系统不会等待消息被处理完毕,这种机制叫异步,系统只是发送通知,何时处理是不管的

非队列消息 SendMessage:发送消息不走消息队列也是可以的,相当于直接调用目标窗口的回调函数,函数结束后,发送者才能继续执行代码,这种机制叫同步,就要等待

队列消息还是非队列消息是由发送者决定的,与消息类型无关

接收到消息后要做什么写的是逻辑代码。

获取窗口句柄:

1
2
3
FindWindow(窗口类名, 窗口名);//得到窗口句柄
FindWindowEx();//得到一个窗口的子窗口句柄
EnumChildWindow();//能枚举所有窗口,需自己过滤,得到目标窗口句柄

代码:

1
2
3
4
5
6
7
8
9
10
#include<windows.h>
int WINAPI WinMain(
HINSTANCE hInstance, //实例句柄,代表一个运行中的程序
HINSTANCE hPreInstance, //没用,已废弃
LPSTR szCmd, //命令行参数
int nShow //命令行参数
) {
MessageBox(0, L"你好", L"15PB", 0);
return 0;
}

系统需要知道这个类是哪个程序注册的,窗口是哪个程序创建的,数值是主模块的加载地址

句柄从概念上来说是某个对象的标识,系统可以根据标识识别出它是哪个对象,类似于 C++ 对象的 this 指针,API 相当于操作系统对外的接口。句柄从数值上来说,HINSTANCE 是模块的加载基址,窗口句柄就不是什么加载基址,进程、线程句柄是一个数组的序号

Windows 下没有比较好用的输出数字的函数,通常需要自己转换,所有的字符串相关的 API 函数,都分成两个版本:A 版和 W

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
#include<windows.h>
#include<stdio.h>
int WINAPI WinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没用了
LPSTR szCmd, //命令行参数
int nShow //命令行参数
) {
//想要nNum的值,用MessageBox显示出来
int nNum = 100;
char buf1[100] = {};//A版,即ASCII
sprintf_s(buf1, "%d", nNum);//数字转字符串的函数
MessageBoxA(0, buf1, "15PB", 0);

char ws[10] = { "112" };
char str[10] = {};
sscanf_s(ws, "%s", &str, _countof(str));//字符串转数字
MessageBoxA(0, str, "15pb", 0);

wchar_t buf2[100] = {};//W版,即UNICODE
swprintf_s(buf2, L"%d", nNum);
MessageBoxW(0, buf2, L"15PB", 0);

wchar_t ws2[10] = L"111";
wchar_t str2[10] = {};
swscanf_s(ws2, L"%s", &str2, _countof(str));
MessageBoxW(0, str2, L"15pb", 0);
return 0;
}

一般我们都使用 T 版函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<windows.h>
#include<stdio.h>
#include<tchar.h>
int WINAPI _tWinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没用了
LPTSTR szCmd, //命令行参数
int nShow //命令行参数
){
//数字转字符串
int nNum = 100;
TCHAR buf[100] = {};
_stprintf_s(buf, _T("%d"), nNum);
MessageBox(0, buf, _T("15PB"), 0);
//字符串转数字
TCHAR ws[10] = _T("908");
TCHAR str[10] = {};
_stscanf_s(ws, _T("%s"), &str, _countof(str));
MessageBox(0, str, _T("15PB"), 0);
return 0;
}

asciiunicoed 间的转换

1
2
3
4
5
6
7
8
9
10
// 宽字符转换为多字符(Unicode --> ASCII)
#define WCHAR_TO_CHAR(lpW_Char, lpChar) \
WideCharToMultiByte(CP_ACP, NULL, lpW_Char, -1,
lpChar, _countof(lpChar), NULL, FALSE);


// 多字符转换为宽字符(ASCII --> Unicode)
#define CHAR_TO_WCHAR(lpChar, lpW_Char) \
MultiByteToWideChar(CP_ACP, NULL, lpChar, -1,
lpW_Char, _countof(lpW_Char));

Windows 下输出调试信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool _trace(const TCHAR* format, ...) {//变参函数
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
OutputDebugString(buffer);
return true;
}
int WINAPI _tWinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没用了
LPTSTR szCmd, //命令行参数
int nShow //命令行参数
) {
int nNum = 100;
_trace(_T("这个数字为%d:"), nNum);
return 0;
}

1571403681224

我们也可自己封装带格式控制符的 MessageBox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool _MyMessageBox(const TCHAR* format, ...) {//变参函数
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
MessageBox(0, buffer, _T("提示"), 0);
return true;
}
int WINAPI _tWinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没用了
LPTSTR szCmd, //命令行参数
int nShow //命令行参数
) {
int nNum = 100;
_MyMessageBox(_T("这个数字为%d:"), nNum);
return 0;
}

1571404150931

关于 Windows 程序的错误码

1
2
3
4
5
6
7
8
9
10
11
12
int WINAPI _tWinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没用了
LPTSTR szCmd, //命令行参数
int nShow //命令行参数
) {
HANDLE hwnd = GetStdHandle(100);
int nError = GetLastError();//这个函数能获取到最近一次调用的API的错误码
//_MyMessageBox(_T("错误码为:%d"), nError);
_trace(_T("错误码为%d"), nError);
return 0;
}

在调试时,快速监视到错误码的方法:

1571404643063

制作一个能够解析错误码的工具,能够提供出错的具体位置

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
#include<windows.h>
#include<stdio.h>
#include<tchar.h>
#define CHAR_TO_WCHAR(lpChar, lpW_Char) \
MultiByteToWideChar(CP_ACP, NULL, lpChar, -1, \
lpW_Char, _countof(lpW_Char));
void MyGetErrorInfo(
LPCTSTR lpErrInfo,
UINT unErrCode,
TCHAR* tFile,
UINT unLine) // unLine=__LINE__
{
LPTSTR lpMsgBuf = nullptr;
WCHAR szMessage[128] = { 0 };
WCHAR szCaption[128] = { 0 };
//这个函数能够将错误码解析为错误信息
FormatMessage(0x1300, NULL, unErrCode,
0x400, (LPTSTR)&lpMsgBuf, 64, NULL);
swprintf_s(szMessage, 128,
L"Error_0x%08X:%s", unErrCode, lpMsgBuf);
swprintf_s(szCaption, 128,
L"%s:%s:(Error Line:%05d)", lpErrInfo, tFile, unLine);
MessageBox(NULL, szCaption, szMessage, MB_OK);
}
int WINAPI WinMain(
HINSTANCE hInstance, //实例句柄
HINSTANCE hPreInstance, //没有用了
LPSTR szCmd, //命令行参数
int nShow //命令行参数
)
{
HANDLE h = GetStdHandle(100);
//1 获得错误码
int nError = GetLastError();
//2 得到出错的文件路径
wchar_t buf[100] = {};
CHAR_TO_WCHAR(__FILE__, buf);
//3 输出错误信息
MyGetErrorInfo(_T("十五派友情提示您:"), nError, buf, __LINE__ - 5);
return 0;
}

第一个窗口程序

  1. 设计一个窗口类
  2. 注册窗口:RegisterClass
  3. 创建窗口:CreateWindow 函数,得到一个窗口句柄
  4. 更新显示窗口:ShowWindow UpdateWindow
  5. 编写一个消息循环
  6. 编写一个消息处理函数(窗口回调函数)

这段代码是固定形式,是微软规定好的流程

只有窗口才能接受消息,窗口的消息需要通过 GetMessage 从消息队列中获取到

大部分事情都是操作系统帮你完成的,比如说往消息队列添加消息,从消息队列中得到消息,删除已得到的消息

回调函数不是程序员调用的函数,程序员提供一个函数给操作系统,操作系统在合适的时机去调用所提供的函数,因为只有操作系统才知道什么时候得到了消息,只有程序员才知道程序怎么处理

创建窗口先要注册窗口类:需要先有一个模板,在创建窗口时根据模板去创建,模板是窗口共有的一些特性,放在窗口类里

更新显示窗口:刚创建的窗口是隐藏起来的,需要用 ShowWindow 显示出来,UpdateWindow 能让窗口产生一次自绘

GetMessage 的作用就是从消息队列中获取消息

DispatchMessage 的作用就是调用窗口相对应的回调函数

msg 结构体里的数值不应该去修改它,因为是系统提供的,处理消息的操作应该在回调函数里

GetMessage 函数里4个参数的作用:

1
2
3
4
5
6
BOOL WINAPI GetMessageW(
_Out_ LPMSG lpMsg, //获取消息的结构体
_In_opt_ HWND hWnd, //若填写一个窗口句柄,则GetMessage只能获取此窗口的消息
_In_ UINT wMsgFilterMin, //获取消息的最小值
_In_ UINT wMsgFilterMax //获取消息的最大值
);

TranslateMessage 会将 WM_KEYDOWN 翻译为 WM_CHAR 消息发送一遍,按下按键会自动产生 WM_KEYDOWN ,但并非所有的按键都是可见字符,如回车、F1~Fn 等。 WM_CHAR 会告诉你哪些可见字符被按下,通常来说处理文本时使用 WM_CHAR ,所有按键都处理时,使用 WM_KEYDOWN

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>
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMessage,
WPARAM wParam, LPARAM lParam);

int WINAPI _tWinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPTSTR szCmd,
int nShow
)
{
//1 设计窗口类
//UINT style;
//WNDPROC lpfnWndProc;
//int cbClsExtra;
//int cbWndExtra;
//HINSTANCE hInstance;
//HICON hIcon;
//HCURSOR hCursor;
//HBRUSH hbrBackground;
//LPCWSTR lpszMenuName;
//LPCWSTR lpszClassName;
WNDCLASS wc;
wc.style = CS_VREDRAW | CS_HREDRAW;//类风格
wc.lpfnWndProc = WndProc;//回调函数的地址(重要)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;//实例句柄,代表此程序
wc.hIcon = 0;//窗口图标
wc.hCursor = 0;//光标
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//背景颜色
wc.lpszMenuName = 0;//菜单名
wc.lpszClassName = L"15PB";//类名(重要)
//2 注册窗口类
RegisterClass(&wc);
//3 创建窗口
HWND hWnd = CreateWindow(
L"15PB", //类名
L"第一个窗口", //窗口名
WS_OVERLAPPEDWINDOW,//重叠窗口风格
100, 100, 400, 600, //位置和大小
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //实例句柄
NULL //附加信息,会随着WM_CREATE的lParam或wParam消息传递到回调函数中
);
//4 更新显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
//5 实现消息循环
MSG msg = {};
// 5.1 获取消息
//GetMessage得到WM_QUIT消息的时候,会返回False
while (GetMessage(&msg, 0, 0, 0))
{
// 5.2 转换消息 将WM_KEYDOWN转换WM_CHAR
TranslateMessage(&msg);
// 5.3 派发消息
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WndProc(//回调函数
HWND hWnd,
UINT uMessage,
WPARAM wParam, //若创建控件,低16位表示控件的ID,高16位表示控件的通知码
LPARAM lParam //若创建控件,表示控件的窗口句柄
)
{
switch (uMessage)
{
case WM_CLOSE:
PostQuitMessage(0);//会发出一个WM_QUIT消息
break;
default:
break;
}
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}

常见的消息处理:

1
2
3
4
5
6
7
8
//WM_CLOSE       窗口关闭的消息
//WM_DESTORY 窗口销毁的消息
//WM_KEYDOWN 键盘按下的消息
//WM_CHAR 字符按键消息
//WM_LBUTTONDOWN 鼠标左键单击消息
//WM_MOVING 窗口移动消息
//WM_SIZE 窗口大小改变的消
//GetClientRect 获取窗口的大小
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
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT uMessage,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMessage)
{
case WM_CREATE://一般进行一些初始化的操作

break;
case WM_DESTROY:
_trace(L"接到了wm_destory消息\n");
PostQuitMessage(0);//会发出一个WM_QUIT消息
break;
case WM_CLOSE:
_trace(L"接到了wm_close消息\n");
break;
case WM_KEYDOWN:
_trace(L"接到了wm_keydown消息\n");
_trace(L"lParam:%d,wParam:%d\n", lParam, wParam);
break;
case WM_CHAR://可见字符
_trace(L"接到了wm_char消息\n");
_trace(L"lParam:%d,wParam:%d\n", lParam, wParam);
break;
case WM_LBUTTONDOWN://鼠标左键单击
{
_trace(L"接到了wm_lbuttondown消息\n");
int x = LOWORD(lParam);
int y = HIWORD(lParam);
_trace(L"鼠标左键点击了x:%d,y:%d\n", x, y);
}
case WM_SIZE://窗口大小发生改变了
{
_trace(L"接到了wm_size消息\n");
RECT rc = {};
GetClientRect(hWnd, &rc);
_trace(L"鼠标左键点击了l:%d,r:%d,t:%d,b:%d\n",
rc.left, rc.right, rc.top, rc.bottom);
}
break;
default:
break;
}
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}

关于PostMessage,SendMessage,自定义消息

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
#include<windows.h>
#include<tchar.h>
#define WM_MYMESSAGE WM_USER + 2
bool _trace(const TCHAR *format, ...) //变参函数
{
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
OutputDebugString(buffer);
return true;
}

LRESULT CALLBACK WndProc(
HWND hWnd,
UINT uMessage,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMessage)
{
case WM_CREATE://一般进行一些初始化的操作

break;
case WM_DESTROY:
_trace(L"接到了wm_destory消息\n");
PostQuitMessage(0);//会发出一个WM_QUIT消息
break;
case WM_CLOSE:
_trace(L"接到了wm_close消息\n");
break;
case WM_KEYDOWN:
_trace(L"接到了wm_keydown消息\n");
_trace(L"lParam:%d,wParam:%d\n", lParam, wParam);
break;
case WM_CHAR://可见字符
_trace(L"接到了wm_char消息\n");
_trace(L"lParam:%d,wParam:%d\n", lParam, wParam);
break;
case WM_LBUTTONDOWN://鼠标左键单击
{
_trace(L"接到了wm_lbuttondown消息\n");
int x = LOWORD(lParam);
int y = HIWORD(lParam);
_trace(L"鼠标左键点击了x:%d,y:%d\n", x, y);
}
case WM_SIZE://窗口大小发生改变了
{
_trace(L"接到了wm_size消息\n");
RECT rc = {};
GetClientRect(hWnd, &rc);
_trace(L"鼠标左键点击了l:%d,r:%d,t:%d,b:%d\n",
rc.left, rc.right, rc.top, rc.bottom);
}
break;
case WM_MYMESSAGE:
{

_trace(L"接到了wm_mymessage消息\n");
_trace(L"lParam:%d,wParam:%d\n", lParam, wParam);
MessageBox(0, 0, 0, 0);
}
break;
default:
break;
}
return DefWindowProc(hWnd, uMessage, wParam, lParam);
}
int WINAPI _tWinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPTSTR szCmd,
int nShow
)
{
WNDCLASS wc;
wc.style = CS_VREDRAW | CS_HREDRAW;//类风格
wc.lpfnWndProc = WndProc;//回调函数的地址(重要)
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;//实例句柄,代表此程序
wc.hIcon = 0;//窗口图标光标
wc.hCursor = 0;//设置光标
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//颜色
wc.lpszMenuName = 0;//菜单名
wc.lpszClassName = L"15PB";//类名
//2 注册窗口类
RegisterClass(&wc);
//3 创建窗口
HWND hWnd = CreateWindow(
L"15PB", //类名
L"第一个窗口", //窗口名
WS_OVERLAPPEDWINDOW,//重叠窗口风格
100, 100, 400, 600, //位置和大小
NULL, //父窗口句柄
NULL, //菜单句柄
hInstance, //实例句柄
NULL //附加信息
);
//4 更新显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
//5 实现消息循环
MSG msg = {};
// 5.1 获取消息
//GetMessage得到WM_QUIT消息的时候,会返回False
while (GetMessage(&msg, 0, 0, 0))
{
// 5.2 转换消息 将WM_KEYDOWN转换WM_CHAR
TranslateMessage(&msg);
// 5.3 派发消息
DispatchMessage(&msg);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define WM_MYMESSAGE WM_USER+2
int main()
{
while (1) {
//PostMessage(//控制计算器
// (HWND)0x000E0686,
// WM_COMMAND,
// 0x83, 0
//);
SendMessage(//控制窗口
(HWND)0x000A07D2,
WM_MYMESSAGE,
68, 55
);
}
}