windows编程

windows编程学习笔记。

参考资料:

[1] https://fishc.com.cn/forum.php?mod=forumdisplay&fid=255&filter=typeid&typeid=420

[2] https://www.bilibili.com/video/BV1us411A7UE?p=1

第一个程序

1
2
3
4
5
6
7
8
9
10
11
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLIne,
int iCmdShow)
{
MessageBox(NULL, TEXT("Hello world"), TEXT("First"), MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2);
// 父窗口句柄 内容 标题 标志
return 0;
}

Unicode解决方案

ASCII:American标准,只有7位。

双字节字符集(DBCS):ANSI标准,用两个字节来存放。

Unicode解决方案:

  • 统一所有的字符使用两个字节来存放
  • 将所有国家的语言都放到一个字符集里

wchar_t就是一个

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main() {
wchar_t c = L'爱';
wchar_t str[] = L"我爱中国";
setlocale(LC_ALL, "Chs");
wprintf(L"%lc, %ls\n", c, str);
return 0;
}
1
2
3
4
5
6
7
8
#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLIne, int iCmdShow) {
TCHAR *szContent = TEXT("这是我的一个程序");
static TCHAR szCaption[] = TEXT("打招呼");
MessageBox(NULL, szContent, szCaption, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2);
return 0;
}

在Unicode编码下,TEXT就相等于L,即:

1
2
3
TEXT("打招呼");
//等同于
L"打招呼"

在Unicode编码下,TCHAR就相等于wchar_t

窗口

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

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("大家好,这是我的点第一个窗口程序!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

Windows是一个事件驱动,基于消息的操作系统。

事件驱动:

消息机制:

消息机制三点补充:

  • 消息队列是FIFO的形式,先进先出。

  • WM_PAINT, WM_TIMER和WM_QUIT这三个消息属于特例,操作系统会把他们时刻放在消息队列的最后。只有等其他消息执行完毕,才能执行它们。

  • 消息其实会细分为队列化消息和非队列化消息。非队列化消息会直接送往窗口过程,会绕过消息队列。

客户区和非客户区:

HDC: Device Context,设备上下文,设备环境,进行绘图绘画都需要HDC。

PAINTSTRUCT:存放绘制窗口所需要的一些参数

RECT:存放矩形的左上角和右下角。

WM_DESTROY消息:在这个消息里面可以处理文件保存等操作,然后调用PostQuitMessage(0)发送WM_QUIT消息来关闭窗口。

响应鼠标左键的消息:

  • 客户区鼠标点击响应
1
2
3
4
5
6
case WM_LBUTTONDOWN:
MessageBox(hwnd, TEXT("点我干嘛!"), TEXT("鼠标1"), MB_OK);
return 0;
case WM_LBUTTONUP:
MessageBox(hwnd, TEXT("点我干嘛!"), TEXT("鼠标2"), MB_OK);
return 0;
  • 非客户区鼠标点击响应
1
2
3
case WM_NCLBUTTONDOWN:
MessageBox(hwnd, TEXT("在非客户区点我干嘛!"), TEXT("鼠标"), MB_OK);
return 0;

窗口销毁机制:

点击关闭时,提示是否确认关闭的实现:

1
2
3
4
5
case WM_CLOSE:
if (MessageBox(hwnd, TEXT("请问是否真的要关闭窗口"), TEXT("请确认"), MB_YESNO) == IDYES) {
DestroyWindow(hwnd);
}
return 0;

文本输出

设备环境句柄:是应用程序使用GDI函数的通行证

设备环境句柄又称设备上下文,一个普通的数据结构,存放文本的颜色,文本的背景色等信息。

获取设备环境句柄:

1
2
3
4
5
6
7
8
9
//方法一:响应WM_PAINT消息时使用
hed = BeginPaint(hwnd, ps);
//使用GDI函数
EndPaint(hwnd, &ps);

//方法二:响应非WM_PAINT消息时使用
hdc = GetDC(hwnd);
//使用GDI函数
ReleaseDC(hwnd, hdc);

·TextOut:显示文本最重要的函数,可以指定显示的位置。

可以通过GetTextAlign获取当前文本对齐方式,通过调用SetTextAlign修改对齐模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TextOut(
HDC hdc,
int nxStart,
int nyStart,
LPCTSTR lpString, //字符串
int cchsting //字符串长度
);

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
TextOut(hdc, 400, 300, TEXT("大家好,我是某某"),16);
EndPaint(hwnd, &ps);
return 0;

字符串处理

四个常用的函数:

1
2
3
4
5
6
7
wsptrintf

lstrlen

lstrcpy

lstrcat

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

TCHAR szBuffer[128];
const int LINEHEIGHT = 15;
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect)
for (int i = 0; i < 10; ++i){
wsprintf(szBuffer, TEXT("%d: I love china!"), i+1);
TextOut(hdc,0, i * LINEHEIGHT, szBuffer, lstrlen(szBuffer));
}
EndPaint(hwnd, &ps);
return 0;
}

安全的字符串处理函数:

都包含在#include <strsafe.h>中。

1
2
3
4
StringCchPrintf
StringCchLength
StringCchCopy
StringCchCat

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TCHAR szBuffer[12];
const int LINEHEIGHT = 15;
size_t iTarget;
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
for (int i = 0; i < 10; ++i) {
StringCchPrintf(szBuffer, 12, TEXT("%d: %s"), i + 1, TEXT("I love China"));
StringCchLength(szBuffer, 12, &iTarget);
TextOut(hdc, 0, i * LINEHEIGHT, szBuffer, iTarget);
}
EndPaint(hwnd, &ps);
return 0;
}

不要试图猜测文本的大小:

行距:tmHeight+tmExternalLeading

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
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

TCHAR szBuffer[12];
size_t iTarget;

static int cxChar, cyChar;

TEXTMETRIC tm;
switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
ReleaseDC(hwnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
for (int i = 0; i < 10; ++i) {
StringCchPrintf(szBuffer, 12, TEXT("%d: %s"), i + 1, TEXT("I love China"));
StringCchLength(szBuffer, 12, &iTarget);
TextOut(hdc, cxChar, i * cyChar, szBuffer, iTarget);
}
EndPaint(hwnd, &ps);
return 0;
}

获取窗口的各种尺寸

GetSystemMetrics函数返回Windows中各种图形的尺寸形式,包括鼠标指针,图标,标题栏和滚动条等等。

参数nIndex参数是一个索引值,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
TCHAR szBuffer[120];
size_t iTarget;

static int cxClient, cyClient;
TEXTMETRIC tm;
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
cxClient = GetSystemMetrics(SM_CXSCREEN);
cyClient = GetSystemMetrics(SM_CXSCREEN);
GetClientRect(hwnd, &rect);
StringCchPrintf(szBuffer, 120, TEXT("当前显示器分辨率%d * %d px"), cxClient, cyClient);
StringCchLength(szBuffer, 120, &iTarget);
TextOut(hdc, 100, 200, szBuffer, iTarget);
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

滚动条

获取客户区的尺寸

  • 方法一,低级

调用GetSystemMetrics(SM_CXFULLSCREEN),获取窗口最大化时的宽度

调用GetSystemMetrics(SM_CYFULLSCREEN),获取窗口最大化时的高度

  • 方法二,效率太低

调用GetClientRect函数来获取当前客户区的大小

  • 方法三

当主窗口的客户区大小发生变化时,操作系统将给应用程序发送WM_SIZE消息

把客户区的宽度和高度分别存放在lParam参数的低16位和高16位,通过下面两个宏定义实现

LOWORD(lParam)

HIWORD(lParam)

添加滚动条

WS_VSCROLL 垂直滚动条

WS_HSCROLL 水平滚动条

这两个属性添加到CreateWindow函数中的窗口风格中:

1
2
3
4
5
6
7
8
9
10
11
12
//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数

添加了上面的代码只能显示滚动条,但是无法拖动滚动跳来实现翻页或滚动一行。

得通过消息来进行响应,具体得由用户实现,具体响应的消息是:

WM_VSCROLL 垂直

WM_HSCROLL 水平

lParam等于0,表示是标准的窗口滚动条,如果该参数等于滚动条窗口句柄,说明它是滚动条控件

当wParam的低16位是SB_THUMBTRACK时,wParam的高16位是用户拖动滚动条滑块的当前位置

当wParam的低16位是SB_LINEDOWN时,表示用户点击了向下翻动一行

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

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;

