抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

本文档为《Windows环境下32位汇编语言程序设计》第五章使用资源的部分代码的C语言版,并提供一些必要的补充说明。

5.1 菜单资源

122页:Menu.rc

资源代码(*.rc)文件分既不是汇编语言也不完全是C语言。资源代码可以直接放在C语言工程文件中编译。但考虑到读者复制方便,本文档会重新抄写一遍原书中的资源代码文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <resource.h>
#define ICO_MAIN 0x1000 //图标
#define IDM_MAIN 0x2000 //菜单
#define IDA_MAIN 0x2000 //加速键
#define IDM_OPEN 0x4101
#define IDM_OPTION 0x4102
#define IDM_EXIT 0x4103
#define IDM_SETFONT 0x4201
#define IDM_SETCOLOR 0x4202
#define IDM_INACT 0x4203
#define IDM_GRAY 0x4204
#define IDM_BIG 0x4205
#define IDM_SMALL 0x4206
#define IDM_LIST 0x4207
#define IDM_DETAIL 0x4208
#define IDM_TOOLBAR 0x4209
#define IDM_TOOLBARTEXT 0x4210
#define IDM_INPUTBAR 0x4211
#define IDM_STATUSBAR 0x4212
#define IDM_HELP 0x4301
#define IDM_ABOUT 0x4302

ICO_MAIN ICON "Main.ico" //一定要保证main.ico文件存在

IDM_MAIN menu discardable
BEGIN
popup "文件(&F)"
BEGIN
menuitem "打开文件(&O)...",IDM_SETFONT
menuitem "关闭文件(&C)...",IDM_OPTION
menuitem separator
menuitem "退出(&X)",IDM_EXIT
END
popup "查看(&V)"
BEGIN
menuitem "字体(&F)...\tAlt+F",IDM_SETFONT
menuitem "背景色(&B)...\tCtrl+Alt+B",IDM_SETCOLOR
menuitem separator
menuitem "被禁用的菜单项",IDM_INACT,INACTIVE
menuitem "被灰化的菜单项",IDM_GRAY,GRAYED
menuitem separator
popup "工具栏(&T)"
BEGIN
menuitem "标准按钮(&S)",IDM_TOOLBAR
menuitem "文字标签(&C)",IDM_TOOLBARTEXT
menuitem "命令栏(&I)",IDM_INPUTBAR
END
menuitem "状态栏(&U)",IDM_STATUSBAR
END
popup "帮助(&H)",HELP
BEGIN
menuitem "帮助主题(&H)\tF1",IDM_HELP
menuitem separator
menuitem "关于本程序(&A)...",IDM_ABOUT
END
END

IDA_MAIN accelerators
// 注意书上这块少个空格……
BEGIN
"B", IDM_SETCOLOR,VIRTKEY,CONTROL,ALT
"F", IDM_SETFONT,VIRTKEY,ALT
END
# 第128页 Menu.c代码
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
// include 文件定义
// 稍微解释一下,C语言WIN32项目只需要包含windows.h即可,其他动态库会由编译器自动链接,不需要像汇编语言那样手动的链接动态库。
#include <windows.h>

// EQU等值定义,对应的就是C语言的define,直接抄Menu.rc就可以了。或者你也可以单独写一个头文件,Menu.c和Menu.rc共同include这个头文件
#define ICO_MAIN 0x1000 //图标
#define IDM_MAIN 0x2000 //菜单
#define IDA_MAIN 0x2000 //加速键
#define IDM_OPEN 0x4101
#define IDM_OPTION 0x4102
#define IDM_EXIT 0x4103
#define IDM_SETFONT 0x4201
#define IDM_SETCOLOR 0x4202
#define IDM_INACT 0x4203
#define IDM_GRAY 0x4204
#define IDM_BIG 0x4205
#define IDM_SMALL 0x4206
#define IDM_LIST 0x4207
#define IDM_DETAIL 0x4208
#define IDM_TOOLBAR 0x4209
#define IDM_TOOLBARTEXT 0x4210
#define IDM_INPUTBAR 0x4211
#define IDM_STATUSBAR 0x4212
#define IDM_HELP 0x4301
#define IDM_ABOUT 0x4302

// 全局变量
HINSTANCE hInstance;
HWND hWinMain;
HMENU hMenu;
HMENU hSubMenu;
// 字符串常量。因为汇编指令里不能直接使用字符串,所以必须提前定义字符串,指令里传指针。
// 但是C语言可以啊,所以本部分常量仅作一次声明,接下来的代码部分我会直接把常量展开。
LPCSTR szClassName = "Menu Example";
LPCSTR szCaptionMain = "Menu";
LPCSTR szMenuHelp = "帮助主题(&H)";
LPCSTR szMenuAbout = "关于本程序(&A)...";
LPCSTR szCaption = "菜单选择";
LPCSTR szFormat = "您选择了菜单命令:%08x";
//代码声明


//代码部分
void WINAPI _DisplayMenuItem(unsigned _dwCommandID)
{
char szBuffer[256];
wsprintfA(szBuffer, "您选择了菜单命令:%08x", _dwCommandID);
MessageBoxA(hWinMain, szBuffer, "菜单选择", MB_OK);
}

void WINAPI _Quit(void)
{
DestroyWindow(hWinMain);
PostQuitMessage(NULL);
}

