Windows编程初探-了解win编程

在逆向一些软件的时候,会涉及到Windows编程以及API的一些知识,以此初探windows编程

helloworld

1.编写Windows程序,需要头文件,这个文件包括了四个文件

  • windef.h:基本类型定义
  • winbase.h:内核函数
  • wingdi.h:用户接口函数
  • winuser.h: 图形设备接口函数

所有的API都在这些头文件中声明

2.程序入口

控制台程序(console)程序的入口是main函数;GUI程序的入口是WinMain()(动态链接库(DLL)以 DllMain() 为入口函数)

WinMain函数原型

1
2
3
4
5
6
int WINAPI WinMain(
HINSTANCE hInstance, // 当前窗口句柄
HINSTANCE hPrevInstance, // 前一个窗口句柄,Win32下为NULL(Win16留下的废物,目前已弃用)
LPSTR lpCmdLine, // 命令行参数
int nCmdShow // 窗口显示方式
);

句柄:用来告诉程序去哪里寻找需要的数据(不是指针)

3.第一个程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <windows.h>
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
) {
// 调用API 函数MessageBox
int nSelect = MessageBox(NULL, TEXT("Hello World\n烫"), TEXT("first GUI"), MB_OKCANCEL);
if (nSelect == IDOK)
{
MessageBox(NULL, TEXT("你点击了“确定”按钮"), TEXT("提示"), MB_OK);
}
else
{
MessageBox(NULL, TEXT("你点击了“取消”按钮"), TEXT("提示"), MB_OK);
}
return 0;
}

MessageBox() 函数:

1
int WINAPI MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );

可以知道,该api的返回值是int类型,代表了一个按钮消息

  • hWnd:该消息框的父窗口句柄,如果此参数为NULL,则该消息框没有拥有父窗口。
  • lpText:消息框的内容。LPCTSTR 是自定义数据类型,等价于 const char *。
  • lpCaption:消息框的标题。
  • uType:对话框的按钮样式和图标。

4.按钮的类型

微软给出了几种默认的按钮类型(MB_按钮的作用)

按钮 含义
0:MB_OK 默认值,有一个“确认”按钮在里面
1:MB_YESNO 有“是”和“否”两个按钮
2:MB_ABORTRETRYIGNORE 有“中止”,“重试”和“跳过”三个按钮
3:MB_YESNOCANCEL 有“是”,“否”和“取消”三个按钮
4:MB_RETRYCANCEL 有“重试”和“取消”两个按钮
5:MB_OKCANCEL 有“确定”和“取消”两个按钮

uType 还图标(图标对用户有提醒作用):

图标 含义
MB_ICONEXCLAMATION 一个惊叹号出现在消息框:img
MB_ICONWARNING 一个惊叹号出现在消息框(同上)
MB_ICONINFORMATION 一个圆圈中小写字母i组成的图标出现在消息框:img
MB_ICONASTERISK 一个圆圈中小写字母i组成的图标出现在消息框(同上)
MB_ICONQUESTION 一个问题标记图标出现在消息框:img
MB_ICONSTOP 一个停止消息图标出现在消息框:img
MB_ICONERROR 一个停止消息图标出现在消息框(同上)
MB_ICONHAND 一个停止消息图标出现在消息框(同上)

图标和按钮混合使用

1
2
    int nSelect = MessageBox(NULL, TEXT("Hello World\nlallalalallalalalla烫"), TEXT("first GUI"), MB_OKCANCEL|MB_ICONEXCLAMATION);
//用一个 | 符号

5.注意,messagebox虽然返回数字,但是被定义为宏,所以可以使用数字或者宏名称

返回值 含义
IDOK 用户按下了“确认”按钮
IDCANCEL 用户按下了“取消”按钮
IDABORT 用户按下了“中止”按钮
IDRETRY 用户按下了“重试”按钮
IDIGNORE 用户按下了“忽略”按钮
IDYES 用户按下了“是”按钮
IDNO 用户按下了“否”按钮

数据类型

1.Windows使用typedef#define定了很多新的数据类型,但是大多数都是基本数据类型的新名字,这样作可以提高兼容性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef int                 INT;       /* 整形 */
typedef unsigned int UINT; /* 无符号整形 */
typedef unsigned int *PUINT; /* 无符号整形指针 */
typedef int BOOL; /* 布尔类型 */
typedef unsigned char BYTE; /* 字节 */
typedef unsigned short WORD; /* WORD (无符号短整型) */
typedef unsigned long DWORD; /* DOUBLE WORD (无符号长整形)*/
typedef float FLOAT; /* 浮点型 */
typedef FLOAT *PFLOAT; /* 指向float类型指针 */
typedef BOOL near *PBOOL; /* 指向布尔类型指针 */
typedef BOOL far *LPBOOL;
typedef BYTE near *PBYTE; /* 指向字节类型指针 */
typedef BYTE far *LPBYTE;
typedef int near *PINT; /* 整形指针 */
typedef int far *LPINT;
typedef WORD near *PWORD; /* 指向WORD类型的指针 */
typedef WORD far *LPWORD;
typedef long far *LPLONG; /* 指向长整形的指针 */
typedef DWORD near *PDWORD; /* 指向DWORD类型的指针 */
typedef DWORD far *LPDWORD;
typedef void far *LPVOID; /* 指向void类型的指针 */
typedef CONST void far *LPCVOID; /* 指向void类型的常指针 */

可以总结一下规律就是

  • 无符号类型:一般是以“U”开头,比如“INT”对应的“UINT”。
  • 指针类型:其指向的数据类型前加“LP”或“P”,比如指向 DWORD 的指针类为“LPDWORD”和“PDWORD”。
  • 句柄类型:以“H”开头。比如,HWND 是window(WND简写)也就是窗口的句柄,菜单(MENU)类型对应的句柄类型为 “HMENU” 等等。

2.宽字节和unicode

unicode是宽字节编码的一种,需要用多个字节来表示的代码称为宽字符

2.1 使用宽字符

使用wchar.h头文件中的wchar_t来定义宽字符

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <wchar.h>
int main()
{
char ch = 'A';
wchar_t wch = 'A';
char str[] = "C语言中文网";
wchar_t wstr[] = L"C语言中文网"; //L是前缀,告诉编译器这是两字节
printf("ch=%d, wch=%d, str=%d, wstr=%d\n",
sizeof(ch), sizeof(wch), sizeof(str), sizeof(wstr));
return 0;
}

运行结果:ch=1, wch=2, str=12, wstr=14

wstr 之所以比 str 多两个字节是因为:字符 ‘C’ 占用两个字节,字符串结束标志 ‘\0’ 也占用两个字节。

2.2 计算ASCII字符串长度使用 strlen 函数,计算宽字符串长度使用 wcslen 函数:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <wchar.h>
#include <string.h>
int main()
{
char str[] = "Hello API";
wchar_t wstr[] = L"Hello API";
printf("strlen(str)=%d, wcslen(wstr)=%d\n", strlen(str), wcslen(wstr));
return 0;
}

strlen函数将一字节算作是一个字符,wcslen将两个字节算成一个字符

窗口

窗口、控件、图像、音频视频等都称为资源(Resource),在程序中都可以使用、创建、添加、修改等。

句柄

不同窗口、控件、图像等都对应一个唯一的数字(初学者可以理解为 ID),称为句柄(Handle)。

通过句柄,程序可以找对对应的信息。例如:例如用 CreateFile() 函数创建文件后会返回一个文件句柄,然后通过这个句柄就可以读写、删除该文件.