switch (message) {
case WM_VSCROLL:
hdc = GetDC(hwnd);
switch(LOWORD(wParam))
{
case SB_LINEUP:
TextOut(hdc, 0, 0, TEXT("向上滚动一行..."), 9);
break;
case SB_LINEDOWN:
TextOut(hdc, 0, 0, TEXT("向下滚动一行..."), 9);
break;
case SB_PAGEUP:
TextOut(hdc, 0, 0, TEXT("向下滚动一页..."), 9);
break;
case SB_PAGEDOWN:
TextOut(hdc, 0, 0, TEXT("向下滚动一页..."), 9);
break;
case SB_THUMBTRACK:
TextOut(hdc, 0, 0, TEXT("别抓着我不放..."), 9);
break;
}
ReleaseDC(hwnd, hdc);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

windows负责:

  • 处理滚动条中的所有鼠标消息
  • 当用户点击滚动条时,提供一种反向显示的闪烁
  • 当用户拖动滑块时,在滚动条内移动滑块
  • 向拥有滚动条的窗口的窗口过程发送滚动条消息

代码负责:

  • 初始化滚动条的范围和位置:SetScrollRange
  • 处理传送给窗口过程的滚动条消息
  • 更新滑块的位置:Set
  • 根据滚动条的变化更新客户区的内容

对应的函数:

  • 设置滚动条范围:SetScrollRange
  • 设置滚动条位置:SetScrollPos
  • 获取滚动条范围:GetScrollRange
  • 获取滚动条位置:GetScrollPos

垂直滚动条设置的流程:

  • 在CreateWindow中设置对应的类型风格,来添加滚动条
  • 定义一个变量表示滚动条的位置,比如iVScrollPos

  • 在WM_CREATE消息中,初始化滚动条的范围和位置

  • 在WM_VSCROLL消息中,响应不同的滚动条操作,从而设定iVScrollPos的值,并通过SetScrollPos来设定滚动条的位置

  • 最后,通过调用InvalidateRect函数或者UpdateWindow来出发一个WM_PAINT消息,实现重绘窗口的效果,实现客户区内容随滚动条移动。这里面主要就是通过iVScrollPos来进行操作。

    SysMets.h

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

#define NUMLINES ((int)(sizeof sysMets / sizeof sysMets[0]))
struct {
int iIndex;
TCHAR * szContent;
TCHAR * szDes;
}
sysMets[] = {
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CXSCREEN, TEXT("SM_CXSCREEN"),
TEXT("Screen width in pixels"),
SM_CYSCREEN, TEXT("SM_CYXCREEN"),
TEXT("Sctreen height in pixels"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"),
TEXT("Vertical scroll width")

};

主程序:

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
#include <windows.h>
#include <strsafe.h>
#include "SysMets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {


static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

TCHAR szBuffer[10];
size_t iTarget;

static int cxChar, cyChar, cxCaps;
static int cxClient, cyClient, iVscrollPos;
TEXTMETRIC tm;
int i, y;
switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth; //字符平均宽度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; //如果低位为0,表示等宽字体,如果为1,表示变宽字体,大写字母是小写字母的1.5倍
cyChar = tm.tmHeight + tm.tmExternalLeading;

SetScrollRange(hwnd, SB_VERT, 0, NUMLINES - 1, FALSE);
SetScrollPos(hwnd, SB_VERT, 0, TRUE);
ReleaseDC(hwnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
for (i = 0; i < NUMLINES; ++i) {
y = cyChar * (i - iVscrollPos);
StringCchLength(sysMets[i].szContent, 128, &iTarget);
TextOut(hdc, 0, y, sysMets[i].szContent, iTarget);

StringCchLength(sysMets[i].szDes, 128, &iTarget);
TextOut(hdc, 22 * cxCaps, y, sysMets[i].szDes, iTarget);

SetTextAlign(hdc, TA_TOP | TA_RIGHT);
StringCchPrintf(szBuffer, 10, TEXT("%d"), GetSystemMetrics(sysMets[i].iIndex));
StringCchLength(szBuffer, 10, &iTarget);
TextOut(hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer, iTarget);

SetTextAlign(hdc, TA_TOP | TA_LEFT);

}
EndPaint(hwnd, &hdc);
return 0;
case WM_VSCROLL:
hdc = GetDC(hwnd);
switch (LOWORD(wParam))
{
case SB_LINEUP:
iVscrollPos -= 1;
break;
case SB_LINEDOWN:
//TextOut(hdc, 0, 0, TEXT("向下滚动一行..."), 9);
iVscrollPos += 1;
break;
case SB_PAGEUP:
iVscrollPos -= (int)(cyClient / cyChar);
//TextOut(hdc, 0, 0, TEXT("向下滚动一页..."), 9);
break;
case SB_PAGEDOWN:
iVscrollPos += (int)(cyClient / cyChar);
//TextOut(hdc, 0, 0, TEXT("向下滚动一页..."), 9);
break;
case SB_THUMBPOSITION:
iVscrollPos = HIWORD(wParam);
//TextOut(hdc, 0, 0, TEXT("别抓着我不放..."), 9);
break;
}

iVscrollPos = max(0, min(iVscrollPos, NUMLINES - 1));
ReleaseDC(hwnd, hdc);
if (iVscrollPos != GetScrollPos(hwnd, SB_VERT)) {
SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE);
InvalidateRect(hwnd, NULL, TRUE);
}

return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

上述实现的问题:

  • 效率低,每次移动一下滚动条需要重绘窗口
  • 实际上所有内容被打印出来,只是在客户区外没有被显示

优化版本:

放弃使用之前的四个函数SetScrollPos等。而使用下面两个函数:

1
2
SetScrollInfo(HWND hwnd, int fnBar, LPCSCROLLINFO lpsi, BOLL fRedraw)
GetScrollInfo()

SCROLLINFO结构体:

1
2
3
4
5
6
7
cbSize    //该结构体大小
fMask //指定设置或获取滚动条参数,该参数由标志组成
nMin //滚动条范围的最小值
nMax //滚动条范围的最大值
nPage //页面的大小
nPos //滑块的位置
nTrackPos //当前滑块的位置

ScrollWindow,ScrollWindowEx可以让客户区的内容滚动起来。

1
2
3
4
5
Bool ScrollWindow(HWND hwnd, 
int XAmount,
int YAmount,
const RECT * lpRect,
const RECT * lpClipRect);

ScrollWindow只滚动已显示的内容,下方的内容会变成失效区域,只要重绘失效区域就行。通过WM_PAINT来实现失效区域的重绘。

具体实现流程:

  • WM_CREATE消息中获取字体的宽度和高度
  • WM_SIZE消息中,设置滚动条的范围和页面大小
  • WM_VSCRLL消息中,响应垂直滚动条的操作,并设置滚动条的位置,以及滚动客户区中的内容,并发送WM_PAINT消息以实现重绘失效区域。
  • WM_PAINT消息中,重绘失效区域
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
#include <windows.h>
#include <strsafe.h>
#include "SysMets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {


static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

TCHAR szBuffer[10];
size_t iTarget;

static int cxChar, cyChar, cxCaps;
static int cxClient, cyClient, iVscrollPos;
TEXTMETRIC tm;
int i, y;

SCROLLINFO si;
int yPos, FirstLine, LastLine;

switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth; //字符平均宽度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; //如果低位为0,表示等宽字体,如果为1,表示变宽字体,大写字母是小写字母的1.5倍
cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC(hwnd, hdc);
return 0;

case WM_SIZE:
hdc = GetDC(hwnd);

//设置滚动条,包括范围和页面大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMax = NUMLINES-1;
si.nMin = 0;
si.nPage = cyClient / cyChar;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

ReleaseDC(hwnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

//获取滚动条位置
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_VERT, &si);
yPos = si.nPos;

//计算需要重绘的区域
FirstLine = max(0, yPos + ps.rcPaint.top / cyChar);
LastLine = min(NUMLINES-1, yPos + ps.rcPaint.bottom / cyChar);

//重绘失效区域
for (i = FirstLine; i <= LastLine; ++i) {
y = cyChar * (i - yPos);
StringCchLength(sysMets[i].szContent, 128, &iTarget);
TextOut(hdc, 0, y, sysMets[i].szContent, iTarget);

StringCchLength(sysMets[i].szDes, 128, &iTarget);
TextOut(hdc, 22 * cxCaps, y, sysMets[i].szDes, iTarget);

SetTextAlign(hdc, TA_TOP | TA_RIGHT);
StringCchPrintf(szBuffer, 10, TEXT("%d"), GetSystemMetrics(sysMets[i].iIndex));
StringCchLength(szBuffer, 10, &iTarget);
TextOut(hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer, iTarget);

SetTextAlign(hdc, TA_TOP | TA_LEFT);

}
EndPaint(hwnd, &ps);
return 0;
case WM_VSCROLL:
//获取滚动条的位置
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);

//记录当前滚动条位置,以为了后续判断是否滚动条位置发生变化
yPos = si.nPos;

//响应各种操作
switch (LOWORD(wParam))
{
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
}

//设置滚动条位置
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

//重新获取滚动条位置
GetScrollInfo(hwnd, SB_VERT, &si);

//判断滚动条位置是否发生变化
if (yPos != si.nPos) {
//滚动客户区的内容
ScrollWindow(hwnd, 0, cyChar * (yPos - si.nPos), NULL, NULL);
//发生WM_PAINT消息,重新绘制失效区域
UpdateWindow(hwnd);
}

return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

窗口绘画

SetPixel:绘制一个车像素点为指定颜色

GetPixel:获取一个像素点的颜色

SetPixelV:比SetPixel快

1
2
SetPixle(HWND hwnd, int x, int y, COLORREF crColor);
//crColor是一个RGB

画直线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int i;

switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
for (i = rect.left; i < rect.right; ++i)
SetPixel(hdc, i, 100, RGB(255, 0, 0));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

MoveToEx:指定直线的起点

LineTo:指定直线的终点

GetCurrentPositionEx:获取当前位置的坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL MoveToEx(HDC hdc, 
int X,
int Y,
LPPOINT lpPoint); //lpPoint之前坐标,NULL表示不返回

BOOL LineTo(HDC hdc,
int nXEnd,
int nYEnd);


case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 100, 100, NULL); //如果不指定起点,则默认是左上角
LineTo(hdc, 600, 100);
EndPaint(hwnd, &ps);
return 0;

练习题:

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

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int i;

switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

//网格实现
GetClientRect(hwnd, &rect);
for (i = rect.top; i < rect.bottom; i += 50) {
MoveToEx(hdc, rect.left, i, NULL);
LineTo(hdc, rect.right, i);
}
for (i = rect.left; i < rect.right; i += 50) {
MoveToEx(hdc, i, rect.top, NULL);
LineTo(hdc, i, rect.bottom);
}


//五角星
MoveToEx(hdc, 30, 10, NULL);
LineTo(hdc, 20, 50);
LineTo(hdc, 50, 20);
LineTo(hdc, 10, 20);
LineTo(hdc, 40, 50);
LineTo(hdc, 30, 10);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

折线

Polyline:画一条折线

PolylineTo:可以修改当前位置,画折线时从起点开始,其他跟Polyline相同。

PolyPolyline:同时画多条折线

1
2
3
4
5
6
7
POINT apt[] = { 30, 10, 20, 50, 50, 20, 10, 20, 40, 50, 30, 10 };
Polyline(hdc, apt, 6);
PolylineTo(hdc, apt, 6);