// 在FirstWindow.c上略有改动
// 函数定义
LRESULT CALLBACK _ProcWinMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/* 窗口过程 回调函数*/
switch (uMsg)
{
case WM_CREATE:
{
HMENU hSysMenu;
hSubMenu = GetSubMenu(hMenu, 1);
// 在系统菜单中添加菜单项
hSysMenu = GetSystemMenu(hWnd, 0);
AppendMenuA(hSysMenu, MF_SEPARATOR, 0, NULL);
AppendMenuA(hSysMenu, 0, IDM_HELP, "帮助主题(&H)");
AppendMenuA(hSysMenu, 0, IDM_ABOUT, "关于本程序(&A)...");
}
break;
case WM_COMMAND:
{
// 处理菜单及加速键消息
_DisplayMenuItem(wParam);
wParam = wParam & 0xFFFF;
if (wParam == IDM_EXIT)
{
_Quit();
}
else if (wParam >= IDM_TOOLBAR && wParam <= IDM_STATUSBAR)
{
if (GetMenuState(hMenu, wParam, MF_BYCOMMAND) == MF_CHECKED)
{
CheckMenuItem(hMenu, wParam, MF_UNCHECKED);
}
else
{
CheckMenuItem(hMenu, wParam, MF_CHECKED);
}
}
else if (wParam >= IDM_BIG && wParam <= IDM_DETAIL)
{
CheckMenuRadioItem(hMenu, IDM_BIG, IDM_DETAIL, wParam, MF_BYCOMMAND);
}
}
break;
case WM_SYSCOMMAND:
{
unsigned mess = wParam & 0xFFFF;
if (mess == IDM_HELP || mess == IDM_ABOUT)
{
_DisplayMenuItem(wParam, 0);
}
else
{
return DefWindowProcA(hWnd, uMsg, wParam, lParam);
}
break;
}
case WM_RBUTTONDOWN:
{
POINT stPos;
GetCursorPos(&stPos);
TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, stPos.x, stPos.y, NULL, hWnd, NULL);
}
break;
case WM_CLOSE:
_Quit();
break;
default:
return DefWindowProcA(hWnd, uMsg, wParam, lParam);
}
return 0;
}
int WINAPI _WinMain()
{
WNDCLASSEXA stWndClass;
MSG stMsg;
HACCEL hAccelerator;
hInstance = GetModuleHandleA(NULL);

// 注册菜单和快捷键
hMenu = LoadMenuA(hInstance, (LPCSTR)IDM_MAIN);
hAccelerator = LoadAcceleratorsA(hInstance, (LPCSTR)IDA_MAIN);

// 注册窗口类,这里获取了hIcon
RtlZeroMemory(&stWndClass, sizeof(stWndClass));
stWndClass.hIcon = LoadIconA(hInstance, (LPCSTR)ICO_MAIN);
stWndClass.hIconSm = stWndClass.hIcon;
stWndClass.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
stWndClass.hInstance = hInstance;
stWndClass.cbSize = sizeof(WNDCLASSEX);
stWndClass.style = CS_HREDRAW | CS_VREDRAW;
stWndClass.lpfnWndProc = _ProcWinMain;
stWndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
stWndClass.lpszClassName = szClassName;
RegisterClassExA(&stWndClass);

// 建立并显示窗口,这里传了个hMenu
hWinMain = CreateWindowExA(WS_EX_CLIENTEDGE, szClassName, szCaptionMain, WS_OVERLAPPEDWINDOW, 100, 100, 400, 300, NULL, hMenu, hInstance, NULL);
ShowWindow(hWinMain, SW_SHOWNORMAL);
UpdateWindow(hWinMain);

// 消息循环
while (1)
{
if (GetMessageA(&stMsg, NULL, 0, 0) == 0)
break;
if (TranslateAcceleratorA(hWinMain, hAccelerator, &stMsg) == 0)
{
TranslateMessage(&stMsg);
DispatchMessageA(&stMsg);
}
}
return 0;
}

// 主函数,子系统选择windows
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
return _WinMain();
}
## 稍微补充几点书上没有提到的问题(因为汇编语言不会遇到而C语言会遇到):

首先是新的几个需要区分A和W版本的函数:

1
2
3
4
5
AppendMenuA();
LoadMenuA();
LoadAcceleratorsA();
LoadIconA();
TranslateAcceleratorA();

之后是资源编号的问题。

C语言中读取资源的函数,以LoadMenuA为例,它的第二个参数只支持传入字符串地址即LPCSTR。但是根据书上所说,这个参数小于0x10000时API会认为是数字,大于0x10000时会认为是字符串地址。因此我们调用这列函数的时候,只需要在编号之前做强制类型转换:

1
LoadMenuA(hInstance, (LPCSTR)IDM_MAIN);
把这个数字传给API就行了,底层API仍然会按照参数小于0x10000是数字,大于0x10000时是字符串地址来解读。当然你也可以直接用字符串进行编号。

此外,如何在C语言项目中添加并编辑资源代码(*.rc),以Visual Studio为例:

1、右键资源文件,添加新建项。 alt text 2、创建以rc结尾的文件。 alt text 3、右键选择打开方式。 alt text 4、选择C++源码编辑器 alt text 5、打开编辑rc文件,编辑完之后保存并关闭。 alt text 完成之后,你再在visual studio上双击打开这个资源文件,你会发现vs自动解析了这个资源代码。 alt text 在资源视图里可以直接右键添加资源。 alt text 事实上,读者完全可以只写一个基本的rc框架,然后用资源视图进行添加资源。