消息机制

1.事件:用户敲击键盘、点击鼠标、拖动窗口、选择菜单、输入文字等所有的操作都称为事件(Event)。

2.当事件发生时,windows会产生一条信息,告诉程序发生了什么。

3.每当事件发生时,Windows 会生成一条消息,并放到一个由系统维护的队列中。然后,程序会自己从这个队列中获取消息并分析,调用事件处理函数(处理事件的代码也就在这个函数中),对用户的操作进行响应。

4.队列:先进先出的数据结构

注意:Windows 向队列中分派消息和应用程序从队列中获取消息并不是同步的,只要有事件发生,就会将消息丢进队列,什么时候处理完毕是应用程序的事。

总结:消息是windows连接程序的桥梁,Windows通过 消息 告诉程序发生了什么,应用程序通过 消息 来办事。

消息结构体

消息其实是一个结构体,名字为 MSG,定义为:

1
2
3
4
5
6
7
8
9
typedef struct tagMSG 
{
HWND hwnd; //消息所属窗口
UINT message; //消息类型(数值)
WPARAM wParam; //同下
LPARAM lParam; //表示的信息随消息的不同而不同。
DWORD time; //消息投递到消息队列中的时间
POINT pt; //鼠标当前位置
} MSG;

Windows 向队列中投递消息,其实就是将一个类型为 MSG 的结构体变量丢进队列。

1.message:因为数值不好记忆,所有Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式。例如:鼠标左键按下消息是WM_LBUTTONDOWN;键盘按下消息WM_KEYDOWN;字符消息是WM_CHA。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。

MSG结构体和WndProc窗口过程详解

MSG结构体

MSG 结构体用来表示一条消息,各个字段的含义如下:

1
2
3
4
5
6
7
8
9
typedef struct tagMSG
{
HWND hwnd; //窗口句柄
UINT message; //消息类型,例如 WM_CREATE、WM_PAINT、WM_DESTROY、WM_COMMAND 等。
WPARAM wParam; //附加消息1
LPARAM lParam; //附加消息2,他们的类型表示32位整数
DWORD time; //消息被传递时候的时间
POINT pt; //消息被传递时光标在屏幕上的位置
} MSG;

1.wParam 和 lParam :wParam、lParam 表示的信息随消息类型的不同而不同。

2.一般约定,wParam 用来表示控件的ID,或者高 16 位和低 16 位组合起来分别表示鼠标的位置,如果发送消息时需要附带某种结构的指针或者是某种类型的句柄时,习惯上用 lParam。

WndProc 窗口过程

1
2
3
4
5
6
7
8
LRESULT CALLBACK WndProc(
HWND hwnd, //窗口句柄
UINT message, //消息类型
WPARAM wParam, //附加消息1
LPARAM lParam //附加消息2
){
// TODO
}

注意:WndProc 的各个参数和 MSG 结构体的前四个字段是一一对应的。需要铭记的是:每产生一条消息,都会调用一次 WndProc 函数。

2.当用户点击按钮、编辑框、下拉列表框等控件的时候,会产生WM_COMMAND消息。对于不同来源的 WM_COMMAND 消息,wParam、lParam 参数也不同,见下表:

消息来源 wParam (高16位) wParam (低16位) lParam
菜单 0 菜单ID 0
快捷键 1 快捷键ID 0
控件 控件通知码 控件ID 控件句柄
  • 控件通知码用来识别控件的操作。例如 Button 控件一般有两种通知码,BN_CLICKED 和 BN_DOUBLECLICKED,前者表示 Button 被单击,后者表示 Button 被双击。

3.对于 Button 控件,我们可以通过LOWORD(wParam)来获取它的 ID。

窗口创建

思路

  • 定义 WinMain 函数
  • 定义 窗口处理函数
  • 注册窗口类
  • 创建窗口
  • 显示窗口
  • 消息循环
  • 消息处理

完整的窗口

一般的窗口包括有:最大化、最小化、关闭按钮,也包含菜单、单选框、图像等各种控件。

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

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

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
static TCHAR szClassName[] = TEXT("HelloWin"); //窗口类名
HWND hwnd; //窗口句柄
MSG msg; //消息
WNDCLASS wndclass; //窗口类

/**********第①步:注册窗口类**********/
//创建的窗口各不相同,windows把通用属性抽取出来用结构体表示
//为窗口类的各个字段赋值
wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格
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.lpszMenuName = NULL ; //窗口菜单
wndclass.lpszClassName= szClassName; //窗口类名

//注册窗口
RegisterClass(&wndclass);
//窗口类仅仅是一个结构体,如果只定义了结构体变量,那么在使用时并不能通过 lpszClassName 字段的值找到这个结构体。所以还要调用 RegisterClass() 来注册,让系统知道窗口类的名字,下次使用时才能找到。

/*****第②步:创建窗口(并让窗口显示出来)*****/
hwnd = CreateWindow(
szClassName, //窗口类的名字
TEXT("Welcome"), //窗口标题(出现在标题栏)
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始化时x轴的位置
CW_USEDEFAULT, //初始化时y轴的位置
500, //窗口宽度
300, //窗口高度
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //当前窗口的句柄
NULL //不使用该值
);

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

/**********第③步:消息循环**********/
while( GetMessage(&msg, NULL, 0, 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("第一个win api程序"),
-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
WNDCLASS wndclass;  //窗口类
//创建的窗口各不相同,windows把通用属性抽取出来用结构体表示
//为窗口类的各个字段赋值
wndclass.style = CS_HREDRAW | CS_VREDRAW;
//窗口风格,这里的取值表示窗口大小改变时重绘窗口,确保内容在中间
wndclass.lpfnWndProc = WndProc;
//窗口过程
wndclass.cbClsExtra = 0; //暂时不需要理解
wndclass.cbWndExtra = 0; //暂时不需要理解
wndclass.hInstance = hInstance;
//当前窗口句柄
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION);
//窗口图标,需要用loadicon来加载
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW);
//鼠标样式
wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH);
//窗口背景画刷,窗口背景的颜色
wndclass.lpszMenuName = NULL ;
//窗口菜单,没有菜单就是null
wndclass.lpszClassName= szClassName;
//窗口类名,方便于其他窗口进行区分

创建窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
hwnd = CreateWindow(
szClassName, // 窗口类的名字,通过这个名字可以找到刚才注册的窗口类
TEXT("Welcome"), //窗口标题(出现在标题栏)
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始化时窗口x轴坐标
CW_USEDEFAULT, //初始化时窗口y轴坐标
500, //窗口宽度
300, //窗口高度
NULL, //父窗口句柄。这里没有父窗口,所以为 NULL
NULL, //窗口菜单句柄。当前窗口没有菜单,所以为 NULL
hInstance, //当前窗口的句柄,通过 WinMain 函数传入。
NULL //不使用该值
);

显示器坐标远点在左上角,横为x,竖为y,没有负数

上述函数只是在内存中创建了窗口,分配了空间,获得了句柄,做好了准备工作,但是并不能显示

显示窗口

1
ShowWindow(hWnd, iCmdShow)//hWnd:指窗口句柄。nCmdShow:指定窗口如何显示。

消息循环

在 UpdateWindow 函数被调用之后,新建的窗口在屏幕中就可以显示了。

此时,程序必须能够接受用户的键盘或鼠标事件

1
2
3
4
5
while( GetMessage(&msg, NULL, 0, 0) )//GetMessage 函数用来从消息队列中获取一条消息,并保存到 MSG 结构体变量中。
{
TranslateMessage(&msg); //翻译消息
DispatchMessage (&msg); //分派消息
}