POINT apt[] = { 50, 20, 20, 60, 80, 60, 50, 20, 70, 20, 100, 60, 130, 20, 70, 20, 150, 20, 150, 60, 190, 60, 190, 20, 150, 20};
int asz[] = { 4,4,5 };
PolyPolyline(hdc, apt, asz, 3);

sin(x)绘制:

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
#include <windows.h>
#include <math.h>
#define NUM 1000
#define TWOPI (2 * 3.14159)

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int cxClient, cyClient;
int i;
POINT apt[NUM];

switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

MoveToEx(hdc, 0, cyClient / 2, NULL);
LineTo(hdc, cxClient, cyClient / 2);
for (i = 0; i < NUM; ++i) {
apt[i].x = cxClient * i / NUM;
apt[i].y = (int)(cyClient / 2 * (1 - sin(TWOPI * i / NUM)));
}

PolylineTo(hdc, apt, NUM);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

贝塞尔曲线:

PolyBezier:不会使用画笔的当前位置,并不会改变最后画笔的位置

PolyBezierTo:使用画笔的当前位置,并改变最后画笔的位置

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static int cxClient, cyClient;
int i;
static POINT apt[4];

switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
apt[0].x = cxClient / 4;
apt[0].y = cyClient / 2;
apt[1].x = cxClient / 2;
apt[1].y = cyClient / 4;
apt[2].x = cxClient / 2;
apt[2].y = cyClient * 3 / 4;
apt[3].x = cxClient * 3 / 4;
apt[3].y = cyClient / 2;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
PolyBezier(hdc, apt, 4);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

边框绘制函数:

Rectangle

Ellipse

RoundRect

Arc

Chord

Pie

1
2
3
4
5
6
7
8
9
10
11
12
13
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
//PolyBezier(hdc, apt, 4);
Rectangle(hdc, 100, 100, 300, 300);
Rectangle(hdc, 150, 50, 200, 300);
Ellipse(hdc, 200, 200, 300, 400);
RoundRect(hdc, 400, 400, 500, 600, 20, 20);
//Arc(hdc, 100, 300, 500, 500, 300,300, 50, 500);
//Chord(hdc, 100, 300, 500, 500, 300, 300, 50, 500);
Pie(hdc, 100, 300, 500, 500, 300, 300, 50, 500);

EndPaint(hwnd, &ps);
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, cxClient / 8, cyClient / 8, cxClient * 7 / 8, cyClient * 7 / 8);
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, cxClient, cyClient);
MoveToEx(hdc, cxClient, 0, NULL);
LineTo(hdc, 0, cyClient);
Ellipse(hdc, cxClient / 8, cyClient / 8, cxClient * 7 / 8, cyClient * 7 / 8);
RoundRect(hdc, cxClient / 4, cyClient / 4, cxClient * 3 / 4, cyClient * 3 / 4, cxClient / 4, cyClient / 4);

EndPaint(hwnd, &ps);
return 0;

画笔

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
HGDIOBJ SelectObject(HDC hdc,
HGDIOBJ hgiobj
); //选择画笔放入设备环境中

HGDIOBJ GetStockObject(int fnObject); //获取画笔

HPEN CreatePen(int fnPenStyle,
int nWidth,
COLORREF crColor); //创建画笔

HPEN CreatePenIndirect();//使用LOGPEN结构,一般用于批量创建多个画笔,这样就可以创建一个LOGPEN结构体数组,然后用循环去创建

BOOL DeleteObject(HGDIOBJ hObject); //用于删除一个逻辑画笔,画刷,字体,位图,区域或者调色板,注意系统的画笔无法删除

SetBkMode(hdc, TRANSPARENT); //设置背景模式,TRANSPARENT表示透明

SetBkColor(hdc, RGB(0, 255, 0)); //设置画笔的背景颜色,此时应该是非透明的

/*
二元光栅操作:
SetOp2
GetOp2
*/

SetROP2(hdc, R2_NOT); //设置画笔颜色与客户区背景颜色相反

int GetROP2(HDC hdc); //获取设备环境的前景色混合模式

实例:

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
HPEN hPen, hOldPen;
switch (message) {

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
//hPen = GetStockObject(BLACK_PEN);
hPen = CreatePen(PS_SOLID, 30, RGB(100, 0, 0));
//hPen = CreatePen(PS_DOT, 2, RGB(255, 0, 0)); //画虚线时,必须小于等于1,
hOldPen = SelectObject(hdc, hPen);
Ellipse(hdc, 100, 100, 300, 300);
hPen = CreatePen(PS_INSIDEFRAME, 30, RGB(255, 0, 0)); //内部扩展
SelectObject(hdc, hPen);
Ellipse(hdc, 400, 100, 500, 300);

SelectObject(hdc, hOldPen);

MoveToEx(hdc, 0, 100, NULL);
LineTo(hdc, rect.right, 100);

MoveToEx(hdc, 0, 300, NULL);
LineTo(hdc, rect.right, 300);

//MoveToEx(hdc, rect.left, (rect.bottom - rect.top) / 2, NULL);
//LineTo(hdc, rect.right, (rect.bottom - rect.top) / 2);
SelectObject(hwnd, hOldPen);
EndPaint(hdc, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

画刷

画刷不是一个一个像素填充的,而是8*8像素的位图来填充的,效率更高。

画刷:

1
2
3
CreateSolidBrush       //创建实心画刷
CreateHatchBrush
CreateBrushIndirect //其参数为LOGBRUSH的结构体,用于批量初始化画刷

绘制填充区域

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static HBRUSH hBrush, hOldBrush;
switch (message) {

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
TextOut(hdc, (rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2, TEXT("I love china"), 12);
//hBrush = GetStockObject(NULL_BRUSH);
//hBrush = GetStockObject(LTGRAY_BRUSH);
hBrush = CreateSolidBrush(RGB(255, 255, 0)); //实心画刷//需要DeleteObject(hBrush)

hOldBrush = SelectObject(hdc, hBrush);
Ellipse(hdc, (rect.right - rect.left) / 4, (rect.bottom - rect.top) / 4,
(rect.right - rect.left) * 3 / 4, (rect.bottom - rect.top) * 3 / 4);
SelectObject(hdc, hOldBrush);
EndPaint(hdc, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

CreateHatchBrush的六种不同阴影:

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static HBRUSH hBrush, hOldBrush;
switch (message) {

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);

SetBkColor(hdc, RGB(0, 0, 0)); //设置画刷背景为黑色

hBrush = CreateHatchBrush(HS_BDIAGONAL, RGB(255, 255, 0));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) /7, (rect.bottom - rect.top) / 5,
(rect.right - rect.left) * 2 / 7, (rect.bottom - rect.top) * 2 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

hBrush = CreateHatchBrush(HS_CROSS, RGB(255, 0, 255));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) * 3 / 7, (rect.bottom - rect.top) / 5,
(rect.right - rect.left) * 4 / 7, (rect.bottom - rect.top) * 2 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

hBrush = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 0, 0));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) * 5 / 7, (rect.bottom - rect.top) / 5,
(rect.right - rect.left) * 6 / 7, (rect.bottom - rect.top) * 2 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

hBrush = CreateHatchBrush(HS_FDIAGONAL, RGB(0, 0, 255));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) / 7, (rect.bottom - rect.top) * 3/ 5,
(rect.right - rect.left) * 2 / 7, (rect.bottom - rect.top) * 4 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

hBrush = CreateHatchBrush(HS_HORIZONTAL, RGB(0, 255, 0));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) * 3 / 7, (rect.bottom - rect.top) * 3/ 5,
(rect.right - rect.left) * 4 / 7, (rect.bottom - rect.top) * 4 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

hBrush = CreateHatchBrush(HS_VERTICAL, RGB(0, 255, 255));
hOldBrush = SelectObject(hdc, hBrush);
Rectangle(hdc, (rect.right - rect.left) * 5 / 7, (rect.bottom - rect.top)*3 / 5,
(rect.right - rect.left) * 6 / 7, (rect.bottom - rect.top) * 4 / 5);
DeleteObject(SelectObject(hdc, hOldBrush));

EndPaint(hdc, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

多边形

Polygon

PolyPolygon

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
POINT apt1[5] = { 100, 100, 200, 100, 200, 200, 100,200, 100, 100 };
POINT apt2[4] = { 300, 100, 400, 100, 400, 200, 300,200 };
HBRUSH hBrush, hOldBrush;

switch(message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
hBrush = CreateSolidBrush(RGB(255, 0, 0));
hOldBrush = SelectObject(hdc, hBrush);
Polyline(hdc, apt1, 5); //画线
Polygon(hdc, apt2, 4); //多边形

DeleteObject(SelectObject(hdc, hOldBrush));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

多边形填充模式:

SetPolyFillMode函数

  • ALTERNATE:交替式,默认。遇到奇数边填充,偶数边不填充。
  • WINDING:螺旋式。填充一笔能够完成的区域。
1
SetPolyFillMode(WINDING);

映射模式

默认为MM_TEXT。

int iMapMode = GetMapMode(hdc)获取当前映射模式。

通过SetMapMode(hdc,MM_TEXT)来设置。用完最后设置为默认模式。

image-20200504163819609

逻辑单位和设备单位。

逻辑单位:独立与设备,编程用到的是逻辑单位。

设备单位:显示在显示器时,会将逻辑单位转换成设备单位,这个转换由映射模式决定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int iMapMode;
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 100, 100, TEXT("I love China"), 12);
iMapMode = GetMapMode(hdc);
SetMapMode(hdc, MM_LOMETRIC);
TextOut(hdc, 100, -100, TEXT("I love Chinese"), 14);
SetMapMode(hdc, iMapMode);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

视口与窗口

编程时,使用的是逻辑单位,所操作的范围,我们称之为窗口。

人眼所见时,是设备坐标,所见的范围,我们称之为对应视口。

设备坐标的单位是像素,映射模式决定逻辑坐标的单位。

绘制sin函数:

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
#include <math.h>
#define NUM 1000
#define TWOPI (2*3.14159)

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int iMapMode, i;
static int cxClient, cyClient;
POINT apt[NUM];

switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);