补充说明一:Windows程序的换行

无论你是用MessageBoxA还是其他显示字符串的winapi,请用""代表换行。单纯的""无法起到换行的作用。这也能解释为什么一些OnlineJudge上的测试样例需要连着两次getchar()才能过滤掉回车,就是测试样例用的是""做回车。Linux等其他操作系统一般用""代表换行。

补充说明二:数据类型 BYTE WORD DWORD

这三种数据类型可以代表Winapi中几乎所有的变量类型(除了结构体类型)。BYTE为字节,大小为1字节,对应C语言的char变量类型的大小。WORD为字,大小为2字节,对应C语言的unsigned short变量类型的大小。DWORD为双字,大小为4字节,对应C语言的unsigned int/unsigned long变量类型的大小。

Winapi中的一些变量类型比如一大堆句柄(HMENU、HWND、HINSTANCE等)归根结底都是这三种基本类型的重命名。必要时可以用强制类型转换。

补充说明三:两个重要的参数wParam和lParam

在winapi中,wParam和lParam是传递消息的重要变量,类型均为DWORD。在不同的消息中,wParam和lParam数值的含义各不相同。普遍情况下,wParam的低2字节和高2字节分别具有不同的含义。

1
2
wParam & 0xFFFF;    // 取低2字节的数据
wParam >> 16; // 取高2字节的数据
这里简要说明一下最常见的WM_COMMAD消息中,wParam和lParam的代表意义。下图为MSDN简单介绍: alt text wParam高2字节为通知码,低2字节为命令ID,lParam为发送命令消息的子窗体句柄。

对于菜单和加速键来说:

lParam始终为0,wParam高2字节菜单为0、加速键为1,wParam低2字节为资源代码文件中对菜单定义的ID值。

对于按钮来说:

lParam为按钮句柄,wParam低2字节为资源代码中对按钮定义的ID值。若是通过CreateWindowEx创建的按钮,则ID为创建按钮传给CreateWindowEx时HMENU hMenu参数的值。

wParam高2字节通知码为以下宏定义:

1
2
3
4
5
6
7
8
9
/*
* User Button Notification Codes
*/
#define BN_CLICKED 0
#define BN_PAINT 1
#define BN_HILITE 2
#define BN_UNHILITE 3
#define BN_DISABLE 4
#define BN_DOUBLECLICKED 5
## 对于编辑框来说: lParam为编辑框句柄,wParam低2字节为资源代码中对编辑框定义的ID值。若是通过CreateWindowEx创建的编辑框,则ID为创建编辑框传给CreateWindowEx时HMENU hMenu参数的值。

wParam高2字节通知码为以下宏定义:

1
2
3
4
5
6
7
8
9
10
11
/*
* Edit Control Notification Codes
*/
#define EN_SETFOCUS 0x0100
#define EN_KILLFOCUS 0x0200
#define EN_CHANGE 0x0300
#define EN_UPDATE 0x0400
#define EN_ERRSPACE 0x0500
#define EN_MAXTEXT 0x0501
#define EN_HSCROLL 0x0601
#define EN_VSCROLL 0x0602
其余控件的详细信息请查询MSDN文档。

这里提供几个相关资料:

1
2
3
4
5
6
7
8
9
MSDN文档——Windows控件:
https://learn.microsoft.com/zh-cn/windows/win32/controls/window-controls
在这里你能查找到所有winapi的控件相关的函数和宏定义。主要看控件的消息和通知码。

创建按钮实例:
https://blog.csdn.net/weixin_44499065/article/details/134073886

创建编辑框实例:
https://blog.csdn.net/ayqy42602/article/details/97931971
# 补充说明四:系统菜单与非系统菜单

Windows程序的菜单分两类:系统菜单和非系统菜单。对应的加速键(快捷键)也分系统与非系统。

简单来说,非系统菜单就是程序员在资源文件中定义的菜单,点击非系统菜单的menuitem,发送的消息是WM_COMMAND;系统菜单是Windows维护的菜单,包含最大化最小化关闭等基本任务,发送的消息是WM_SYSCOMMAND。非系统菜单的句柄需要使用LoadMenu获取,而系统菜单的句柄需要使用GetSysMenu函数获取。

对于Menu.c缩写的程序,非系统菜单是这些。标题下面的菜单项及其子项,右键弹出的菜单项及其子项都是非系统菜单: alt text 单击图标或者按Alt+Space弹出的菜单是系统菜单: alt text 我们稍微的对Menu.c的代码改动一下,方便读者更好地区分哪部分是系统菜单哪部分是非系统菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//代码部分,增加一个int i参数
void WINAPI _DisplayMenuItem(unsigned _dwCommandID, int i)
{
char szBuffer[256];
wsprintfA(szBuffer, "您选择了菜单命令:%08x\r\n%s", _dwCommandID, (i ? "非系统菜单" : "系统菜单"));
MessageBoxA(hWinMain, szBuffer, "菜单选择", MB_OK);
}

//......

case WM_COMMAND:
_DisplayMenuItem(wParam,1);

