让我们与星光探索者一起,探索Windows吧!

探索绘图编程

GDI,Graphic Device Interface,Windows提供了大量API供我们绘制各种各样的图形。

Windows每个窗口都是绘制出来的,当窗口需要绘制的时候,产生WM_PAINT消息,进而我们可以绘制窗口。

HDC,即为设备上下文句柄,DC是设备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
#define _CRT_SECURE_NO_WARNINGS
#include "MyWindowApplication/resource.h"
#include <windows.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "kernel32.lib")

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


INT APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
static const TCHAR TITLE[] = TEXT("星光探索者");
static const TCHAR CLASSNAME[] = TEXT("MyStruct");

WNDCLASS wc{0};
wc.hInstance = hInstance;
wc.lpfnWndProc = WinProc;
wc.lpszClassName = CLASSNAME;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0, CLASSNAME, TITLE,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

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

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

return 0;
}

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
// 定义绘图结构体,用来绘图
PAINTSTRUCT ps;

// 开始绘画,得到DC
HDC hdc = BeginPaint(hwnd, &ps);

// 绘制图标
DrawIcon(hdc, 50, 50, LoadIcon(NULL, LPCTSTR(IDI_MYICON)));
for (int i = 10; i != 40; ++i)
{
for (int j = 10; j != 40; ++j)
{
// 绘制一个点
SetPixel(hdc, i, j, RGB(255, 0, 255));
}
}
// 结束绘画
EndPaint(hwnd, &ps);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

这样就可以简单的绘图了。只需BeginPaint获得HDC,然后调用相关的API(API直接查官网相关资源),然后再EndPaint,就可以了。但是并不是什么时候都可以绘图的,只有绘图消息和计时器消息发生时,绘图才会成功

使用对话框

对话框是一种窗口,他主要用于向用户显示信息或等待用户输入时的一个小弹窗。如果我们快速制作一个Win32程序,使用对话框是一种便捷方法。

同样是这个熟悉的界面,右键项目,添加(Add),资源(Resource)

点击对话框(Dialog),之后VS显示了一个可视化的设计器。我们可以直接拖拽控件快速布局。大部分小伙伴都对很喜欢这种形式吧!

为了示范,星光探索者之后就直接写代码了。

对话框根据显示后是否阻止与其他窗口交互,可分为模态对话框和非模态对话框。

  • 模态对话框:显示后阻止你创建的其他窗口交互

  • 非模态对话框:显示后不阻止你创建的其他窗口交互

模态对话框的创建方式,调用DialogBox函数。这个函数是阻塞函数,返回值为对话框销毁时的返回值。参数如下:

1
2
3
4
5
6
INT_PTR DialogBox(
HINSTANCE hInstance, // 实例句柄,如果传NULL将使用当前的
LPCTSTR lpTemplate, // 创建的资源模板,在资源文件
HWND hWndParent, // 对话框的父窗口
DLGPROC lpDialogFunc // 对话框消息过程处理函数
);

非模态对话框创建方式,调用CreateDialog函数,参数如下,意义和上方相同,不多叙述。这个函数不是阻塞函数。

1
2
3
4
5
6
void CreateDialog(
HINSTANCE hInstance,
LPCTSTR lpName,
HWND hWndParent,
DLGPROC lpDialogFunc
);

DLGPROC是对话框过程函数的类型,为:

1
typedef INT_PTR (CALLBACK* DLGPROC)(HWND, UINT, WPARAM, LPARAM);

实际上和窗口消息过程函数是一模一样的。但是这个对话框过程函数的返回值如果为FALSE,表明还需要默认处理函数处理,否则不调用默认处理函数处理。

当对话框开始创建未显示时,产生WM_INITDIALOG消息 ,不产生WM_CREATE消息。因此,对话框过程函数不应该处理WM_CREATE消息。各位感兴趣的小伙伴可验证一下。

模态对话框和非模态对话框的处理函数写法要求大不同。模态对话框销毁时使用EndDialog 函数,不可以使用DestroyWindow等函数;而非模态对话框销毁时使用DestroyWindow 函数

要想处理对话框内部的控件的操作,需要处理 WM_COMMAND消息。控件被点击时,lParam为控制窗口的句柄,wParam的高字节为控件的消息,低字节为控件ID标识符