MoveToEx(hdc, 0, cyClient / 2, NULL);
LineTo(hdc, cxClient, cyClient / 2);

MoveToEx(hdc, cxClient / 2, 0, NULL);
LineTo(hdc, cxClient / 2, cyClient);

iMapMode = GetMapMode(hdc);
SetMapMode(hdc, MM_LOMETRIC); //重新设置映射模式
SetViewportOrgEx(hdc, 0, cyClient / 2, NULL); //重新设定原点

GetClientRect(hwnd, &rect);
DPtoLP(hdc, (PPOINT)(&rect), 2); //设备坐标转换成逻辑坐标
for (i = 0; i < NUM; ++i) {
apt[i].x = (rect.right - rect.left) * i / NUM;
apt[i].y = (int)(rect.top * sin(TWOPI * i / NUM));
}


Polyline(hdc, apt, NUM);
SetMapMode(hdc, iMapMode);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

自定义映射模式:

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int iMapMode, i;
static int cxClient, cyClient;

switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

iMapMode = GetMapMode(hdc);
//SetMapMode(hdc, MM_ANISOTROPIC); //不裁剪,而是将高度和宽度分为100份
SetMapMode(hdc, MM_ISOTROPIC); //裁剪
SetWindowExtEx(hdc, 100, 100, NULL); //设置逻辑单位
SetViewportExtEx(hdc, cxClient / 2, cyClient / 2, NULL); //设置设备单位,结合逻辑单位计算比例
SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);

Rectangle(hdc, -100, -100, 100, 100);
Ellipse(hdc, -100, -100, 100, 100);

SetMapMode(hdc, iMapMode);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

MM_ISOTROPIC模式下,可以通过设置SetViewportExtEx()中设备单位的正负来改变坐标轴的方向。

默认情况下,y轴向下为正,x轴向右为正。

1
2
3
SetMapMode(hdc, MM_ISOTROPIC);     //裁剪
SetWindowExtEx(hdc, 100, 100, NULL); //设置逻辑单位
SetViewportExtEx(hdc, cxClient / 2, -cyClient / 2, NULL); //设置设备单位,结合逻辑单位计算比例

上述代码中,y轴向上为正,x轴向右为正。

处理矩形

FillRect:填充矩形

FrameRect:画矩形的边框

InvertRect:矩形的填充色置为背景的取反

SetRect:初始化矩形

OffsetRect:偏移矩形

InflateRect:缩放矩形

SetRectEmpty:将矩形置为空

CopyRect:拷贝矩形

IntersectRect:求两个矩形的交集

UnionRect:求两个矩形的并集

IsRectEmpty:判断是一个矩形是否为空

PtInRect:检测一个点是否在矩形当中

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect, rectC;
int iMapMode, i;
static int cxClient, cyClient;
HBRUSH hBrush;
switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
/*
rect.top = cyClient / 2 - 50;
rect.left = cxClient / 2 - 50;
rect.bottom = cyClient / 2 + 50;
rect.right = cxClient / 2 + 50;
*/
SetRect(&rect, cxClient / 2 - 50, cyClient / 2 - 50, cxClient / 2 + 50, cyClient / 2 + 50); //对rect进行赋值



hBrush = CreateSolidBrush(RGB(255, 0, 0));
OffsetRect(&rect, -50, -50); //将矩形进行移动
FillRect(hdc, &rect, hBrush); //填充矩形
FrameRect(hdc, &rect, hBrush); //画边框
InvertRect(hdc, &rect); //颜色对背景色取反

SetRect(&rectC, cxClient / 2 + 100, cyClient / 2 - 50, cxClient / 2 + 200, cyClient / 2 + 50); //对rect进行赋值
InflateRect(&rectC, 100, 100); //将矩形扩大缩小, 向上增加100,向下增加100,向左向右都增加100
FrameRect(hdc, &rectC, hBrush); //画边框

SetRectEmpty(&rect); //将矩形置空
CopyRect(&rectC, &rect); //拷贝矩形
//IntersectRect()
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

PtInRect测试:

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
static RECT rect, rectC;
static int cxClient, cyClient;
HBRUSH hBrush;
POINT point;
switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_LBUTTONDOWN:
point.x = LOWORD(lParam);
point.y = HIWORD(lParam);
if (PtInRect(&rect, point)) {
MessageBox(hwnd, TEXT("被点中了!!"), TEXT("Demo"), MB_OK);
}
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

SetRect(&rect, cxClient / 2 - 50, cyClient / 2 - 50, cxClient / 2 + 50, cyClient / 2 + 50); //对rect进行赋值
hBrush = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hdc, &rect, hBrush); //填充矩形

EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

随机矩形

PeekMessage函数检查线程的消息队列并获取消息(如果存在的话)。

利用PeekMessage函数来实现利用程序执行的空余时间绘制矩形。具体代码如下:

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
#include <windows.h>
#include <math.h>
#define NUM 1000
#define TWOPI (2*3.14159)


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawRectangle(HWND hwnd);

static int cxClient, cyClient;

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (TRUE) {//WM_QUIT消息,返回0
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
//让程序空闲的时候执行的代码
DrawRectangle(hwnd);
}
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
switch (message) {
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

void DrawRectangle(HWND hwnd) {
HBRUSH hBrush;
HDC hdc;
RECT rect;
if (cxClient == 0 || cyClient == 0)
return;
SetRect(&rect, rand() % cxClient, rand() % cyClient, rand() % cxClient, rand() % cyClient);

hBrush = CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256));

hdc = GetDC(hwnd);
FillRect(hdc, &rect, hBrush);
DeleteObject(hBrush);
ReleaseDC(hwnd, hdc);
}

区域和裁剪

区域是对显示器一块空间的描述,这个空间可以是矩形,多边形或椭圆的组合。

我们可以使用区域进行绘图或剪裁。

将区域选入设备环境,就可以使用这个区域来剪裁,也就是绘制动作限制在客户区的一个特定部分。

同画刷和画笔,区域也是一个GDI对象,应当通过调用DeleteObject函数来删除所建立的区域。

创建区域:

创建区域的API函数:

键盘

击键消息:点击鼠标一瞬间的消息,SYS表示属于系统关注的消息,交给window默认处理就可以,比如Alt+F4

  • WM_KEYDOWN
  • WM_KEYUP
  • WM_SYSKEYDOWN
  • WM_SYSKEYUP

字符消息:

  • WM_CHAR
  • WM__DEADCHAR
  • WM_SYSCHAR
  • WM_SYSDEADCHAR

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
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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

TCHAR szBuffer[10];
size_t iTarget;

static int cxChar, cyChar, cxCaps;
static int cxClient, cyClient, iVscrollPos;
TEXTMETRIC tm;
int i, y;

SCROLLINFO si;
int yPos, FirstLine, LastLine;

switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth; //字符平均宽度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; //如果低位为0,表示等宽字体,如果为1,表示变宽字体,大写字母是小写字母的1.5倍
cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC(hwnd, hdc);
return 0;

case WM_SIZE:
hdc = GetDC(hwnd);

//设置滚动条,包括范围和页面大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMax = NUMLINES-1;
si.nMin = 0;
si.nPage = cyClient / cyChar;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

ReleaseDC(hwnd, hdc);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

//获取滚动条位置
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_VERT, &si);
yPos = si.nPos;

//计算需要重绘的区域
FirstLine = max(0, yPos + ps.rcPaint.top / cyChar);
LastLine = min(NUMLINES-1, yPos + ps.rcPaint.bottom / cyChar);

//重绘失效区域
for (i = FirstLine; i <= LastLine; ++i) {
y = cyChar * (i - yPos);
StringCchLength(sysMets[i].szContent, 128, &iTarget);
TextOut(hdc, 0, y, sysMets[i].szContent, iTarget);

StringCchLength(sysMets[i].szDes, 128, &iTarget);
TextOut(hdc, 22 * cxCaps, y, sysMets[i].szDes, iTarget);

SetTextAlign(hdc, TA_TOP | TA_RIGHT);
StringCchPrintf(szBuffer, 10, TEXT("%d"), GetSystemMetrics(sysMets[i].iIndex));
StringCchLength(szBuffer, 10, &iTarget);
TextOut(hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer, iTarget);

SetTextAlign(hdc, TA_TOP | TA_LEFT);

}
EndPaint(hwnd, &ps);
return 0;
case WM_VSCROLL:
//获取滚动条的位置
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);

//记录当前滚动条位置,以为了后续判断是否滚动条位置发生变化
yPos = si.nPos;

//响应各种操作
switch (LOWORD(wParam))
{
case SB_TOP:
si.nPos = si.nMin;
break;
case SB_BOTTOM:
si.nPos = si.nMax;
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
}

//设置滚动条位置
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

//重新获取滚动条位置
GetScrollInfo(hwnd, SB_VERT, &si);

//判断滚动条位置是否发生变化
if (yPos != si.nPos) {
//滚动客户区的内容
ScrollWindow(hwnd, 0, cyChar * (yPos - si.nPos), NULL, NULL);
//发生WM_PAINT消息,重新绘制失效区域
UpdateWindow(hwnd);
}

return 0;

case WM_KEYDOWN:
switch (LOWORD(wParam)) {
case VK_UP:
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
break;
case VK_DOWN:
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
break;
case VK_PRIOR:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
break;
case VK_NEXT:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
break;
case VK_HOME:
SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
break;
case VK_END:
SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
break;
}
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

字符消息:

WM_DEADCHAR:有些国家的字母是有符号,比如A上有两个点等等。

消息的参数:

image-20200505163631841

如果RegisterClass使用的是RegisterClassW使用的就是Unicode,如果使用的是RegisterClassA就使用的ANSI,或者是使用IsWindowUnicode来判断。

用户按下A键并释放时,窗口过程会接收到以下三个消息:

image-20200505163410839

如果用户先按下Shift,在按下A,释放A按键,在释放Shift按键,窗口过程收到5个消息:

image-20200505164145565

如果持续按下A键,那么会连续产生击键消息和相应的字符消息。