case WM_SYSCOMMAND:
_DisplayMenuItem(wParam,0);
非必要的情况下,请最好不要改动系统菜单。(本书的例子其实就是非必要,但可能是为了介绍系统菜单才这么用的吧)如若需要改动系统菜单,一定要执行DefWindowProcA(),否则,窗口将不能收到最小化、关闭、移动等基本消息,出现假卡死现象。
1
2
3
4
5
6
7
8
9
10
11
12
13
case WM_SYSCOMMAND:
{
unsigned mess = wParam & 0xFFFF;
if (mess == IDM_HELP || mess == IDM_ABOUT)
{
_DisplayMenuItem(wParam, 0);
}
else // 不是自己新添加的消息一定要交给默认处理函数!!
{
return DefWindowProcA(hWnd, uMsg, wParam, lParam);
}
break;
}
WM_COMMAND可以不执行DefWindowProcA(),因为都是程序员自定义的菜单所传的消息。

P132页 1、加载菜单:

用CreateWindowEx指定菜单

1
2
// 建立并显示窗口,这里传了个hMenu
hWinMain = CreateWindowExA(WS_EX_CLIENTEDGE, szClassName, szCaptionMain, WS_OVERLAPPEDWINDOW, 100, 100, 400, 300, NULL, hMenu, hInstance, NULL);
在参数中指出的hMenu必须要有LoadMenu从资源二进制文件(.res,由.rc编译而来)获取到菜单句柄。
1
hMenu = LoadMenuA(hInstance, (LPCSTR)IDM_MAIN);
如果是字符串定义的菜单,则直接传字符串。

加速键(快捷键)与菜单一样,需要获取句柄。

1
hAccelerator = LoadAcceleratorsA(hInstance, (LPCSTR)IDA_MAIN);
要实现加速键,好需要在消息循环中处理用户按键的消息,因此要用TranslateAccelerator,如果消息循环中检测到用户按下了某一快捷键,则会执行目标窗口的WM_COMMAD消息并返回TRUE。而其他不是加速键的消息就要用之前的方式处理这些消息。
1
2
3
4
5
6
7
8
9
10
11
// 在win32汇编中,eax是32位通用寄存器,其基本功能之一就是存储函数的返回值;invoke指令是调用函数,因此书上的代码意思就是TranslateAccelerator函数返回值位0时执行TranslateMessage和DispatchMessage
while (1)
{
if (GetMessageA(&stMsg, NULL, 0, 0) == 0)
break;
if (TranslateAcceleratorA(hWinMain, hAccelerator, &stMsg) == 0)
{
TranslateMessage(&stMsg);
DispatchMessageA(&stMsg);
}
}
# 134页 WM_COMMAND分支的一般结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(uMsg)
{
case WM_COMMAND:
{
unsigned wID = wParam & 0xFFFF;
if(wID == 命令ID1)
{

}
else if(...)
{

}
}
break;
}
书上说的ax扩展eax等关于寄存器的操作可以忽略掉,也不要把eax当作wParam的替身。eax指的是什么,完全由汇编程序员的意愿决定。

135页 菜单项的修改

程序运行中可以动态修改菜单项,由以下几个API完成:

1
2
3
4
5
BOOL WINAPI AppendMenuA(HMENU hMenu, UINT uFlags,UINT_PTR uIDNewItem,LPCSTR lpNewItem); //添加菜单项
BOOL WINAPI InsertMenuA(HMENU hMenu,UINT uPosition,UINT uFlags,UINT_PTR uIDNewItem,LPCSTR lpNewItem); //插入菜单项
BOOL WINAPI ModifyMenuA(HMENU hMenu,UINT uPosition,uFlags,UINT_PTR uIDNewItem,LPCSTR lpNewItem); //修改菜单项
BOOL WINAPI RemoveMenu(HMENU hMenu,UINT uPosition,UINT uFlags); //删除菜单项
BOOL WINAPI DeleteMenu(HMENU hMenu,UINT uPosition,UINT uFlags); //删除菜单项
这些函数的详细介绍看书上的就可以了。

136页 使用系统菜单

(本人并不推荐这种做法,一是容易把程序写崩,二是很少有人知道怎样打开程序的系统菜单。)

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
// 窗口过程 消息循环中
case WM_CREATE:
{
HMENU hSysMenu;
hSubMenu = GetSubMenu(hMenu, 1);
// 在系统菜单中添加菜单项
hSysMenu = GetSystemMenu(hWnd, 0);
AppendMenuA(hSysMenu, MF_SEPARATOR, 0, NULL);
AppendMenuA(hSysMenu, 0, IDM_HELP, "帮助主题(&H)");
AppendMenuA(hSysMenu, 0, IDM_ABOUT, "关于本程序(&A)...");
}
// 另外说明一下,WM_CREATE是窗口或组件创建时发出的消息,类似于类的构造函数的作用。

// 窗口过程 消息循环中
case WM_SYSCOMMAND:
{
unsigned wID = wParam & 0xFFFF;
if (wID == ??)
{
// TODO...
}
else if(wID == ??)
{
// TODO...
}
else // 一定要注意这个
{
return DefWindowProcA(hWnd, uMsg, wParam, lParam);
}
break;
}

137页 右键弹出菜单

实现函数:

1
BOOL WINAPI TrackPopupMenu(HMENU hMenu,UINT uFlags,int x,int y,int nReserved,HWND hWnd,const RECT *prcRect);
获取鼠标位置:
1
2
POINT stPos;
GetCursorPos(&stPos);
Menu.c中的相关代码:
1
2
3
POINT stPos;
GetCursorPos(&stPos);
TrackPopupMenu(hSubMenu, TPM_LEFTALIGN, stPos.x, stPos.y, NULL, hWnd, NULL);
主菜单不能作为popup菜单,只能使用二级菜单(子菜单),因此需要获取子菜单句柄:
1
HMENU WINAPI GetSubMenu(HMENU hMenu,int nPos);
菜单状态的检测与设置:
1
UINT WINAPI GetMenuState(HMENU hMenu,UINT uId,UINT uFlags);
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned st = GetMenuState(...);
if(st & MF_CHECKED)
{
// 表示IDM_XXX菜单项现在是选中状态
}
else if(st & MF_DISABLED)
{

}
else if(st & MF_GRAYED)
{

}
设置菜单项的状态:
1
2
3
BOOL WINAPI EnableMenuItem(HMENU hMenu,UINT uIDEnableItem,UINT uEnable);
DWORD WINAPI CheckMenuItem(HMENU hMenu,UINT uIDCheckItem,UINT uCheck);
BOOL WINAPI CheckMenuRadioItem(HMENU hmenu,UINT first,UINT last,UINT check,UINT flags);
Menu.c中的应用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
else if (wParam >= IDM_TOOLBAR && wParam <= IDM_STATUSBAR)
{
if (GetMenuState(hMenu, wParam, MF_BYCOMMAND) == MF_CHECKED)
{
CheckMenuItem(hMenu, wParam, MF_UNCHECKED);
}
else
{
CheckMenuItem(hMenu, wParam, MF_CHECKED);
}
}
else if (wParam >= IDM_BIG && wParam <= IDM_DETAIL)
{
CheckMenuRadioItem(hMenu, IDM_BIG, IDM_DETAIL, wParam, MF_BYCOMMAND);
}

5.2 图标和光标

图标的话就是在资源文件里定义图标资源,在CreateWindowEx之前调用LoadIcon。

定义图标资源:

1
2
3
4
5
6
7
// resource.rc
#include <resource.h>
#define ICO_BIG 0x1000
#define ICO_SMALL 0x1001

ICO_SMALL ICON "Small.ico"
ICO_BIG ICON "Big.ico"
1
hIcon = LoadIconA(hInstance,lpIconName);
如果不定义光标的话,将使用Windows默认光标。

5.4 对话框

(本人推荐主窗口用CreateWindow,做子任务的子窗口用对话框)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Dialog.rc
#include <resource.h>

#define ICO_MAIN 0x1000 //图标
#define DLG_MAIN 1

ICO_MAIN ICON "Main.ico"

DLG_MAIN DIALOG 50,50,113,64
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "对话框模板"
FONT 9, "宋体"
{
ICON ICO_MAIN, -1, 10, 11, 18, 21
CTEXT "简单的对话框例子\r\n用Win32ASM编写", -1, 36, 14, 70, 19
DEFPUSHBUTTON "退出(&X)", IDOK, 58, 46, 50, 14
CONTROL "", -1, "Static", SS_ETCHEDHORZ | WS_CHILD | WS_VISIBLE, 6, 39, 103, 1
}
## 使用对话框代码Dialog.c:
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
#include <windows.h>

#define ICO_MAIN 0x1000
#define DLG_MAIN 1
HINSTANCE hInstance;
LRESULT CALLBACK _ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HICON hIcon;
switch (uMsg)
{
case WM_CLOSE:
EndDialog(hWnd, NULL);
break;
case WM_INITDIALOG:
hIcon = LoadIconA(hInstance, (LPCSTR)ICO_MAIN);
SendMessageA(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
break;
case WM_COMMAND:
wParam &= 0xFFFF;
if (wParam == IDOK)
{
EndDialog(hWnd, NULL);
}
break;
default:
return FALSE;
}
return TRUE;
}

// 主函数,子系统选择windows
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInstance = GetModuleHandleA(NULL);
DialogBoxParamA(hInstance, (LPCSTR)DLG_MAIN, NULL, (DLGPROC)_ProcDlgMain, NULL);
return 0;
}
创建模态对话框
1
INT_PTR WINAPI DialogBoxParamA(HINSTANCE hInstance,LPCSTR lpTemplateName,HWND hWndParent,DLGPROC lpDialogFunc,LPARAM dwInitParam);
其中lpTemplateName为对话框资源ID,可以是低于0x10000的数字也可以是字符串地址。lpDialogFunc为过程函数地址,用法为
1
(DLGPROC)函数名称
要结束模态对话框,必须在对话框过程的WM_CLOSE使用EndDialog
1
BOOL WINAPI EndDialog(HWND hDlg,INT_PTR nResult);
创建非模态对话框
1
HWND WINAPI CreateDialogParamA(HINSTANCE hInstance,LPCSTR lpTemplateName,HWND hWndParent,DLGPROC lpDialogFunc,LPARAM dwInitParam);
关闭非模态对话框要使用DestroyWindow。

对话框过程,与普通窗口的过程一致:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
LRESULT CALLBACK _ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HICON hIcon;
switch (uMsg)
{
case WM_CLOSE:
// 模态对话框用EndDialog
// 非模态对话框用DestoryWindow
break;
case WM_INITDIALOG:
// 窗口初始化代码
break;
case WM_COMMAND:
// 子窗口控件发送的消息
// wParam的低2字节位子窗口控件ID
break;
default:
return FALSE;
}
return TRUE;
}

注意对话框过程和普通窗口过程在使用时有以下区别(见书上描述)

在对话框中使用子窗口控件

Control.rc

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