1.GetMessage 的返回值永远为非零值,while 循环会一直进行下去。如果队列中没有消息,GetMessage 函数会等待,直到有消息进入。

2.获取到消息后,需要调用 TranslateMessage 函数对消息进行转换(翻译),然后再调用 DispatchMessage 函数将消息传给窗口过程去处理(调用窗口过程)。

窗口过程

1.就是处理窗口事件的函数,也就是上面代码中最后的 WndProc 函数。

2.GetMessage 每获取到一条消息,最终都会丢给窗口过程去处理。

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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc; //设备环境句柄
PAINTSTRUCT ps;
RECT rect;
switch (message)//不同的消息往往需要进行不同的处理,所以一般通过 switch case 语句来匹配。
{
//窗口绘制消息
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText(
hdc,
TEXT("第一个win api程序"),
-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自己处理应用程序没有处理的消息,必须要有该语句。
}

绘制和重绘

1.当用户在屏幕移动其他程序的窗口时,遮住当前程序的部分窗口。Windows 不会保存被遮住的那部分窗口,当其他程序的窗口被移开时,Windows 会要求你的程序重新绘制刚才被遮住的部分区域。如果你自己不重绘,Windows 是不会管的。

并不是简单的遮挡关系

2.Windows 是一个消息驱动的系统,它使用两种方式把各种事件通知给应用程序:

  • 把消息放到应用程序的消息队列中,让程序自己通过 GetMessage 函数获取;
  • 向窗口直接发送消息。

WM_PAINT 消息

1.WM_PAINT 消息表示绘制窗口的客户区

以下任何一个事件发生时,窗口过程都会收到一条 WM_PAINT 消息:

  • 用户移动一个窗口,导致原来被遮住的部分窗口暴露出来。
  • 用户调整窗口的大小(当窗口类中 style 字段的值设定为 CS_HREDRAW | CS_VREDRAW 时)。
  • 客户区滚动条滚动时。
  • 当然,你可以可以调用相应的函数强制生成一条 WM_PAINT 消息。

注意:窗口类中 style 字段的值经常被设定为CS_HREDRAW | CS_VREDRAW,只有这样,调整窗口宽度或高度时才会发生重绘

2.在少数情况下,Windows 总是会保存被覆盖的部分窗口,然后再恢复,例如:

  • 鼠标指针在客户区内移动。
  • 在客户区内拖动图标。

在Windows中一切界面都是绘图,一旦界面发生改变,就需要重新绘制。

有效矩形和无效矩形

1.Windows 一般不重绘整个客户区,而是绘制客户区的一部分。需要重新绘制的部分被称为“无效区域”或“更新区域”

在绘制的时候,会将无效区域装进一个“最小矩形之中”,称为 无效矩形

向窗口输出文字—TextOut和DrawText函数

设备环境DC

1.Windows不允许我们直接访问硬件,在与这些硬件通信前要获得设备环境(Device Context,简称 DC),进而间接的访问硬件。

设备环境有时也被称为设备上下文或设备描述表。

2.设备环境:与当前硬件设备有关的各种信息,它是硬件设备的抽象。

3.与文字输出、图形绘制有关的函数,在使用时大都需要传入一个参数,就是设备环境句柄。让程序知道往哪里输出

4.获取句柄:常用的是 BeginPaint 函数。绘图完成后,还要释放句柄,使用 EndPaint 函数。

BeginPaint 和 EndPaint 函数

发生绘制事件时,程序通过bp函数通知windows当前程序需要显示器,bp函数执行完就返回显示器的句柄, 绘图之后,用ep函数通知windows绘图结束,可以往下走了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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) ;
//GetClientRect 函数用来获得窗口客户区的坐标,也就是获得客户区这个矩形。
DrawText(
hdc,
TEXT("第一个win api程序"),
-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) ;
}

TextOut 函数

TextOut 函数可以在客户区输出一段文本,原型为:

1
2
3
4
5
6
7
BOOL TextOut(
HDC hdc, //设备环境句柄
int nXStart, //开始输出位置的x坐标
int nYStart, //开始输出位置的y坐标
LPCTSTR lpString, //要输出的字符串
int cbString //字符串的长度
);

每当发生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
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
HDC hdc; //设备环境句柄
PAINTSTRUCT ps; //存储绘图环境的相关信息
TCHAR szText[20] = TEXT("欢迎来到C语言中文网");
switch (message){
case WM_PAINT:
//开始绘图并返回环境句柄
hdc = BeginPaint (hwnd, &ps);
//输出文字
TextOut(hdc, 50, 50, szText, wcslen(szText));//注意这里的宽字符
//结束绘图并释放环境句柄
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage(0) ;
return 0 ;
}
return DefWindowProc(hwnd, message, wParam, lParam) ;
}

DrawText函数

TextOut 在一个起点开始输出文本,可以精确定位,不能自动换行;

DrawText 在指定区域内输出文本,可以控制格式对,齐居左、居中、居右,可以换行(DrawText 在内部其实也是调用TextOut )

1
2
3
4
5
6
7
8
int DrawText(
HDC hDC, //设备环境句柄
LPCTSTR lpString, //将要输出的字符串
int nCount, //字符串的长度
// 如果 nCount 为 -1,则表明 lpString 指向的字符串是以'\0'结束的,DrawText 会自动计算字符数。
LPRECT lpRect, //指向一个矩形区域的结构体 RECT 的指针
UINT uFormat //输出格式,有四种取值,它们可以任意组合
);

参数 uFormat 有四种取值,它们可以任意组合:用 | 隔开

  • DT_CALCRECT:这个参数比较重要,可以使 DrawText 函数计算出输出文本的尺寸。如果输出文本有多行,DrawText 函数使用 lpRect 定义的矩形的宽度,并扩展矩形的底部以容纳输出文本的最后一行。如果输出文本只有一行,则 DrawText 函数改变矩形的右边界,以容纳下正文行的最后一个字符。出现上述任何一种情况。
  • DT_CENTER:指定文本水平居中显示。
  • DT_VCENTER:指定文本垂直居中显示。该标记只在单行文本输出时有效,所以它必须与DT_SINGLELINE结合使用。
  • DT_SINGLELINE:单行显示文本,回车和换行符都不断行。

Windows GDI绘图基础与轻量进阶

1,简介:GDI 是 Graphics Device Interface 的缩写,称为图形设备接口,主要用来绘图,由动态链接库 GDI32.DLL 提供支持。Windows 本身也使用GDI来显示用户界面,比如菜单、滚动条、图标和鼠标指针等。

GDI 基础

绘制矩形

Rectangle 函数可以在窗口上绘制一个矩形,它的原型为:

1
2
3
4
5
6
7
BOOL Rectangle(
HDC hdc, //设备环境句柄
int nLeftRect, //矩形左上角x坐标
int nTopRect, //矩形左上角y坐标
int nRightRect, //矩形右下角x坐标
int nBottomRect //矩形右下角y坐标
);

示例代码

1
2
3
4
5
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, 50, 50, 150, 150);
EndPaint(hwnd, &ps);
return 0 ;

运行效果就是会在一个窗口内出现一个矩形。

绘制圆形

如果要绘制一个圆形,可以使用 RoundRect 函数,函数的原型是:

1
2
3
4
5
6
7
8
9
BOOL RoundRect(
HDC hdc, //设备环境句柄
int nLeftRect, //矩形左上角x坐标
int nTopRect, //矩形左上角y坐标
int nRightRect, //矩形右下角x坐标
int nBottomRect, //矩形右下角y坐标
int nWidth, //用来画圆角的椭圆的宽度
int nHeight //用来画圆角的椭圆的高度
);

当 nHeight >= nBottomRect 且 nWidth = nRightRect 时,那么绘制出的就是一个圆

1
RoundRect(hdc, 20, 20, 150, 150, 150, 150);

绘制椭圆

Ellipse() 函数可以用来绘制椭圆,它的原型为:

1
2
3
4
5
6
7
BOOL Ellipse(
HDC hdc, //设备环境句柄
int nLeftRect, //左上角x坐标
int nTopRect, //左上角y坐标
int nRightRect, //右下角x坐标
int nBottomRect //右下角y坐标
);

绘制直线

确定起点使用 MoveToEx 函数。MoveToEx 用来指定画笔的起始位置,也就是从哪里开始画,它的原型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL MoveToEx(
HDC hdc, //设备环境句柄
int x, //起始位置x坐标
int y, //起始位置y坐标
LPPOINT lpPoint //指向用于保存当前位置的POINT结构体的指针,直接返回NULL
);

//下面继续设置终点函数
BOOL LineTo(
HDC hdc, //设备环境句柄
int xEnd, //终点的x坐标
int yEnd //终点的y坐标
);

注意:win32不再支持 MoveTo,只支持它的扩展函数 MoveToEx。

(在这里知道后面的Ex是原先api的拓展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);

//DrawText(hdc, TEXT("第一个真正的gui程序"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
//Rectangle(hdc, 50,50,20,20);//用来绘制矩形
//RoundRect(hdc, 20, 20, 150, 150, 150, 150);//绘制圆角或者是绘制圆形
//Ellipse(hdc, 20, 20, 180, 90);//绘制椭圆,也可以绘制圆形
//Ellipse(hdc, 20, 20, 40, 40);//绘制圆形
MoveToEx(hdc, 150, 150, NULL); //设定起始点,不保存当前点坐标
LineTo(hdc, 200, 60); //第一条线
LineTo(hdc, 250, 150); //第二条线
LineTo(hdc, 150, 150); //第三条线



EndPaint(hwnd, &ps);
return 0;
//窗口销毁消息

这样就可以绘制出一个三角形

画笔和画刷

1.画笔就是划线的,画刷就是可以覆盖 一块区域

创建和使用画笔

1.上面的绘图使用的是Windows的默认画笔,也就是宽度为1个像素,颜色为黑色的画笔。

创建画笔的API函数为 CreatePen:

1
2
3
4
5
6
HPEN CreatePen(
int nPenStyle, //画笔的样式
int nWidth, //画笔的宽度
COLORREF crColor //画笔的颜色
);
//因为指定设备环境,所以新创建的画笔与当前设备环境并没有关联,无法使用。

1.画笔样式 nPenStyle 有7种取值:

宏定义 宏定义对应的值 说明
PS_SOLID 0 实线
PS_DASH 1 虚线(段线),要求画笔宽度 <= 1
PS_DOT 2 点线,要求画笔宽度 <= 1
PS_DASHDOT 3 线、点,要求画笔宽度 <= 1
PS_DASHDOTDOT 4 线、点、点,要求画笔宽度 <= 1
PS_NULL 5 不可见
PS_INSIDEFRAME 6 实线,但画笔宽度是向里扩展的

2.画笔宽度 :nWidth 指逻辑宽度。iWidth为 0 则意味着画笔宽度为一个像素。如果画笔样式为点线或者虚线,同时又指定一个大于 1 的画笔宽度,那么Windows将使用实线画笔来代替。

3.画笔的颜色 crColor 可以直接使用 RGB 颜色。

GDI对象

画笔、画刷、字体等被称为GDI对象。可以供 GDI 函数使用。新创建的 GDI 对象必须通过 SelectObject 函数选入设备环境才能使用。