插入符号

5个基本的插入符号函数:

1
2
3
4
5
CreateCaret();   //创建和窗口相关的插入符号
SetCaretPos(); //设置窗口内的插入符号的位置
ShowCaret(); //显示插入符号
HideCaret(); //隐藏插入符号
DestroyCaret(); //销毁插入符号

通过响应WM_SETFOCUS消息和WM_KILLFOCUS消息来判断窗口是否具备输入焦点。

TextEdit的实现:

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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#include <windows.h>

#define BUFFER(x, y) *(pBuffer + y * cxBuffer + x)

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
static DWORD dwCharSet = DEFAULT_CHARSET;
static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer, cxCaret, cyCaret;
TEXTMETRIC tm;
static TCHAR * pBuffer = NULL;
int x, y, i;

switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); //设置为等宽字符,方便计算插入符的位置
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC(hwnd, hdc);
case WM_SIZE:
if (message == WM_SIZE) {
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
}

cxBuffer = max(cxClient / cxChar, 1);
cyBuffer = max(cyClient / cyChar, 1);

if (pBuffer != NULL) {
free(pBuffer);
}

pBuffer = (TCHAR *)malloc(sizeof(TCHAR) * cxBuffer * cyBuffer);

for (y = 0; y < cyBuffer; ++y) {
for (x = 0; x < cxBuffer; ++x) {
BUFFER(x, y) = ' ';
}
}

cxCaret = 0;
cyCaret = 0;

if (hwnd == GetFocus()) {
SetCaretPos(cxCaret*cxChar, cyCaret*cyChar);
}

InvalidateRect(hwnd, NULL, TRUE); //重绘整个窗口
return 0;

case WM_SETFOCUS:
CreateCaret(hwnd, NULL, cxChar, cyChar);
SetCaretPos(cxCaret * cxChar, cyCaret * cyChar);
ShowCaret(hwnd);
return 0;

case WM_KILLFOCUS:
HideCaret(hwnd);
DestroyCaret();
return 0;

case WM_KEYDOWN:
switch (wParam) {
case VK_HOME:
cxCaret = 0;
break;
case VK_END:
cxCaret = cxBuffer - 1;
break;
case VK_PRIOR:
cyCaret = 0;
break;
case VK_NEXT:
cyCaret = cyBuffer - 1;
break;
case VK_LEFT:
cxCaret = max(0, cxCaret - 1);
break;
case VK_RIGHT:
cxCaret = min(cxCaret + 1, cxBuffer - 1);
break;
case VK_UP:
cyCaret = max(0, cyCaret - 1);
break;
case VK_DOWN:
cyCaret = min(cyCaret + 1, cyBuffer - 1);
break;
case VK_DELETE:
for (x = cxCaret; x < cxBuffer-1; ++x) {
BUFFER(x, cyCaret) = BUFFER(x + 1, cyCaret);
}
BUFFER(cxBuffer-1, cyCaret) = ' ';

HideCaret(hwnd);
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
TextOut(hdc, cxCaret * cxChar, cyCaret * cyChar, &BUFFER(cxCaret, cyCaret), cxBuffer - cxCaret);
ReleaseDC(hwnd, hdc);
ShowCaret(hwnd);
break;
}
SetCaretPos(cxCaret * cxChar, cyCaret * cyChar);
return 0;

case WM_CHAR:
for (i = 0; i < (int)LOWORD(lParam); ++i) { //低16位表示重复的次数
switch (wParam) {
case '\b': //退格
if (cxCaret > 0) {
--cxCaret;
SendMessage(hwnd, WM_KEYDOWN, VK_DELETE, 1);
}
break;
case '\t':
do {
SendMessage(hwnd, WM_CHAR, ' ', 1);
} while (cxCaret % 8 != 0);
break;
case '\n':
if (++cyBuffer == cyBuffer) {
cyCaret = 0;
}
break;
case '\r':
cxCaret = 0;
if (++cyCaret == cyBuffer) {
cyBuffer = 0;
}
break;
case '\x1B': //十六进制18,对应ASCII字符是ESC
for (y = 0; y < cyBuffer; ++y) {
for (x = 0; x < cxBuffer; ++x)
BUFFER(x, y) = TEXT(' ');
}
cxCaret = 0, cyCaret = 0;

InvalidateRect(hwnd, NULL, FALSE);
break;

default:
BUFFER(cxCaret, cyCaret) = (TCHAR)wParam;

HideCaret(hwnd);
hdc = GetDC(hwnd);
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
TextOut(hdc, cxCaret * cxChar, cyCaret * cyChar, &BUFFER(cxCaret, cyCaret), 1);
ShowCaret(hwnd);
ReleaseDC(hwnd, hdc);

if (++cxCaret == cxBuffer) {
cxCaret = 0;
if (++cyCaret == cyBuffer) {
cyCaret = 0;
}
}
break;
}
}
SetCaretPos(cxCaret*cxChar, cyCaret * cyChar);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
for (y = 0; y < cyBuffer; ++y) {
TextOut(hdc, 0, y*cyChar, &BUFFER(0, y), cxBuffer);
}

EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

客户区鼠标消息

1
fMouse = GetSystemMetrics(SM_MOUSEPRESENT);

判断是否链接鼠标,如果鼠标已被安装,fMouse的值是非0,否则是0。

1
cButtons = GetSystemMetrics(SM_CMOUSEBUTTONS);

判断鼠标的按钮个数。

基本术语:

  • 热点(hot spot):鼠标指针的精确位置
  • 单机
  • 双击
  • 拖动
  • LBUUTON,MBUTTON,RBUTTON

收到鼠标消息,不一定需要具备焦点,这跟键盘消息不同。

只要有鼠标

窗口的客户区上移动,窗口过程会收到WM_MOUSEMOVE消息。

在客户区内按下或释放鼠标按钮时,窗口过程也会收到相应的消息:

image-20200506114157779

lParam参数:

对于收到的这些消息,其lParam参数都包含了触发该消息时鼠标的位置信息:

1
2
LOWORD(lParam);  //x坐标
HIWORD(lParam); //y坐标

现在不推荐使用上面的两个宏来获取鼠标的坐标,因为在多显示器的情况下会得到错误的坐标,应该使用:

1
2
3
#include <windowsx.h> //需要包含该头文件
GET_X_LPARAM(lParam)
GET_Y_LPARAM(lParam)

wParam参数:

它用来表示鼠标按钮,Shitf键,Ctrl键的状态:

image-20200506114557378

比如当收到鼠标消息时,wParam & WM_LBUTTON的值为TRUE,则表示此时鼠标左键是被按下的状态。

对于鼠标双击。

如果窗口类型没有包含说CS_DBLCLKS,则双击过程中产生的消息:

image-20200506143425257

如果窗口类型包含CS_DBLCLKS,则产生的消息为:

image-20200506143458506

1
2
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;                    
//窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘

在捕获双击的时候,需要注意不能存在捕获单机的消息,不然会无法捕获到双击的消息,这个时候需要注意逻辑问题。

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
static int iCount;
int i, j;
static POINT apt[1000];