#define ICO_MAIN 0x1000
#define DLG_MAIN 1
#define IDB_1 1
#define IDB_2 2
#define IDC_ONTOP 101
#define IDC_SHOWBMP 102
#define IDC_ALOW 103
#define IDC_MODALFRAME 104
#define IDC_THICKFRAME 105
#define IDC_TITLETEXT 106
#define IDC_CUSTOMTEXT 107
#define IDC_BMP 108
#define IDC_SCROLL 109
#define IDC_VALUE 110

ICO_MAIN ICON "Main.ico"
IDB_1 BITMAP "Picture1.bmp"
IDB_2 BITMAP "Picture2.bmp"

DLG_MAIN DIALOG 193, 180, 310, 134
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
CAPTION "对话框子窗口控件示例"
FONT 9, "宋体"
{
GROUPBOX "选项", -1, 55, 5, 120, 100
AUTOCHECKBOX "总在最前面", IDC_ONTOP, 65, 20, 100, 12
AUTOCHECKBOX "显示图片", IDC_SHOWBMP, 65, 35, 100, 12
AUTOCHECKBOX "允许更换图片", IDC_ALOW, 65, 50, 100, 12
CONTROL "", -1, "Static", SS_ETCHEDHORZ | WS_CHILD | WS_VISIBLE, 60, 65, 110, 1
AUTORADIOBUTTON "模态边框(&Modal Frame)", IDC_MODALFRAME, 65, 70, 100, 12, WS_TABSTOP
AUTORADIOBUTTON "可变边框(&Thick Frame)", IDC_THICKFRAME, 65, 85, 100, 12, WS_TABSTOP
GROUPBOX "标题栏文字", -1, 180, 5, 125, 100, BS_GROUPBOX
COMBOBOX IDC_TITLETEXT, 190, 20, 105, 70, CBS_DROPDOWNLIST | WS_TABSTOP
LTEXT "自定义文字:", -1, 190, 40, 105, 10
EDITTEXT IDC_CUSTOMTEXT, 190, 55, 105, 12
LTEXT "请在此选自显示在标题栏上面的问题,或者选中“自定义”后自行输入", -1, 191, 73, 105, 26, WS_BORDER
CONTROL "", -1, "Static", SS_ETCHEDHORZ | WS_CHILD | WS_VISIBLE, 5, 110, 300, 1
DEFPUSHBUTTON "更换图片(&C)", IDOK, 200, 115, 50, 14
PUSHBUTTON "退出(&X)", IDCANCEL, 255, 115, 50, 14
CONTROL IDB_1, IDC_BMP, "Static", SS_BITMAP | WS_CHILD | WS_VISIBLE,5, 5, 40, 95
SCROLLBAR IDC_SCROLL, 6, 119, 125, 10
LTEXT "0", IDC_VALUE, 138, 120, 50, 8
}

注意在编译时,你的项目工程里要有Main.ico、Picture1.bmp和Picture2.bmp文件。 ### Control.c

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
#include <windows.h>
#define ICO_MAIN 0x1000
#define DLG_MAIN 1
#define IDB_1 1
#define IDB_2 2
#define IDC_ONTOP 101
#define IDC_SHOWBMP 102
#define IDC_ALOW 103
#define IDC_MODALFRAME 104
#define IDC_THICKFRAME 105
#define IDC_TITLETEXT 106
#define IDC_CUSTOMTEXT 107
#define IDC_BMP 108
#define IDC_SCROLL 109
#define IDC_VALUE 110

HINSTANCE hInstance;
HBITMAP hBmp1, hBmp2;
DWORD dwPos;
LPCSTR szText1 = "Hello, World!";
LPCSTR szText2 = "嘿,你看到标题栏变了吗?";
LPCSTR szText3 = "自定义";

LRESULT CALLBACK _ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
char szBuffer[128];
HICON hIcon;
switch (uMsg)
{
case WM_CLOSE:
EndDialog(hWnd, NULL);
DeleteObject(hBmp1);
DeleteObject(hBmp2);
break;
case WM_INITDIALOG:
// 设置标题栏图标
hIcon = LoadIconA(hInstance, (LPCSTR)ICO_MAIN);
SendMessageA(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);

// 初始化组合框
SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_ADDSTRING, 0, (LPARAM)"Hello, World!");
SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_ADDSTRING, 0, (LPARAM)"嘿,你看到标题栏变了吗?");
SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_ADDSTRING, 0, (LPARAM)"自定义");
SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_SETCURSEL, 0, 0);
EnableWindow(GetDlgItem(hWnd, IDC_CUSTOMTEXT), FALSE);
hBmp1 = LoadBitmapA(hInstance, (LPCSTR)IDB_1);
hBmp2 = LoadBitmapA(hInstance, (LPCSTR)IDB_2);

// 初始化单选钮和复选框
CheckDlgButton(hWnd, IDC_SHOWBMP, BST_CHECKED);
CheckDlgButton(hWnd, IDC_ALOW, BST_CHECKED);
CheckDlgButton(hWnd, IDC_THICKFRAME, BST_CHECKED);