1
2
3
4
HGDIOBJ SelectObject(
HDC hdc, //设备环境句柄
HGDIOBJ ho //GDI对象句柄
);
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
//窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
PAINTSTRUCT ps;
HDC hdc;
//定义一个画笔句柄,请定义为静态变量
static HPEN hPen;
switch (message){
case WM_CREATE:
//创建宽度为2个像素的红色点线画笔,保存句柄到 hPen 变量
hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
//选入画笔到设备环境
SelectObject(hdc, hPen);
//绘制三角形
MoveToEx(hdc, 150, 150, NULL);
LineTo(hdc,200, 60); //第一条线
LineTo(hdc, 250, 150); //第二条线
LineTo(hdc, 150, 150); //第三条线
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
//请做好善后工作,处理 WM_DESTROY 消息时删除之前我们创建的一切GDI对象
DeleteObject(hPen);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

上述代码会画一个红色的三角形。

画笔最好在 WM_CREATE 事件中创建,因为当应用程序运行时会频繁地触发 WM_PAINT 事件,比如窗口被覆盖后再显示、窗口被拖动、窗口被拉伸等,每次都需要重新创建画笔,浪费资源

创建和使用画刷

Windows API 中有两个函数可以用来创建画刷。

1.CreateSolidBrush 函数可以用来创建一个指定颜色的实心画刷

1
HBRUSH CreateSolidBrush( COLORREF crColor );  // crColor为画刷颜色

2.CreateHatchBrush 函数可以用来创建一个指定颜色的含有特定阴影样式的画刷,原型为:

1
2
3
4
HBRUSH CreateHatchBrush(
int fnStyle, //画刷样式,6种样式
COLORREF crColor //画刷颜色
);
  • HS_BDIGONAL: 45度向上,自左至右的阴影(///)
  • HS_CROSS: 表示水平直线和垂直直线交叉阴影(+++)
  • HS_DIAGCROSS: 45度交叉阴影(XXX)
  • HS_FDIAGONAL: 45度向下自左至右的阴影(\)
  • HS_HORIZONTAL:水平阴影(—-)
  • HS_VERTICAL: 垂直阴影
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
//窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
PAINTSTRUCT ps;
HDC hdc;
//定义两个画刷,请定义为静态变量
static HBRUSH hSolidBrush;
static HBRUSH hHatchBrush;
switch (message){
case WM_CREATE:
//创建蓝色实心画刷,保存句柄到 hSolidBrush 变量
hSolidBrush = CreateSolidBrush(RGB(0, 0, 255));
//创建绿色交叉阴影画刷,保存句柄到 hHatchBrush 变量
hHatchBrush = CreateHatchBrush(HS_DIAGCROSS,RGB(0,255,0));
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
//选入蓝色实心画刷到设备环境
SelectObject(hdc, hSolidBrush);
Rectangle(hdc, 0, 0, 200, 100); //绘制矩形
//选入绿色交叉画刷到设备环境
SelectObject(hdc, hHatchBrush);
Ellipse(hdc,0,100,200,200); //绘制椭圆
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
//请做好善后工作,处理 WM_DESTROY 消息时删除之前我们创建的一切GDI对象。
DeleteObject(hSolidBrush);
DeleteObject(hHatchBrush);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

如上程序可以生成带颜色的长方形和矩形

Windows static控件

1.实际开发中一般使用静态文本框控件来输出文本。静态文本框是Windows 的一种标准控件,可以用来在窗口上显示一段文本,并且文本容易受到控制。除了静态文本框,Windows的标准控件还有很多种,例如按钮、下拉菜单、单选按钮、复选框等。

2.控件是子窗口,创建时必须指定父窗口,这样控件才能有“归属”。

1
2
3
4
5
6
7
8
9
10
11
12
13
HWND CreateWindow(
LPCWSTR lpClassName, //窗口类名
LPCWSTR lpWindowName, //窗体标题(或控件文本)
DWORD dwStyle, //窗口/控件样式,WS开头
int x, //窗口相对桌面(或子窗口相对父窗口)的 X 坐标
int y, //窗口相对桌面(或子窗口相对父窗口)的 Y 坐标
int nWidth, //窗体宽度
int nHeight, //窗体高度
HWND hWndParent, //父窗口句柄,独立窗口,那么为 NULL,如果是控件,那么就需要父窗口的句柄。
HMENU hMenu, //菜单句柄
HINSTANCE hInstance, //当前程序实例句柄
LPVOID lpParam //一个指向某数值的指针
);

lpWindowName 应传入窗口的标题,若你希望创建控件,则应传入控件的文本。

写起来程序和上面的程序差不多,但是有一个地方就是`CreateWindow 函数传入的倒数第二个参数为 hInst,表示当前程序的实例句柄。

当前实例句柄是通过 WinMain 函数的参数传入的,所以必须要定义一个全局变量 hInst,然后在 WinMain 中给它赋值后才能使用。

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

HINSTANCE hInst;

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow
)
{
// TODO: 其他代码
hInst = hInstance;
// TODO: 其他代码
}

完整代码

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

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

HINSTANCE hInst; //hInst 在 WndProc 函数中并不存在,
//因为当前实例句柄是通过 WinMain 函数的参数传入的,所以必须要定义一个全局变量 hInst,然后在 WinMain 中给它赋值后才能使用。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szClassName[] = L"HelloWinStatic"; //窗口类名

HWND hwnd; //窗口句柄
MSG msg; //消息
WNDCLASS wndclass; //窗口类
hInst = hInstance;
/**********第①步:注册窗口类**********/
//为窗口类的各个字段赋值
wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格
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.lpszMenuName = NULL; //窗口菜单
wndclass.lpszClassName = szClassName; //窗口类名
//注册窗口
RegisterClass(&wndclass);


/*****第②步:创建窗口(并让窗口显示出来)*****/
hwnd = CreateWindow(
szClassName, //窗口类的名字
TEXT("Welcome2Static"), //窗口标题(出现在标题栏)
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始化时x轴的位置
CW_USEDEFAULT, //初始化时y轴的位置
500, //窗口宽度
500, //窗口高度
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //当前窗口的句柄
NULL //不使用该值
);
//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新(绘制)窗口
UpdateWindow(hwnd);
/**********第③步:消息循环**********/
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); //翻译消息
DispatchMessage(&msg); //分派消息
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
) {
PAINTSTRUCT ps;
HDC hdc;
HWND hStatic;
switch (message) {
case WM_CREATE:
hStatic = CreateWindow(
L"static", //静态文本框的类名
L"C语言中文网", //控件的文本
WS_CHILD /*子窗口*/ | WS_VISIBLE /*创建时显示*/ | WS_BORDER /*带边框*/ | SS_CENTER /*水平居中*/ | SS_CENTERIMAGE /*垂直居中*/,
20 /*X坐标*/, 20 /*Y坐标*/, 200 /*宽度*/, 100 /*高度*/,
hWnd, //父窗口句柄
(HMENU)1, //为控件指定一个唯一标识符
hInst, //当前程序实例句柄
NULL
);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

获取、修改控件文本

1.GetWindowText 函数用于将指定窗口的标题文本(如果存在)拷贝到一个缓存区内;如果指定的窗口是一个控件,则拷贝控件的文本。

1
2
3
4
5
Int GetWindowText(
HWND hWnd, //窗口/控件句柄
LPTSTR lpString, //指向接收文本的缓冲区指针
Int nMaxCount //要保存在缓冲区内的字符的最大个数,包含NULL字符。文本超过界被截断。
);

2.类似的函数:SetWindowText,用来设置窗口标题或者控件文本。

1
2
3
4
BOOL SetWindowText(
HWND hwnd,
LPCTSTR lpString
);

如下代码显示鼠标点击次数

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

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

HINSTANCE hInst;

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow
) {
static TCHAR szClassName[] = TEXT("Win32Demo");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;

hInst = hInstance;


wndclass.style = CS_HREDRAW | CS_VREDRAW;
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.lpszMenuName = NULL;
wndclass.lpszClassName = szClassName;

RegisterClass(&wndclass);

hwnd = CreateWindow(
szClassName,
TEXT("Welcome"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
500,
300,
NULL,
NULL,
hInstance,
NULL
);


ShowWindow(hwnd, iCmdShow);

UpdateWindow(hwnd);


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

return msg.wParam;
}


LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
) {
PAINTSTRUCT ps;
HDC hdc;
static int iClick = 0;
static TCHAR szTextBuf[20];
static HWND hStatic; //静态变量是因为防止在creat结束的时候会销毁无法使用

switch (message) {
case WM_CREATE:
hStatic = CreateWindow(
L"static",
L"LALALALALALALA",
WS_CHILD | WS_VISIBLE | WS_BORDER | SS_CENTER | SS_CENTERIMAGE ,
20 , 20 , 200 , 100 ,
hWnd,
(HMENU)1,
hInst,
NULL
);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_RBUTTONDOWN:
iClick++;
wsprintf(szTextBuf, TEXT("%d"), iClick);//往缓冲区写东西的函数
SetWindowText(hStatic, szTextBuf);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}

1.对于窗口函数中的变量,如果是在 WM_CREATE 消息中赋值,但在其他消息中使用,那么一般声明为静态变量,这样下次执行窗口函数时依然有效。尤其是这种窗口会不断变化的函数

Windows CreateFont:创建自己的字体

创建字体使用 CreateFont 函数,它的原型是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HFONT CreateFont(
int cHeight, //字体的逻辑高度
int cWidth, //字体的逻辑宽度,一般取负数,高是宽的两倍
int cEscapement, //指定移位向量相对X轴的偏转角度
int cOrientation, //指定字符基线相对X轴的偏转角度
int cWeight, //设置字体粗细程度,可以使用 FW_ 开头的宏定义;一般取 FW_NORMAL (400),此参数需要值的值域为 [0,1000]。
DWORD bItalic, //是否启用斜体
DWORD bUnderline, //是否启用下划线
DWORD bStrikeOut, //是否启用删除线。以上这仨,直接TRUE或者FALSE取值就可以
DWORD iCharSet, //指定字符集,直接使用 DEFAULT_CHARSET 让系统自动处理。
DWORD iOutPrecision, //输出精度
DWORD iClipPrecision, //剪裁精度
DWORD iQuality, //输出质量
DWORD iPitchAndFamily, //字体族
LPCSTR pszFaceName //字体名
);

注意:字体也是一种 GDI 对象,使用完后也要在 WM_DESTROY 消息中删除。

1.创建完字体后并不能立即使用,还需要手动触发 WM_SETFONT 消息(设置字体的消息),让Windows 将当前字体设置为我们创建的字体。

2.发送消息使用 SendMessage 函数,它可以让我们在必要时主动向窗口发送各种消息,原型为:

1
2
3
4
5
6
LRESULT SendMessage(	//SendMessage 发送的消息就是由窗口过程来处理的。
HWND hWnd, //发送消息的目标窗口句柄
UINT msg, //将要发送的消息
WPARAM wParam, //附加消息1, 传入 CreateFont 返回的句柄即可
LPARAM lParam //附加消息2
);

Windows button控件(按钮控件)

1.按钮既是标准控件,也是子窗口,窗口类名是button

2.Button 控件样式以BS开头

样式 说明
BS_LEFT 文本居左。
BS_RIGHT 文本居右。
BS_CENTER 文本水平居中(默认为 BS_CENTER)。
BS_BOTTOM 文本位于按钮底部。
BS_TOP 文本位于按钮顶部。
BS_VCENTER 文本垂直居中(默认为 BS_VCENTER)。
BS_FLAT 扁平样式。默认情况下按钮具有3D阴影效果。
BS_MULTILINE 允许显示多行文本。也就是说当文本过长时会自动换行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
){
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static HFONT hFont;
static HWND hBtn;
switch (message){
case WM_CREATE:
//创建逻辑字体
hFont = CreateFont(
-15/*高度*/, -7.5/*宽度*/, 0, 0, 400 /*一般这个值设为400*/,
FALSE/*不带斜体*/, FALSE/*不带下划线*/, FALSE/*不带删除线*/,
DEFAULT_CHARSET, //使用默认字符集
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, //这行参数不用管
DEFAULT_QUALITY, //默认输出质量
FF_DONTCARE, //不指定字体族*/
TEXT("微软雅黑") //字体名
);
//创建按钮控件
hBtn = CreateWindow(
TEXT("button"), //按钮控件的类名
TEXT("这是按钮"),
WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT/*扁平样式*/,
30 /*X坐标*/, 20 /*Y坐标*/, 150 /*宽度*/, 50/*高度*/,
hWnd, (HMENU)2 /*控件唯一标识符*/, hInst, NULL
);
SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, NULL);//设置按钮字体
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
//请做好善后工作
DeleteObject(hFont);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
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
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
LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
){
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static HFONT hFont;
static HWND hBtn;
static HWND hStatic;
switch (message){
case WM_CREATE:
//创建逻辑字体
hFont = CreateFont(
-15/*高度*/, -7.5/*宽度*/, 0, 0, 400 /*一般这个值设为400*/,
FALSE/*不带斜体*/, FALSE/*不带下划线*/, FALSE/*不带删除线*/,
DEFAULT_CHARSET, //使用默认字符集
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, //这行参数不用管
DEFAULT_QUALITY, //默认输出质量
FF_DONTCARE, //不指定字体族*/
TEXT("微软雅黑") //字体名
);
//创建静态文本控件
hStatic = CreateWindow(
TEXT("static"), //静态文本框的类名
TEXT("你好,欢迎来到C语言中文网"), //控件的文本
WS_CHILD /*子窗口*/ | WS_VISIBLE /*创建时显示*/ | WS_BORDER /*带边框*/,
30 /*X坐标*/, 20/*Y坐标*/, 150/*宽度*/, 80/*高度*/, hWnd/*父窗口句柄*/,
(HMENU)1, //为控件指定一个唯一标识符
hInst, //当前实例句柄
NULL
);
//创建按钮控件
hBtn = CreateWindow(
TEXT("button"), //按钮控件的类名
TEXT("点击这里试试"),
WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT/*扁平样式*/,
30 /*X坐标*/, 110 /*Y坐标*/, 150 /*宽度*/, 50/*高度*/,
hWnd, (HMENU)2 /*控件唯一标识符*/, hInst, NULL
);
SendMessage(hStatic,WM_SETFONT,(WPARAM)hFont,NULL);//设置文本框字体
SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, NULL);//设置按钮字体
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId){
case 2: //按下按钮
//更改文本框的内容
SetWindowText( hStatic, TEXT("你点击了下面的按钮") );
break;
default:
//不处理的消息一定要交给 DefWindowProc 处理。
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
//请做好善后工作
DeleteObject(hFont);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

Windows edit控件(编辑框控件)

1.相当于是控制台程序的scanf函数

2.编辑框控件的窗口类名是edit。除了子窗口常用的风格 WS_CHILD、WS_VISIBLE、WS_BORDER 外,edit 控件也有自己的风格,都是以ES开头。

风格 说明
ES_AUTOHSCROLL 当输入文字超出横向显示范围时自动滚动(一般必选)。
ES_AUTOVSCROLL 当输入文字超出纵向显示范围时自动滚动。
ES_CENTER 文本居中对齐。
ES_LEFT 文本左对齐(默认)。
ES_RIGHT 文本右对齐。
ES_MULTILINE 是否允许多行输入。
ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为“*”。
ES_READONLY 是否为只读。禁止用户输入或修改编辑控件中的文本。
ES_UPPERCASE 显示大写字符。
ES_LOWERCASE 显示小写字符。
ES_LOWERCASE 将用户输入到编辑控件的字符全部转换为小写。
ES_UPPERCASE 将用户输入到编辑控件的字符全部转换为大写。
ES_MULTILINE 指明了一个多行编辑控件(缺省的是单行的)。 1) 如果指定了ES_AUTOVSCROLL风格,编辑控件将显示尽可能多的文本,并且当用户按下ENTER键时会自动地垂直滚动文本。 2) 如果没有指定ES_AUTOVSCROLL风格,则编辑控件将显示尽可能多的行,如果在按下ENTER键却没有更多的行要显示的话,就发出蜂鸣声。 3) 如果指定了ES_AUTOHSCROLL风格,当光标到达控件的右边时,多行编辑控件会自动地水平滚动文本。如果要开始一个新行,用户必须按下ENTER键。 4) 如果没有指定ES_AUTOHSCROLL风格,控件会在有必要时自动将单词折合到下一行的开始。如果按下ENTER键,则另起一行。折回单词的位置是由窗口的大小决定的。如果窗口的大小发生改变,折回单词的位置也会反生改变,将会重新显示文本。

实例程序,登录框

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
#include <windows.h>    //代码段2 完整的窗口

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

HINSTANCE hInst;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szClassName[] = TEXT("HelloWin"); //窗口类名

HWND hwnd; //窗口句柄
MSG msg; //消息
WNDCLASS wndclass; //窗口类

hInst = hInstance;

/**********第①步:注册窗口类**********/
//为窗口类的各个字段赋值
wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口风格
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.lpszMenuName = NULL; //窗口菜单
wndclass.lpszClassName = szClassName; //窗口类名
//注册窗口
RegisterClass(&wndclass);


/*****第②步:创建窗口(并让窗口显示出来)*****/
hwnd = CreateWindow(
szClassName, //窗口类的名字
TEXT("Welcome"), //窗口标题(出现在标题栏)
WS_OVERLAPPEDWINDOW, //窗口风格
CW_USEDEFAULT, //初始化时x轴的位置
CW_USEDEFAULT, //初始化时y轴的位置
500, //窗口宽度
300, //窗口高度
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //当前窗口的句柄
NULL //不使用该值
);
//显示窗口
ShowWindow(hwnd, iCmdShow);
//更新(绘制)窗口
UpdateWindow(hwnd);
/**********第③步:消息循环**********/
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); //翻译消息
DispatchMessage(&msg); //分派消息
}
return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
static HFONT hFont; //逻辑字体
static HWND hLabUsername; //静态文本框--用户名
static HWND hLabPassword; //静态文本框--密码
static HWND hEditUsername; //单行文本输入框
static HWND hEditPassword; //密码输入框
static HWND hBtnLogin; //登录按钮
//定义缓冲区
TCHAR szUsername[100];
TCHAR szPassword[100];
TCHAR szUserInfo[200];
switch (message) {
case WM_CREATE:
//创建逻辑字体
hFont = CreateFont(-14/*高*/, -7/*宽*/, 0, 0, 400 /*一般这个值设为400*/,
FALSE/*斜体?*/, FALSE/*下划线?*/, FALSE/*删除线?*/, DEFAULT_CHARSET,
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
FF_DONTCARE, TEXT("微软雅黑")
);
//创建静态文本框控件--用户名
hLabUsername = CreateWindow(TEXT("static"), TEXT("用户名:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE /*垂直居中*/ | SS_RIGHT /*水平居右*/,
0 /*x坐标*/, 20 /*y坐标*/, 70 /*宽度*/, 26 /*高度*/,
hWnd /*父窗口句柄*/, (HMENU)1 /*控件ID*/, hInst /*当前程序实例句柄*/, NULL
);
//创建静态文本框控件--密码
hLabPassword = CreateWindow(TEXT("static"), TEXT("密码:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE /*垂直居中*/ | SS_RIGHT /*水平居右*/,
0, 56, 70, 26,
hWnd, (HMENU)2, hInst, NULL
);
//创建单行文本框控件
hEditUsername = CreateWindow(TEXT("edit"), TEXT(""),
WS_CHILD | WS_VISIBLE | WS_BORDER /*边框*/ | ES_AUTOHSCROLL /*水平滚动*/,
80, 20, 200, 26,
hWnd, (HMENU)3, hInst, NULL
);
//创建密码输入框
hEditPassword = CreateWindow(TEXT("edit"), TEXT(""),
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD /*密码*/ | ES_AUTOHSCROLL /*水平滚动*/,
80, 56, 200, 26,
hWnd, (HMENU)4, hInst, NULL
);
//创建按钮控件
hBtnLogin = CreateWindow(TEXT("button"), TEXT("登录"),
WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT/*扁平样式*/,
80, 92, 200, 30,
hWnd, (HMENU)5, hInst, NULL
);
//依次设置控件的字体
SendMessage(hLabUsername, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(hLabPassword, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(hEditUsername, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(hEditPassword, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(hBtnLogin, WM_SETFONT, (WPARAM)hFont, NULL);
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId) {
case 5: //按钮的HMENU参数
//获取输入框的数据
GetWindowText(hEditUsername, szUsername, 100);
GetWindowText(hEditPassword, szPassword, 100);
wsprintf(szUserInfo, TEXT("C语言中文网提示:\r\n您的用户账号:%s\r\n您的用户密码:%s"), szUsername, szPassword);
MessageBox(hWnd, szUserInfo, TEXT("信息提示"), MB_ICONINFORMATION);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
DeleteObject(hFont);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

修改static控件背景颜色和文字颜色

1.当 static 控件或具有 ES_READONLY 风格的 edit 控件被绘制时,会向父窗口发送 WM_CTLCOLORSTATIC 消息。如果我们在窗口过程中处理该消息,就必须返回一个画刷句柄,Windows 会使用该画刷来绘制控件背景(子窗口背景)。

所以,改变空间颜色的方法就是处理 WM_CTLCOLORSTATIC 消息。

修改文字背景颜色

1.修改文字背景颜色使用 SetBkColor 函数,它的原型为:

1
2
3
4
COLORREF SetBkColor(
HDC hdc, //设备环境句柄
COLORREF crColor //背景颜色
);

2.修改文字前景色(文字颜色)使用 SetTextColor 函数,它的原型为:

1
2
3
4
COLORREF SetTextColor(
HDC hdc, //设备环境句柄
COLORREF crColor //背景颜色
);

这里我们修改的是 static 控件中的文本颜色,所以需要获取 static 控件的设备环境句柄。

非常巧妙的是,发送 WM_CTLCOLORSTATIC 消息时,wParam 参数表示的就是 static 控件的设备环境句柄(lParam 表示控件句柄)。

透明背景

实现透明背景需要设置文本背景颜色透明,同时返回没有颜色的画刷。

1.SetBkMode 函数可以用来设置文本的背景模式,它的原型为:

1
2
3
4
int SetBkMode(
HDC hdc, //设备环境句柄
int iBkMode //背景模式
);

iBkMode 有下面两种取值:

取值 说明
OPAQUE 使用当前背景颜色来填充背景。
TRANSPARENT 背景透明。

2.GetStockObject (NULL_BRUSH); 语句可以返回没有颜色的画刷。示例代码:

1
2
3
4
5
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wParam;
SetTextColor( hdcStatic, RGB(0x41, 0x96, 0x4F) ); //翠绿色
SetBkMode(hdcStatic, TRANSPARENT); //透明背景
return (INT_PTR)GetStockObject(NULL_BRUSH); //无颜色画刷

Windows单选按钮、复选框、分组框控件

单选按钮和复选框都是一种特殊的按钮,窗口类名称都是button

单选按钮的样式为BS_AUTORADIOBUTTON,复选框的样式为BS_AUTOCHECKBOX。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建一个单选按钮
CreateWindow(
TEXT("button"), TEXT("单选按钮"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON/*单选按钮*/,
235, 40, 100, 26,
hWnd, (HMENU)7, hInst, NULL
);
//创建一个复选框
CreateWindow(
TEXT("button"), TEXT("复选框"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX/*复选框*/,
95, 70, 50, 26,
hWnd, (HMENU)9, hInst, NULL
);

想为单选按钮分组,可以增加WS_GROUP样式。设置了 WS_GROUP 样式的单选框为一组中的首元素,随后的所有单选按钮都和它在同一组,直到下一个设置了 WS_GROUP 样式的单选按钮。

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
//窗口过程
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
PAINTSTRUCT ps;
HDC hdc;
static HFONT hFont; //逻辑字体
//一组单选按钮
static HWND labSex; //静态文本框--性别
static HWND radioMale; //单选按钮--男
static HWND radioFemale; //单选按钮--女
//一组单选按钮
static HWND labMarriage; //静态文本框--婚姻状况
static HWND radioMarried; //单选按钮--已婚
static HWND radioSingle; //单选按钮--未婚
static HWND radioSecrecy; //单选按钮--保密
//一组复选框
static HWND labPet; //静态文本框--你的宠物
static HWND checkboxDog; //复选框--狗
static HWND checkboxCat; //复选框--猫
static HWND checkboxFish; //复选框--鱼
static HWND checkboxOther; //复选框--其他
switch (message){
case WM_CREATE:
hFont = CreateFont(
-14, -7, 0, 0, 400,
FALSE, FALSE, FALSE,DEFAULT_CHARSET,
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
FF_DONTCARE, TEXT("微软雅黑")
);
//选择性别
labSex = CreateWindow(
TEXT("static"), TEXT("你的性别:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT/*文字居右*/,
10, 10, 80, 26,
hWnd, (HMENU)1, hInst, NULL
);
radioMale = CreateWindow(
TEXT("button"), TEXT("男"),
WS_CHILD | WS_VISIBLE | BS_LEFT/*文字居左*/ | BS_AUTORADIOBUTTON /*单选按钮*/ | WS_GROUP,
95, 10, 50, 26,
hWnd, (HMENU)2, hInst, NULL
);
radioFemale = CreateWindow(
TEXT("button"), TEXT("女"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
150, 10, 50, 26,
hWnd, (HMENU)2, hInst, NULL
);
//选择婚姻状况
labMarriage = CreateWindow(
TEXT("static"), TEXT("婚姻状况:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT,
10, 40, 80, 26,
hWnd, (HMENU)4, hInst, NULL
);
radioMarried = CreateWindow(
TEXT("button"), TEXT("已婚"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON | WS_GROUP,
95, 40, 65, 26,
hWnd, (HMENU)5, hInst, NULL
);
radioSingle = CreateWindow(
TEXT("button"), TEXT("未婚"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
165, 40, 65, 26,
hWnd, (HMENU)6, hInst, NULL
);
radioSecrecy = CreateWindow(
TEXT("button"), TEXT("保密"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
235, 40, 100, 26,
hWnd, (HMENU)7, hInst, NULL
);
//你的宠物
labPet = CreateWindow(
TEXT("static"), TEXT("你的宠物:"),
WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT,
10, 70, 80, 26,
hWnd, (HMENU)8, hInst, NULL
);

checkboxDog = CreateWindow(
TEXT("button"), TEXT("狗"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX/*复选框*/,
95, 70, 50, 26,
hWnd, (HMENU)9, hInst, NULL
);
checkboxCat = CreateWindow(
TEXT("button"), TEXT("猫"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
150, 70, 50, 26,
hWnd, (HMENU)10, hInst, NULL
);
checkboxFish = CreateWindow(
TEXT("button"), TEXT("鱼"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
205, 70, 50, 26,
hWnd, (HMENU)11, hInst, NULL
);
checkboxOther = CreateWindow(
TEXT("button"), TEXT("其他"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX,
260, 70, 65, 26,
hWnd, (HMENU)11, hInst, NULL
);
SendMessage(labSex, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(radioMale, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(radioFemale, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(labMarriage, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(radioMarried, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(radioSingle, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(radioSecrecy, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(labPet, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(checkboxDog, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(checkboxCat, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(checkboxFish, WM_SETFONT, (WPARAM)hFont, NULL);
SendMessage(checkboxOther, WM_SETFONT, (WPARAM)hFont, NULL);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

分组框控件

分组框控件也是一种特殊的按钮,它的样式为BS_GROUPBOX,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建分组框控件
hGroupSex = CreateWindow(
TEXT("button"), TEXT("你的性别"),
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
20, 20, 300, 70,
hWnd, (HMENU)1, hInst, NULL
);
//接下来添加单选按钮
hRadioMale = CreateWindow(
TEXT("button"), TEXT("男"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON /*单选按钮*/,
15, 30, 50, 26,
hGroupSex /*父窗口为 GroupBox 控件*/, (HMENU)2, hInst, NULL
);
hRadioFemale = CreateWindow(
TEXT("button"), TEXT("女"),
WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON,
80, 30, 50, 26,
hGroupSex, (HMENU)3, hInst, NULL
);

注意父子窗口关系:需要注意的是:单选按钮的父窗口为分组框控件 hGroupSex,而不是顶级窗口 hWnd。

判断单选按钮和复选框是否被选中

对于一组单选按钮,只有一个选项能被选中,最好的办法是将这组按钮作为参数传入一个函数,通过函数返回值判断哪个按钮被选中了。

但遗憾的是,在Windows中不能一次性获得一组按钮的选中状态,只能一个一个地遍历。复选框也是如此。

解决:可以通过SendMessage函数发送BM_GETCHECK消息来获取按钮的选中状态,返回 BST_CHECKED 表示按钮被选中,返回 BST_UNCHECKED 是未被选中。

SendMessage 原型为:

1
2
3
4
5
6
LRESULT WINAPI SendMessage(
HWND hWnd, //窗口(控件)句柄
UINT Msg, //消息类型
WPARAM wParam, //附加消息1
LPARAM lParam //附加消息2
);

注意:发送 BM_GETCHECK 消息时,wParam 和 lParam 两个参数必须都为 0。

如果被选中,那么会获取按钮文本并保存到缓冲区:

1
2
3
4
if( SendMessage(hCheckBox, BM_GETCHECK, 0, 0) == BST_CHECKED )
{
GetWindowText(hCheckBox, szBuffer, 10);
}

程序部分

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
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
//设置缓冲区
static TCHAR szBufSex[10]; //保存性别
static TCHAR szBufMarriage[10]; //保存婚姻状况
static TCHAR szBufPet[20]; //保存宠物
static TCHAR szBufSubmit[100]; //保存提交时显示的文本
static TCHAR szBufTmp[10]; //临时缓冲区
/**
* 为了节省空间,这里省略了各种控件句柄的定义
* 这里的代码与上节相同,请参考上节代码
**/
static HWND btnSubmit; //按钮控件
switch (message){
case WM_CREATE:
/**
* 为了节省空间,这里省略了创建各种控件的语句
* 这里的代码与上节相同,请参考上节代码
**/
//创建按钮控件
btnSubmit = CreateWindow(TEXT("button"), TEXT("提 交"),
WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT,
95, 110, 200, 36,
hWnd, (HMENU)13, hInst, NULL
);
/**
* 为了节省空间,这里省略了设置字体的语句
* 这里的代码与上节相同,请参考上节代码
**/
SendMessage(btnSubmit, WM_SETFONT, (WPARAM)hFont, NULL);
break;
//这里是重点
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
if( wmEvent== BN_CLICKED){
switch (wmId){
//获取性别
case 2:
case 3:
GetWindowText((HWND)lParam, szBufSex, 10);
break;
//获取婚姻状况
case 5:
case 6:
case 7:
GetWindowText((HWND)lParam, szBufMarriage, 10);
break;
//获取宠物
case 9:
case 10:
case 11:
case 12:
memset(szBufPet, 0, sizeof(szBufPet));
//是否选中狗
if( SendMessage(checkboxDog, BM_GETCHECK, 0, 0) == BST_CHECKED ){
GetWindowText(checkboxDog, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
}
//是否选中猫
if( SendMessage(checkboxCat, BM_GETCHECK, 0, 0) == BST_CHECKED ){
GetWindowText(checkboxCat, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
}
//是否选中鱼
if( SendMessage(checkboxFish, BM_GETCHECK, 0, 0) == BST_CHECKED ){
GetWindowText(checkboxFish, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
}
//是否选中其他
if( SendMessage(checkboxOther, BM_GETCHECK, 0, 0) == BST_CHECKED ){
GetWindowText(checkboxOther, szBufTmp, 10);
wsprintf(szBufPet, TEXT("%s %s"), szBufPet, szBufTmp);
}
break;
//点击提交按钮
case 13:
wsprintf(szBufSubmit, TEXT("你的性别:%s\n婚姻状况:%s\r\n你的宠物:%s"), szBufSex, szBufMarriage, szBufPet);
MessageBox(hWnd, szBufSubmit, TEXT("信息提示"), MB_ICONINFORMATION);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
/**
* 为了节省空间,这里省略了部分语句
* 这里的代码与上节相同,请参考上节代码
**/
}
return 0;
}

1.用户点击控件时会产生 WM_COMMAND 消息,这时 wParam 参数的低16位表示控件ID,高16位表示控件通知码,lParam 表示控件句柄。

2.对于单选按钮,根据控件ID判断当前按钮是否被选中,是的话就获取按钮文本,保存到缓冲区。

3.对于复选框,根据控件ID判断当前复选框是否被选中,是的话就需要遍历所有复选框,检测它们的选中状态,并将所有被选中的复选框的文本保存到缓冲区。

PS:单选按钮可以不发送 BM_GETCHECK 消息,因为一组中只能有一个被选中,就是当前被点击的这个。而复选框就不同了,一组中可以有多个被选中,所以必须发送 BM_GETCHECK 消息检测其他复选框的选中状态。