switch (message) {
case WM_LBUTTONDBLCLK: //捕获双击消息,如果有捕获单击消息,需要注意逻辑,不然没发捕获双击消息
MessageBox(hwnd, TEXT("666666"), TEXT("提示"), MB_OK);
return 0;
case WM_LBUTTONDOWN: //鼠标点下清空窗口
if (wParam & MK_SHIFT) { //判断是否同时按下了Shift或Control
if (wParam & MK_CONTROL) {
MessageBox(hwnd, TEXT("同时按下了Ctrl和Shift键"), TEXT("提醒"), MB_OK);
}
else {
MessageBox(hwnd, TEXT("同时按下了Shift键"), TEXT("提醒"), MB_OK);
}
}
else {
if (wParam & MK_CONTROL) {
MessageBox(hwnd, TEXT("同时按下了Control键"), TEXT("提醒"), MB_OK);
}
else {
MessageBox(hwnd, TEXT("没有按下了Shift和Control键"), TEXT("提醒"), MB_OK);
}
}
iCount = 0;
InvalidateRect(hwnd, NULL, TRUE);
return 0;

case WM_MOUSEMOVE: //鼠标移动绘制点,并记录点
if (wParam & MK_LBUTTON && iCount < 1000) {
apt[iCount].x = LOWORD(lParam);
apt[iCount].y = HIWORD(lParam);


hdc = GetDC(hwnd);
SetPixel(hdc, apt[iCount].x, apt[iCount].y, RGB(0, 0, 0));
ReleaseDC(hwnd, hdc);

++iCount;
}
return 0;
case WM_LBUTTONUP: //鼠标左键松开,重绘窗口,不重绘背景,并触发WM_PAINT消息
InvalidateRect(hwnd, NULL, FALSE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

SetCursor(LoadCursor(NULL, IDC_WAIT)); //设置鼠标为加载状态
ShowCursor(TRUE); //显示鼠标

for (i = 0; i < iCount - 1; ++i) {
for (j = i + 1; j < iCount; ++j) {
MoveToEx(hdc, apt[i].x, apt[i].y, NULL);
LineTo(hdc, apt[j].x, apt[j].y);
}
}

ShowCursor(FALSE);
SetCursor(LoadCursor(NULL, IDC_ARROW));

EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

非客户区鼠标消息

只要鼠标在窗口的非客户区上移动,窗口过程会收到WM_NCMOUSEMOVE消息。

在非客户区内按下或释放鼠标按钮时,窗口过程也会收到响应的消息:

image-20200506144228796

lParam参数:

对于收到的这些消息,其lParam参数都包含了触发该消息时鼠标的位置信息:

1
2
LOWORD(lParam);  //x坐标
HIWORD(lParam); //y坐标

现在不推荐使用上面的两个宏来获取鼠标的坐标,因为在多显示器的情况下会得到错误的坐标,应该使用:

1
2
3
#include <windowsx.h> //需要包含该头文件
GET_X_LPARAM(lParam)
GET_Y_LPARAM(lParam)

利用下边的函数可以互相转换屏幕坐标和客户区坐标:

1
2
ScreenToClient(hwnd, &pt); //POINT pt;
ClientToScreen(hwnd, &pt);

wParam参数:

与客户区鼠标消息不同,非客户区鼠标消息的wParm消息的值被设定为一个以“HT”为首的标识符,用于表示非客户区鼠标移动或点击的位置。

image-20200506144831002

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
#include <windows.h>
#include <windowsx.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
TCHAR pszDest[200];
size_t cChDest = 200;
LPCSTR pszFormat = TEXT("关键按钮被按下,当前鼠标的坐标为(屏幕坐标):x = %d, y = %d,转换为客户区坐标后是:x = %d, y = %d");
POINT ptScreen, ptClient;
switch (message) {
case WM_NCLBUTTONDOWN:
if (wParam == HTCLOSE){
ptScreen.x = GET_X_LPARAM(lParam);
ptScreen.y = GET_Y_LPARAM(wParam);
ptClient.x = ptScreen.x;
ptClient.y = ptScreen.y;
ScreenToClient(hwnd, &ptClient);
StringCchPrintf(pszDest, cChDest, pszFormat, ptScreen.x, ptScreen.y, ptClient.x, ptClient.y);
MessageBox(hwnd, pszDest, TEXT("提醒"), MB_OK);
SendMessage(hwnd, WM_CLOSE, 0, 0); //保证窗口能过关闭
}
return DefWindowProc(hwnd, message, wParam, lParam); //处理其他客户区鼠标消息

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("大家好,这是我的点第一个窗口程序!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

击中测试消息:

WM_NCHITTEST

鼠标点击实例:

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

#define DIVISIONS 5

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
static BOOL state[DIVISIONS][DIVISIONS];
PAINTSTRUCT ps;
static int x, y, cxBlock, cyBlock;
RECT rect;
switch (message) {
case WM_SIZE:
cxBlock = GET_X_LPARAM(lParam) / DIVISIONS;
cyBlock = GET_Y_LPARAM(lParam) / DIVISIONS;

return 0;

case WM_LBUTTONDOWN:
x = GET_X_LPARAM(lParam) / cxBlock;
y = GET_Y_LPARAM(lParam) / cyBlock;

if (x < DIVISIONS && y < DIVISIONS) {
state[x][y] ^= 1;
rect.left = x * cxBlock;
rect.top = y * cyBlock;
rect.bottom = (y + 1)*cyBlock;
rect.right = (x + 1)*cxBlock;
InvalidateRect(hwnd, NULL, FALSE);
}
else {
MessageBeep(0);
}
return;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
Rectangle(hdc, x * cxBlock, y *cyBlock, (x + 1)*cxBlock, (y + 1)*cyBlock);
if (state[x][y]) {
MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL);
LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);
LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
}
}
}

EndPaint(hwnd, &ps);
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

键盘模拟鼠标操作

使用跟鼠标点击相同的实例,通过键盘在方格内移动。

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
static BOOL state[DIVISIONS][DIVISIONS];
PAINTSTRUCT ps;
static int x, y, cxBlock, cyBlock;
RECT rect;
POINT point;
switch (message) {
case WM_SIZE:
cxBlock = GET_X_LPARAM(lParam) / DIVISIONS;
cyBlock = GET_Y_LPARAM(lParam) / DIVISIONS;

return 0;
case WM_KEYDOWN:
GetCursorPos(&point);
ScreenToClient(hwnd, &point);

x = max(0, min(DIVISIONS - 1, point.x / cxBlock));
y = max(0, min(DIVISIONS - 1, point.y / cyBlock));

switch (wParam) {
case VK_UP:
--y;
break;
case VK_DOWN:
++y;
break;
case VK_LEFT:
--x;
break;
case VK_RIGHT:
++x;
break;
case VK_HOME:
x = y = 0;
break;
case VK_END:
x = y = DIVISIONS - 1;
break;
case VK_RETURN:
case VK_SPACE:
SendMessage(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x*cxBlock, y*cyBlock));
break;
}
x = (x + DIVISIONS) % DIVISIONS;
y = (y + DIVISIONS) % DIVISIONS;

point.x = x * cxBlock + cxBlock / 2;
point.y = y * cyBlock + cyBlock / 2;

ClientToScreen(hwnd, &point);
SetCursorPos(point.x, point.y);

return 0;

case WM_SETFOCUS:
ShowCursor(TRUE);
return 0;
case WM_KILLFOCUS:
ShowCursor(FALSE);
return 0;

case WM_LBUTTONDOWN:
x = GET_X_LPARAM(lParam) / cxBlock;
y = GET_Y_LPARAM(lParam) / cyBlock;

if (x < DIVISIONS && y < DIVISIONS) {
state[x][y] ^= 1;
rect.left = x * cxBlock;
rect.top = y * cyBlock;
rect.bottom = (y + 1)*cyBlock;
rect.right = (x + 1)*cxBlock;
InvalidateRect(hwnd, NULL, FALSE);
}
else {
MessageBeep(0);
}
return;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
Rectangle(hdc, x * cxBlock, y *cyBlock, (x + 1)*cxBlock, (y + 1)*cyBlock);
if (state[x][y]) {
MoveToEx(hdc, x * cxBlock, y * cyBlock, NULL);
LineTo(hdc, (x + 1)*cxBlock, (y + 1)*cyBlock);
MoveToEx(hdc, x * cxBlock, (y + 1) * cyBlock, NULL);
LineTo(hdc, (x + 1)*cxBlock, y*cyBlock);
}
}
}

EndPaint(hwnd, &ps);
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

子窗口

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

#define DIVISIONS 5

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szChildClass[] = TEXT("CHECK_CHILD");

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//注册子窗口
wndclass.lpfnWndProc = ChildWndProc;
wndclass.cbWndExtra = sizeof(long);
wndclass.hIcon = NULL;
wndclass.lpszClassName = szChildClass;

RegisterClass(&wndclass);

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数




//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

static HWND hwndChild[DIVISIONS][DIVISIONS];
static int x, y, cxBlock, cyBlock;

switch (message) {
case WM_CREATE:
for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
//创建子窗口句柄
hwndChild[x][y] = CreateWindow(szChildClass,
NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hwnd,
(HMENU)(y << 8 | x), //相同的父窗口中,每个子窗口的id不能相同
(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), //获取窗口的实例句柄
NULL);
}
}
return 0;

//确定子窗口的位置和尺寸
case WM_SIZE:
cxBlock = GET_X_LPARAM(lParam) / DIVISIONS;
cyBlock = GET_Y_LPARAM(lParam) / DIVISIONS;
//初始化子窗口大小和位置
for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
MoveWindow(hwndChild[x][y], cxBlock*x, cyBlock*y, cxBlock, cyBlock, TRUE);
}
}
return 0;

//点击在非子窗口时
case WM_LBUTTONDOWN:
MessageBeep(0);
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message) {
//初始化额外参数为0,表示没有被点击
case WM_CREATE:
SetWindowLongPtr(hwnd, 0, 0); //设定子窗口中额外参数的值为0(第二个0),第一个0表示额外参数的便宜
return 0;

//点击该子窗口时,将额外参数的值与1异或
case WM_LBUTTONDOWN:
SetWindowLongPtr(hwnd, 0, 1 ^ GetWindowLongPtr(hwnd, 0)); //1表示被点中
InvalidateRect(hwnd, NULL, FALSE);
return 0;

//绘制子窗口
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
if (GetWindowLongPtr(hwnd, 0)) {
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, rect.right, rect.bottom);
MoveToEx(hdc, 0, rect.bottom, NULL);
LineTo(hdc, rect.right, 0);
}
EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

丰富上面的实例,可以键盘在各个子窗口中移动。

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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#include <windows.h>
#include <windowsx.h>

#define DIVISIONS 5

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);

TCHAR szChildClass[] = TEXT("CHECK_CHILD"); //子窗口名字
int idFocus = 0; //记录当前焦点所在子窗口的id

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//注册子窗口
wndclass.lpfnWndProc = ChildWndProc;
wndclass.cbWndExtra = sizeof(long);
wndclass.hIcon = NULL;
wndclass.lpszClassName = szChildClass;

RegisterClass(&wndclass);

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数




//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {

static HWND hwndChild[DIVISIONS][DIVISIONS];
static int x, y, cxBlock, cyBlock;

switch (message) {
case WM_CREATE:
for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
//创建子窗口句柄
hwndChild[x][y] = CreateWindow(szChildClass,
NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hwnd,
(HMENU)(y << 8 | x), //相同的父窗口中,每个子窗口的id不能相同
(HINSTANCE)GetWindowLongPtr(hwnd, GWL_HINSTANCE), //获取窗口的实例句柄
NULL);
}
}
return 0;

//确定子窗口的位置和尺寸
case WM_SIZE:
cxBlock = GET_X_LPARAM(lParam) / DIVISIONS;
cyBlock = GET_Y_LPARAM(lParam) / DIVISIONS;
//初始化子窗口大小和位置
for (x = 0; x < DIVISIONS; ++x) {
for (y = 0; y < DIVISIONS; ++y) {
MoveWindow(hwndChild[x][y], cxBlock*x, cyBlock*y, cxBlock, cyBlock, TRUE);
}
}
return 0;

//点击在非子窗口时
case WM_LBUTTONDOWN:
MessageBeep(0);
return 0;

case WM_SETFOCUS:
SetFocus(GetDlgItem(hwnd, idFocus)); //指定id的子窗口句柄
return 0;

case WM_KEYDOWN:
x = idFocus & 0xFF;
y = idFocus >> 8;

switch (wParam) {
case VK_UP:
--y;
break;
case VK_DOWN:
++y;
break;
case VK_LEFT:
--x;
break;
case VK_RIGHT:
++x;
break;
case VK_HOME:
x = y = 0;
break;
case VK_END:
x = y = DIVISIONS - 1;
break;
default:
return 0;
}