// 初始化滚动条
SendDlgItemMessageA(hWnd, IDC_SCROLL, SBM_SETRANGE, 0, 100);
break;
case WM_COMMAND:
switch (wParam & 0xFFFF)
{
case IDCANCEL:
EndDialog(hWnd, NULL);
DeleteObject(hBmp1);
DeleteObject(hBmp2);
break;
case IDOK:
// 更换图片
{
HBITMAP tmp;
tmp = hBmp1;
hBmp1 = hBmp2;
hBmp2 = tmp;
SendDlgItemMessageA(hWnd, IDC_BMP, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp1);
}
break;
case IDC_ONTOP:
// 设置是否总在最前面
if (IsDlgButtonChecked(hWnd, IDC_ONTOP) == BST_CHECKED)
{
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
else
{
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
break;
case IDC_SHOWBMP:
// 演示隐藏和显示图片控件
{
HWND hDlg = GetDlgItem(hWnd, IDC_BMP);
if (IsWindowVisible(hDlg))
{
ShowWindow(hDlg, SW_HIDE);
}
else
{
ShowWindow(hDlg, SW_SHOW);
}
break;
}
case IDC_ALOW:
// 允许或灰化“更换图片”按钮
EnableWindow(GetDlgItem(hWnd, IDOK), IsDlgButtonChecked(hWnd, IDC_ALOW) == BST_CHECKED);
break;
case IDC_MODALFRAME:
SetWindowLongA(hWnd, GWL_STYLE, GetWindowLongA(hWnd, GWL_STYLE) & (~WS_THICKFRAME));
break;
case IDC_THICKFRAME:
SetWindowLongA(hWnd, GWL_STYLE, GetWindowLongA(hWnd, GWL_STYLE) | WS_THICKFRAME);
break;
case IDC_TITLETEXT:
// 演示处理下拉式组合框
if ((wParam >> 16) == CBN_SELENDOK)
{
DWORD retID = SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_GETCURSEL, 0, 0);
if (retID == 2)
{
EnableWindow(GetDlgItem(hWnd, IDC_CUSTOMTEXT), TRUE);
}
else
{
SendDlgItemMessageA(hWnd, IDC_TITLETEXT, CB_GETLBTEXT, retID, (LPARAM)szBuffer);
SetWindowTextA(hWnd, szBuffer);
EnableWindow(GetDlgItem(hWnd, IDC_CUSTOMTEXT), FALSE);
}
}
break;
case IDC_CUSTOMTEXT:
// 在文本框中输入文字
GetDlgItemTextA(hWnd, IDC_CUSTOMTEXT, szBuffer, sizeof(szBuffer));
SetWindowTextA(hWnd, szBuffer);
break;
}
break;
// WM_COMMAD消息的处理已经结束了
case WM_HSCROLL:
switch (wParam & 0xFFFF)
{
case SB_LINELEFT:
dwPos--;
break;
case SB_LINERIGHT:
dwPos++;
break;
case SB_PAGELEFT:
dwPos -= 10;
break;
case SB_PAGERIGHT:
dwPos += 10;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
dwPos = (wParam >> 16);
break;
default:
return TRUE;
}
if (dwPos < 0)dwPos = 0;
if (dwPos > 100)dwPos = 100;
SetDlgItemInt(hWnd, IDC_VALUE, dwPos, FALSE);
SendDlgItemMessageA(hWnd, IDC_SCROLL, SBM_SETPOS, dwPos, TRUE);
break;
default:
return FALSE;
}
return TRUE;
}

// 主函数,子系统选择windows
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInstance = GetModuleHandleA(NULL);
DialogBoxParamA(hInstance, (LPCSTR)DLG_MAIN, NULL, (DLGPROC)_ProcDlgMain, NULL);
return 0;
}
### 162页,子窗口控件的通用方法 通用函数:
1
2
3
HWND WINAPI GetDlgItem(HWND hDlg,int nIDDlgItem);   //通过控件ID获取其句柄
int WINAPI GetDlgCtrlID(HWND hWnd); //通过句柄获取ID
LRESULT WINAPI SendDlgItemMessageA(HWND hDlg,int nIDDlgItem,UINT Msg,WPARAM wParam,LPARAM lParam); //像某个对话框的控件发送消息
使用单选钮和复选框:
1
2
3
UINT WINAPI IsDlgButtonChecked(HWND hDlg,int nIDButton);
BOOL WINAPI CheckDlgButton(HWND hDlg,int nIDButton,UINT uCheck);
BOOL WINAPI CheckRadioButton(HWND hDlg,int nIDFirstButton,int nIDLastButton,int nIDCheckButton);
静态控件(文本标签、图片)。

文本编辑框:

1
2
3
4
5
UINT WINAPI GetDlgItemTextA(HWND hDlg,int nIDDlgItem,LPCSTR lpString,int cchMax);
BOOL WINAPI SetDlgItemTextA(HWND hDlg,int nIDDlgItem,LPCSTR lpString);
BOOL WINAPI SetDlgItemInt(HWND hDlg,int nIDDlgItem,UINT uValue,BOOL bSigned);
UINT WINAPI GetDlgItemInt(HWND hDlg,int nIDDlgItem,BOOL *lpTranslated,BOOL bSigned);
LRESULT WINAPI SendDlgItemMessageA(HWND hDlg,int nIDDlgItem, UINT Msg,WPARAM wParam,LPARAM 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
// 消息处理分支,uMsg为WM_HSCROLL时
if(lParam == 滚动条的句柄1)
{
switch(wParam & 0xFFFF)
{
case SB_LINELEFT:
位置变量--;
break;
case SB_LINERIGHT:
位置变量++;
break;
case SB_PAGELEFT:
位置变量-=页长;
break;
case SB_PAGERIGHT:
位置变量+=页长;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
位置变量=(wParam>>16);
break;
}
}
else if(lParam == 滚动条的句柄2)
{
// ...
}
位置矫正:
1
2
if (dwPos < 0)dwPos = 0;
if (dwPos > 100)dwPos = 100;
使用列表框: #### ListBox.rc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <resource.h>
#define ICO_MAIN 0x1000
#define DLG_MAIN 1
#define IDC_LISTBOX1 101
#define IDC_LISTBOX2 102
#define IDC_SEL1 103
#define IDC_RESET 104
ICO_MAIN ICON "Main.ico"

DLG_MAIN DIALOG 163, 160, 190, 108
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "列表框控件实例"
FONT 9, "宋体"
{
LISTBOX IDC_LISTBOX1, 6, 5, 55, 86, LBS_STANDARD
LISTBOX IDC_LISTBOX2, 68, 5, 115, 86, LBS_STANDARD | LBS_MULTIPLESEL
LTEXT "", IDC_SEL1, 6, 93, 55, 8
PUSHBUTTON "复位(&R)", IDC_RESET, 89, 90, 45, 14
DEFPUSHBUTTON "查看(&S)", IDOK, 139, 90, 45, 14, WS_DISABLED
}
#### ListBox.c
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
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <string.h>
#define ICO_MAIN 0x1000
#define DLG_MAIN 1
#define IDC_LISTBOX1 101
#define IDC_LISTBOX2 102
#define IDC_SEL1 103
#define IDC_RESET 104

HINSTANCE hInstance;
LPCSTR szText1 = "项目1";
LPCSTR szText2 = "项目2";
LPCSTR szText3 = "项目3";
LPCSTR szPath = "*.*";
LPCSTR szMessage = "选择结果:%s";
LPCSTR szTitle = "您的选择";
LPCSTR szSelect = "您选择了以下的项目:";
LPCSTR szReturn = "\r\n";

LRESULT CALLBACK _ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DWORD szBuffer[128];
char szBuffer1[128];
char szTextBuff[2048];
int dwCount;
switch (uMsg)
{
case WM_CLOSE:
EndDialog(hWnd, NULL);
break;
case WM_INITDIALOG:
SendMessageA(hWnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIconA(hInstance, (LPCSTR)ICO_MAIN));
//初始化列表框
SendDlgItemMessageA(hWnd, IDC_LISTBOX1, LB_ADDSTRING, 0, (LPARAM)"项目1");
SendDlgItemMessageA(hWnd, IDC_LISTBOX1, LB_ADDSTRING, 0, (LPARAM)"项目2");
SendDlgItemMessageA(hWnd, IDC_LISTBOX1, LB_ADDSTRING, 0, (LPARAM)"项目3");
SendDlgItemMessageA(hWnd, IDC_LISTBOX2, LB_DIR, DDL_ARCHIVE | DDL_DRIVES | DDL_DIRECTORY, (LPARAM)"*.*");
break;
case WM_COMMAND:
switch (wParam & 0xFFFF)
{
case IDOK:
{
dwCount = SendDlgItemMessageA(hWnd, IDC_LISTBOX2, LB_GETSELCOUNT, 0, 0);
SendDlgItemMessageA(hWnd, IDC_LISTBOX2, LB_GETSELITEMS, 128 / 4, (LPARAM)szBuffer);
strcpy(szTextBuff, szSelect);
for (int i = 0; i < dwCount; i++)
{
SendDlgItemMessageA(hWnd, IDC_LISTBOX2, LB_GETTEXT, szBuffer[i], (LPARAM)szBuffer1);
strcat(szTextBuff, "\r\n");
strcat(szTextBuff, szBuffer1);
}
MessageBoxA(hWnd, szTextBuff, szTitle, MB_OK);
}
break;
case IDC_RESET:
SendDlgItemMessageA(hWnd, IDC_LISTBOX2, LB_SETSEL, FALSE, -1);
break;
case IDC_LISTBOX1:
switch (wParam >> 16)
{
case LBN_SELCHANGE:
// 将鼠标点击结果显示在文本框中
{
UINT id = SendMessageA((HWND)lParam, LB_GETCURSEL, 0, 0);
SendMessageA((HWND)lParam, LB_GETTEXT, id, (LPARAM)szTextBuff);
SetDlgItemTextA(hWnd, IDC_SEL1, szTextBuff);
}
break;
case LBN_DBLCLK:
{
UINT id = SendMessageA((HWND)lParam, LB_GETCURSEL, 0, 0);
SendMessageA((HWND)lParam, LB_GETTEXT, id, (LPARAM)szTextBuff);
wsprintfA(szBuffer1, szMessage, szTextBuff);
MessageBoxA(hWnd, szBuffer1, szTitle, MB_OK);
}
break;
}
break;
case IDC_LISTBOX2:
if ((wParam >> 16) == LBN_SELCHANGE)
{
UINT id = SendMessageA((HWND)lParam, LB_GETSELCOUNT, 0, 0);
EnableWindow(GetDlgItem(hWnd, IDOK), id);
}
break;
}
break;
default:
return FALSE;
}
return TRUE;
}

// 主函数,子系统选择windows
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInstance = GetModuleHandleA(NULL);
DialogBoxParamA(hInstance, (LPCSTR)DLG_MAIN, NULL, (DLGPROC)_ProcDlgMain, NULL);
return 0;
}