下面是一个模态对话框的示例:

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
#define _CRT_SECURE_NO_WARNINGS
#include "resource.h"
#include <windows.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "kernel32.lib")

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


INT APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
static const TCHAR TITLE[] = TEXT("星光探索者");
static const TCHAR CLASSNAME[] = TEXT("MyStruct");

WNDCLASS wc{0};
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.hInstance = hInstance;
wc.lpfnWndProc = WinProc;
wc.lpszClassName = CLASSNAME;
wc.style = CS_HREDRAW | CS_VREDRAW;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0,CLASSNAME, TITLE, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

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

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

return 0;
}

LRESULT CALLBACK DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_INITDIALOG:
// 处理对话框被创建的消息
MessageBox(hwnd, TEXT("对话框被创建"), TEXT("探索对话框"), MB_OK);
return TRUE;
case WM_COMMAND:
// 处理对话框被点击的消息
switch (LOWORD(wParam))
{
// IDOK和IDCANCEL都是定义好的,分别代表OK和CANCEL按钮
case IDOK:
MessageBox(hwnd, TEXT("我也觉得OK"), TEXT("来自对话框的信息"), MB_OK);
return TRUE;
case IDCANCEL:
EndDialog(hwnd, 0);
return FALSE;
}
return FALSE;
}
return FALSE;
}

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_RBUTTONDOWN:
DialogBox(NULL, (LPCTSTR)IDD_DIALOG1, hwnd, DialogProc);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

运行这个实例,当我们鼠标右键的时候,弹出一个消息框后弹出对话框。点击OK按钮,将显示“我也觉得OK啊“;点击CANCEL按钮,将关闭对话框。

使用系统定义好的控件

对话框里能拖出这么多控件,实际上系统已经提前定义好了一些控件。

包含头文件CommCtrl.h,这个头文件定义了很多定义好的系统窗口类 。但是你需要在#include <windows.h>头文件后再#include <CommCtrl.h>,因为你微软居然要用windows.h头文件,居然不包含,导致编译的时候提示有很多标识符未定义,不这么做会报错。

系统已经定义好的控件包括WC_BUTTON(按钮),WC_EDIT(文本编辑框),WC_STATIC(不可改文本控件),WC_TREEVIEW(树状图),ANIMATE_CLASS(动画)等。

窗口控件的消息,是通过WM_COMMAND消息间接传上来的。 控件被点击时,lParam为控制窗口的句柄,wParam的高字节为控件的消息,低字节为控件ID标识符。 这个在对话框消息过程函数已经提及。

看代码:

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
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <CommCtrl.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "kernel32.lib")

LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);


INT APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
static const TCHAR TITLE[] = TEXT("星光探索者");
static const TCHAR CLASSNAME[] = TEXT("MyStruct");

WNDCLASS wc{0};
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.hInstance = hInstance;
wc.lpfnWndProc = WinProc;
wc.lpszClassName = CLASSNAME;

RegisterClass(&wc);

HWND hwnd = CreateWindowEx(
0,CLASSNAME, TITLE, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
);

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

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

return 0;
}


LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_CREATE:
{
// GetModuleHandle用于获得程序的hInstance,
// 就是WinMain函数那里的hInstnace
HINSTANCE hInstance = GetModuleHandle(NULL);
// WC_BUTTON是按钮类,BS_PUSHBUTTON是按钮的风格
// 下方的代码创建了一个按钮
CreateWindowEx(
0, WC_BUTTON, TEXT("BUTTON"),
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
5, 5, 100, 100, hwnd,
NULL, hInstance, NULL
);
}
case WM_COMMAND:
// 控件的消息通过wParam间接传递
// BN_CLICKED是按钮被点击时产生的消息
// 点击下方链接查看按钮的部分相关内容
// https://zhuanlan.zhihu.com/p/353558221
if (HIWORD(wParam) == BN_CLICKED)
{
MessageBox(hwnd, TEXT("按钮被点击!"), TEXT("你好!"), MB_OK);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

运行本程序,可以看到成功创建了一个按钮,并且点击之后会弹出“按钮被点击”的消息框。

阶段小结

经过了好几篇文章的探索,想必你对Win32程序设计有了更深一步的了解,可以继续探索Windows了。转眼间春节就要到了,星光探索者祝各位春节快乐!

本期到此结束!感谢各位小伙伴的阅读!