x = (x + DIVISIONS) % DIVISIONS;
y = (y + DIVISIONS) % DIVISIONS;

idFocus = y << 8 | x;

SetFocus(GetDlgItem(hwnd, idFocus));

return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message) {
//初始化额外参数为0,表示没有被点击
case WM_CREATE:
SetWindowLongPtr(hwnd, 0, 0); //设定子窗口中额外参数的值为0(第二个0),第一个0表示额外参数的便宜
return 0;

case WM_KEYDOWN:
if (wParam != VK_RETURN && wParam != VK_SPACE) { //如果按下的不是回车和空格键,给主窗口发送消息,让主程序处理上下左右操作
SendMessage(GetParent(hwnd), message, wParam, lParam);
return 0;
}
//没有return
//如果是回车和空格键,则直接给当前的子窗口获得焦点,并将额外参数设定为被点中

//点击该子窗口时,将额外参数的值与1异或
case WM_LBUTTONDOWN:
SetWindowLongPtr(hwnd, 0, 1 ^ GetWindowLongPtr(hwnd, 0)); //1表示被点中
SetFocus(hwnd);
InvalidateRect(hwnd, NULL, FALSE);
return 0;


case WM_SETFOCUS://当前子窗口获得焦点,将idFocus设定为在子窗口的id,同时重绘子窗口,通过不要return来实现
idFocus = GetWindowLongPtr(hwnd, GWL_ID);
//不要return

case WM_KILLFOCUS:
InvalidateRect(hwnd, NULL, TRUE);
return 0;

//绘制子窗口
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
Rectangle(hdc, 0, 0, rect.right, rect.bottom);
if (GetWindowLongPtr(hwnd, 0)) {
MoveToEx(hdc, 0, 0, NULL);
LineTo(hdc, rect.right, rect.bottom);
MoveToEx(hdc, 0, rect.bottom, NULL);
LineTo(hdc, rect.right, 0);
}

//给获得焦点的子窗口绘制一个小虚线窗口
if (hwnd == GetFocus()) {
rect.left += rect.right / 10;
rect.right -= rect.left;
rect.top += rect.bottom / 10;
rect.bottom -= rect.top;

SelectObject(hdc, GetStockObject(NULL_BRUSH));
SelectObject(hdc, CreatePen(PS_DASH, 0, 0));
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
DeleteObject(SelectObject(hdc, GetStockObject(BLACK_PEN)));
}

EndPaint(hwnd, &ps);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

程序运行结果:

image-20200506223947680

捕获鼠标

如何捕获在窗口之外的鼠标消息。

通过SetCapture(hwnd)来捕获,通过ReleaseCapture()来释放

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

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBoxOutline(HWND hwnd, POINT ptBeg, POINT ptEnd);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}


//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数




//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}


//绘制鼠标移动时的透明矩形
void DrawBoxOutline(HWND hwnd, POINT ptBeg, POINT ptEnd) {
HDC hdc = GetDC(hwnd);

SetROP2(hdc, R2_NOT); //将画笔的颜色进行取反,为了实现删除之前绘制的透明矩形的
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);

ReleaseDC(hwnd, hdc);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static BOOL fBlocking, fValidBox; //fBlocking表示鼠标已经点下,还未松开; fValidBox表示当前已经绘制了一个黑色矩形
static POINT ptBeg, ptEnd, ptBoxBeg, ptBoxEnd; //ptBeg和ptBeg表示鼠标移动时绘制的矩形,另外两个表示鼠标释放时绘制的黑色矩形
HDC hdc;
PAINTSTRUCT ps;

switch (message) {
case WM_LBUTTONDOWN: //鼠标点下时
ptBeg.x = ptEnd.x = GET_X_LPARAM(lParam);
ptBeg.y = ptEnd.y = GET_Y_LPARAM(lParam);

SetCapture(hwnd); //捕获窗口外的鼠标,只能是当前线程
DrawBoxOutline(hwnd, ptBeg, ptEnd);
SetCursor(LoadCursor(NULL, IDC_CROSS));

fBlocking = TRUE;
return 0;

case WM_MOUSEMOVE: //鼠标移动时
if (fBlocking == TRUE) {
DrawBoxOutline(hwnd, ptBeg, ptEnd); //用相反的颜色再画一次,即去掉现在的边框
SetCursor(LoadCursor(NULL, IDC_CROSS));

ptEnd.x = GET_X_LPARAM(lParam);
ptEnd.y = GET_Y_LPARAM(lParam);

DrawBoxOutline(hwnd, ptBeg, ptEnd); //用再次相反的颜色,重新画一个边框
}
return 0;

case WM_LBUTTONUP: //鼠标释放时
if (fBlocking == TRUE) {
DrawBoxOutline(hwnd, ptBeg, ptEnd);

ptBoxBeg = ptBeg;
ptBoxEnd.x = GET_X_LPARAM(lParam);
ptBoxEnd.y = GET_Y_LPARAM(lParam);

ReleaseCapture(); //释放窗口的鼠标捕获
SetCursor(LoadCursor(NULL, IDC_ARROW));
fBlocking = FALSE;

fValidBox = TRUE;

InvalidateRect(hwnd, NULL, TRUE);
}

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

//绘制鼠标释放时的黑色矩形
if (fValidBox == TRUE) {
SelectObject(hdc, GetStockObject(BLACK_BRUSH));
Rectangle(hdc, ptBoxBeg.x, ptBoxBeg.y, ptBoxEnd.x, ptBoxEnd.y);
}

if (fBlocking) {
SetROP2(hdc, R2_NOT);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
}

EndPaint(hwnd, &ps);
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

鼠标滚轮

利用鼠标滚轮实现滚动条的移动。

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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#include <windows.h>
#include <strsafe.h>
#include "SysMets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {


static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;

TCHAR szBuffer[10];
size_t iTarget;

static int cxChar, cyChar, cxCaps;
static int cxClient, cyClient, iVscrollPos;
TEXTMETRIC tm;
int i, y;

SCROLLINFO si;
int yPos, FirstLine, LastLine;

static iDeltaPerLine, iAccumDelta; //iDeltaPerLine表示滚动一行Delta增加的值
ULONG ulScrollLines;

switch (message) {
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth; //字符平均宽度
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2; //如果低位为0,表示等宽字体,如果为1,表示变宽字体,大写字母是小写字母的1.5倍
cyChar = tm.tmHeight + tm.tmExternalLeading;

ReleaseDC(hwnd, hdc);

//没有return

//设置改变或初始化时,计算每行的delta的值
case WM_SETTINGCHANGE:
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ulScrollLines, 0);
if (ulScrollLines) {
iDeltaPerLine = WHEEL_DELTA / ulScrollLines; //每行的detal
}
else {
iDeltaPerLine = 0;
}
return 0;



case WM_SIZE:
hdc = GetDC(hwnd);

//设置滚动条,包括范围和页面大小
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMax = NUMLINES-1;
si.nMin = 0;
si.nPage = cyClient / cyChar;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

ReleaseDC(hwnd, hdc);
return 0;

//响应鼠标滚轮操作
case WM_MOUSEWHEEL:
if (iDeltaPerLine == 0) { //如果每行的delta值为0,表示不支持滚轮
break;
}

iAccumDelta = GET_WHEEL_DELTA_WPARAM(wParam); //得到滚动的delta值

while (iAccumDelta >= iDeltaPerLine) { //向上滚动
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
iAccumDelta -= iDeltaPerLine;
}
while (iAccumDelta <= -iDeltaPerLine) { //向下滚动
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
iAccumDelta += iDeltaPerLine;
}

return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);

//获取滚动条位置
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_VERT, &si);
yPos = si.nPos;

//计算需要重绘的区域
FirstLine = max(0, yPos + ps.rcPaint.top / cyChar);
LastLine = min(NUMLINES-1, yPos + ps.rcPaint.bottom / cyChar);

//重绘失效区域
for (i = FirstLine; i <= LastLine; ++i) {
y = cyChar * (i - yPos);
StringCchLength(sysMets[i].szContent, 128, &iTarget);
TextOut(hdc, 0, y, sysMets[i].szContent, iTarget);

StringCchLength(sysMets[i].szDes, 128, &iTarget);
TextOut(hdc, 22 * cxCaps, y, sysMets[i].szDes, iTarget);

SetTextAlign(hdc, TA_TOP | TA_RIGHT);
StringCchPrintf(szBuffer, 10, TEXT("%d"), GetSystemMetrics(sysMets[i].iIndex));
StringCchLength(szBuffer, 10, &iTarget);
TextOut(hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer, iTarget);

SetTextAlign(hdc, TA_TOP | TA_LEFT);

}
EndPaint(hwnd, &ps);
return 0;
case WM_VSCROLL:
//获取滚动条的位置
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);

//记录当前滚动条位置,以为了后续判断是否滚动条位置发生变化
yPos = si.nPos;

//响应各种操作
switch (LOWORD(wParam))
{
case SB_TOP:
si.nPos = si.nMin;
break;
case SB_BOTTOM:
si.nPos = si.nMax;
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
}

//设置滚动条位置
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

//重新获取滚动条位置
GetScrollInfo(hwnd, SB_VERT, &si);

//判断滚动条位置是否发生变化
if (yPos != si.nPos) {
//滚动客户区的内容
ScrollWindow(hwnd, 0, cyChar * (yPos - si.nPos), NULL, NULL);
//发生WM_PAINT消息,重新绘制失效区域
UpdateWindow(hwnd);
}

return 0;

case WM_KEYDOWN:
switch (LOWORD(wParam)) {
case VK_UP:
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
break;
case VK_DOWN:
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
break;
case VK_PRIOR:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
break;
case VK_NEXT:
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
break;
case VK_HOME:
SendMessage(hwnd, WM_VSCROLL, SB_TOP, 0);
break;
case VK_END:
SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
break;
}
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

计时器

实现计时器,需要使用SetTimer一个函数,通常情况下,在指定时间周期一道,系统就会发送一个缴WM_TIMER的消息通知指定的应用程序。当程序不再需要计时器时,使用KillTimer函数来清除一个计时器。

如果程序在整个运行过程中需要一个计时器,在WinMain函数中或处理WM_Create消息时,调用SetTimer函数;在离开WinMain函数时 或处理WM_DESTROY消息时,调用KillTimer函数。

三种方法实现计时器:

第一种:定时发送wM_TIMER消息

第二种:定时调用计时器回调函数

第三种:让系统自动分配计时器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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int ID_TIMER = 1;
switch (message) {
case WM_CREATE:
SetTimer(hwnd, ID_TIMER, 1000, NULL);
return 0;

case WM_TIMER:
SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND,
(LONG)(CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256))));
InvalidateRect(hwnd, NULL, TRUE);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("大家好,这是我的点第一个窗口程序!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

第二种:

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
void CALLBACK TimerProc(HWND, UINT, UINT, DWORD);

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
int ID_TIMER = 1;
switch (message) {
case WM_CREATE:
SetTimer(hwnd, ID_TIMER, 1000, TimerProc);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("大家好,这是我的点第一个窗口程序!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
KillTimer(hwnd, ID_TIMER);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}


void CALLBACK TimerProc(HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime) {
SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND,
(LONG)(CreateSolidBrush(RGB(rand() % 256, rand() % 256, rand() % 256))));
InvalidateRect(hwnd, NULL, TRUE);
}

第三种:

将SetTimer的hwnd的值设定为NULL,同时nIDEvent参数指定为一个已经存在的定时器,那么这个定时器将和一个已经存在的非NULL的hWind值的定时器一样被替换。此时nIDEvent会被忽略,并将重新产生一个新的定时器ID。适用于程序需要创建很多计时器,不想去声明每个ID。

同时KillTimer的hwnd的值也设定为NULL。

可以修改一个计时器。方法如下:

将hwnd不为NULL,并且hwnd指定的窗口已经有一个具有nIDEvent值的定时器,则已经存在的定时器会被重新产生的定时器替换。注意,前面两个参数一样,后面的参数需要不一样。

各种按钮

按钮类

复选框

编辑框

列表框

组合框

文本字符串

滚动条

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

struct {
int iStyle;
TCHAR * scText;
} button[] = {
BS_PUSHBUTTON, TEXT("PushButton"),
BS_DEFPUSHBUTTON, TEXT("DefPushButton"),
BS_CHECKBOX, TEXT("CheckBox"),
BS_AUTOCHECKBOX, TEXT("AutoCheckBox"),
BS_RADIOBUTTON, TEXT("RadioButton"),
BS_3STATE, TEXT("3State"),
BS_AUTO3STATE, TEXT("Auto3State"),
BS_GROUPBOX, TEXT("GroupBox"),
BS_AUTORADIOBUTTON, TEXT("AutoRadioButton"),
BS_OWNERDRAW, TEXT("Ownerdarw")
};

#define NUM (sizeof(button) / sizeof(button[0]))


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
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.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("中国"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static int cxChar, cyChar;
int i;

switch (message) {
case WM_CREATE:
cxChar = LOWORD(GetDialogBaseUnits()); //GetDialogBaseUnits()返回的值就是字体的大小,低16位为宽
cyChar = HIWORD(GetDialogBaseUnits()); //高16为高

for (i = 0; i < NUM; ++i) {

CreateWindow(TEXT("Button"),
button[i].scText,
WS_CHILD | WS_VISIBLE | button[i].iStyle,
cxChar, cyChar * (1 + 2 *i), //位置
20*cxChar, 7 * cyChar / 4, //大小
hwnd, //父窗口句柄
(HMENU)1,
((LPCREATESTRUCT)lParam)->hInstance,
NULL);
}
return 0;

case WM_COMMAND:
MessageBox(hwnd, TEXT("点击了PushButton按钮"), TEXT("提醒"), MB_OK);
return 0;

case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("大家好,这是我的点第一个窗口程序!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

实现翻牌子程序:

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
214
215
216
217
218
219
#include <windows.h>
#include <stdlib.h>
#include <time.h>

#define NUM 5
#define GROUPBUTTONID (NUM+1)
#define PUSHBUTTON1 (NUM+2)
#define PUSHBUTON2 (NUM+3)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow) {

static TCHAR * szAppName = TEXT("MyWindows");
HWND hwnd; //句柄,特殊的指针,找到窗口的标识
MSG msg; //消息结构,windwos核心就是消息机制
WNDCLASS wndclass; //窗口类结构

wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格, CS_HERDRAW表示水平调整窗口会发生重绘
wndclass.lpfnWndProc = WndProc; //窗口过程
wndclass.cbClsExtra = 0; //预留的额外空间
wndclass.cbWndExtra = 0; //
wndclass.hInstance = hInstance; //实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //光标
wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); //背景
wndclass.lpszClassName = szAppName; //窗口类的名字
wndclass.lpszMenuName = NULL; //菜单名字

//注册窗口,传入窗口类结构指针
if (!RegisterClass(&wndclass)) {//返回NULL
MessageBox(NULL, TEXT("这个改程序需要在Windows NT才能执行"), szAppName, MB_ICONERROR);
return 0;
}

//创建窗口
hwnd = CreateWindow(szAppName, //窗口类名称
TEXT("翻牌子"), //窗口标题
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU, //窗口风格
CW_USEDEFAULT, //初始x坐标
CW_USEDEFAULT, //初始y坐标
CW_USEDEFAULT, //初始x方向尺寸
CW_USEDEFAULT, //初始y方向尺寸
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL); //创建参数


//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新窗口
UpdateWindow(hwnd);

//消息循环
while (GetMessage(&msg, NULL, 0, 0)) {//WM_QUIT消息,返回0
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
static int cxChar, cyChar;
int i, id;
static int winner = -1;
TCHAR * girls[NUM] = { TEXT("王昭君"), TEXT("杨玉环"), TEXT("西施"), TEXT("貂蝉"), TEXT("苍老师") };
static HWND hwndButton[NUM], hwndRandomButton, hwndFuliButton, hwndGroupBox;
switch (message) {
case WM_CREATE:
cxChar = LOWORD(GetDialogBaseUnits()); //GetDialogBaseUnits()返回的值就是字体的大小,低16位为宽
cyChar = HIWORD(GetDialogBaseUnits()); //高16为高

GetWindowRect(hwnd, &rect);
MoveWindow(hwnd, rect.left, rect.top, 27 * cxChar, 13 * cyChar, TRUE);

for (i = 0; i < NUM-1; ++i) {
hwndButton[i] = CreateWindow(
TEXT("button"),
girls[i],
BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE,
2 * cxChar, cyChar * (2 + 2*i),
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)i,
((LPCREATESTRUCT)lParam)->hInstance, NULL
);
}

hwndButton[NUM - 1] = CreateWindow(
TEXT("button"),
girls[NUM - 1],
BS_AUTORADIOBUTTON | WS_CHILD,
2 * cxChar, cyChar * (2 * NUM ),
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)(NUM - 1),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

hwndGroupBox = CreateWindow(
TEXT("button"),
TEXT("妹子们"),
BS_GROUPBOX | WS_CHILD | WS_VISIBLE,
cxChar, cyChar / 2,
12 * cxChar, 10 * cyChar,
hwnd,
(HMENU)(NUM+1),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

CreateWindow(
TEXT("button"),
TEXT("提交"),
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
14 * cxChar, cyChar,
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)(NUM + 2),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

hwndRandomButton= CreateWindow(
TEXT("button"),
TEXT("帮我选择"),
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
14 * cxChar, 3 * cyChar,
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)(NUM + 3),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

hwndFuliButton = CreateWindow(
TEXT("button"),
TEXT("隐藏福利"),
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_DISABLED,
14 * cxChar, 5 * cyChar,
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)(NUM + 4),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

CreateWindow(
TEXT("button"),
TEXT("隐藏按钮"),
BS_OWNERDRAW | WS_CHILD | WS_VISIBLE,
14 * cxChar, 7 * cyChar,
10 * cxChar, 7 * cyChar / 4,
hwnd,
(HMENU)(NUM + 5),
((LPCREATESTRUCT)lParam)->hInstance, NULL
);

return 0;

case WM_COMMAND:
id = LOWORD(wParam);
if (id < NUM) {
winner = id;
}
switch (id) {
//提交按钮
case NUM + 2:
if (winner == -1) {
MessageBox(hwnd, TEXT("启奏陛下,未有妃子被选中"), TEXT("启奏"), MB_OK);
}
else {
MessageBox(hwnd, girls[winner], TEXT("本次临幸的妃子"), MB_OK);
}
break;
//帮我选择按钮
case NUM+3:
SendMessage(GetDlgItem(hwnd, winner), BM_SETCHECK, BST_UNCHECKED, 0); //将之前选择的按钮去掉选中
//获取随机 的id值
srand((unsigned int)time(NULL));
winner = rand() % (NUM - 1);
SendMessage(GetDlgItem(hwnd, winner), BM_SETCHECK, BST_CHECKED, 0);//设置随机选择的按钮选中

EnableWindow(hwndRandomButton, FALSE); //禁止按钮
for (i = 0; i < NUM; ++i) {
EnableWindow(GetDlgItem(hwnd, i), FALSE);
}
break;
//隐藏福利按钮
case NUM+4:
//调整窗口尺寸
GetWindowRect(hwnd, &rect);
MoveWindow(hwnd, rect.left, rect.top, 27 * cxChar, 15 * cyChar, TRUE);

//调整GroupBox的尺寸
MoveWindow(hwndGroupBox, cxChar, cyChar / 2, 12 * cxChar, 12 * cyChar, TRUE);
//将隐藏的选中按钮显示出来
ShowWindow(hwndButton[NUM - 1], SW_SHOWNORMAL);

break;
//隐藏按钮
case NUM+5:
//将隐藏福利按钮设为可用
EnableWindow(hwndFuliButton, TRUE);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;

case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

运行结果:

image-20200507154425806

zxp wechat
欢迎关注微信公众号!