在笔记的第一章我们演示过.rc
资源文件和rc.exe
资源编译器的用法,不过在实际开发中,我们没必要自己编写.rc
资源脚本,这篇笔记我们使用Visual Studio集成开发环境介绍常用资源类型的添加和使用。
在Visual Studio解决方案资源管理器中,我们可以在资源文件上点击右键,选择添加->资源
,此时就会默认创建一个.rc
文件。.rc
是一个文本文件,它其中包含了一些脚本,供资源编译器编译,这个脚本的内容不需要我们手动更改,它由Visual Studio维护。除了.rc
文件,同时创建的还有一个Resource.h
头文件,这个头文件的作用很容易理解,我们会给资源定义一些ID供源代码引用,这些资源ID实际上就定义在这个头文件中,Resource.h
也不需要我们手动修改。
有了.rc
资源文件,我们就可以添加各种类型的资源了。
在Win32程序中,菜单也是一种资源,这可能有些奇怪,但Win32API确实就是这么设计的。菜单实际上包括两种:窗体菜单和上下文菜单。前者通常固定在客户区上方,后者通常随着鼠标按键在某一位置弹出。
在Visual Studio中,我们可以添加一个Menu资源,点击该资源可以打开一个可视化的菜单编辑器,我们可以在其中添加菜单。
对于菜单项,默认指定的ID属性可读性不好,这里建议手动修改其ID属性,以方便后续引用。
在C源码中,加载菜单也十分简单,我们需要用到LoadMenu
函数,下面是一个例子。
HMENU hMenu = LoadMenu(hInstance, IDR_MENU1);
代码中,hInstance
是应用程序实例句柄,IDR_MENU1
是我们菜单的资源ID,它可以在资源视图中找到。注意源码中为了使用资源ID,我们要引入自动创建的Resource.h
头文件,否则编译是无法通过的。
LoadMenu
的返回值是菜单资源句柄,对于该句柄我们可以有3种使用方式,将其设置到窗体类、使用CreateWindow
时指定,或者在窗体初始化消息WM_CREATE
触发时使用SetMenu
函数设置,我们任选一种都可以。这里我们直接在CreateWindow
参数上指定。
// 创建窗体实例
HWND hwnd; // 声明窗体的句柄
hwnd = CreateWindow(
appName, // 窗体类名称
TEXT("Window程序示例"), // 窗体标题
WS_OVERLAPPEDWINDOW, // 窗体风格
CW_USEDEFAULT, // 初始X坐标
CW_USEDEFAULT, // 初始Y坐标
CW_USEDEFAULT, // 初始宽度
CW_USEDEFAULT, // 初始高度
NULL, // 父窗体句柄
hMenu, // 窗体菜单句柄
hInstance, // 应用程序实例句柄
NULL // 创建参数
);
运行结果如下。
处理菜单点击则可以使用WM_COMMAND
消息,该消息下WPARAM的LOWORD是菜单项ID,我们可以通过该参数可以判断哪个菜单被点击了。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDM_ABOUT)
{
MessageBox(hwnd, TEXT("关于被点击了"), TEXT("提示"), MB_OK);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
上下文菜单的创建和配置方式其实和窗体菜单中弹出式的那部分是一样的。唯一的区别是窗体菜单触发的位置是客户区顶部的一排按钮,上下文菜单则需要通过WM_CONTEXTMENU
消息触发,调用TrackPopupMenu
函数弹出。下面是一个消息处理函数例子,它从窗体菜单中取出第0个弹出菜单,作为上下文菜单。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CONTEXTMENU:
{
HMENU hMenuSub = GetSubMenu(g_hMenu, 0);
TrackPopupMenu(hMenuSub, // 菜单资源句柄
TPM_LEFTALIGN | TPM_TOPALIGN, // 菜单弹出位置
LOWORD(lParam), // 菜单X坐标
HIWORD(lParam), // 菜单Y坐标
0, // 保留字段,固定传0
hwnd, // 窗体句柄
NULL // 通常不使用,固定传NULL
);
}
case WM_COMMAND:
if (LOWORD(wParam) == IDM_ABOUT)
{
MessageBox(hwnd, TEXT("关于被点击了"), TEXT("提示"), MB_OK);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
注意使用TrackPopupMenu
函数弹出上下文菜单时,位置坐标使用屏幕坐标系(而非客户区坐标系),不过好在WM_CONTEXTMENU
消息的LPARAM参数也是给出的屏幕坐标系,两者通常配合使用,因此这里通常不能使用WM_RBUTTONUP
来代替。
Visual Studio中,我们可以直接添加Icon图标资源,Visual Studio还提供了画图功能,供我们绘制图标。
在代码中,我们可以使用LoadIcon
函数加载图标资源。下面例子中,我们将图标设置到了窗体类上。
WNDCLASS wndclass;
// ... 其它设置
wndclass.hIcon = LoadIcon(hInstance, IDI_ICON1);
对于光标资源,也是类似的使用方式,这里就不多介绍了。
Win32程序中,String Table字符串表通常用于实现国际化(i18n)。我们在一个表格中定义字符串的ID和对应的字符串内容,程序源代码则直接通过ID引用字符串,这样如果需要更改字符串的值或者修改为其它语言版本的程序,我们集中修改字符串表即可,不必修改程序源代码。大部分GUI框架、游戏引擎等都有类似的设计。
Visual Studio中创建字符串表非常简单,我们直接添加String Table资源,然后编辑其中的内容即可。
引用字符串表需要使用LoadString
函数,下面例子代码通过字符串表引用了字符串。
TCHAR buffer[256];
LoadString(g_hInstance, IDS_HELLO, &buffer, 256);
MessageBox(hwnd, &buffer, TEXT("提示"), MB_OK);
LoadString
函数中,g_hInstance
是程序的实例句柄,IDS_HELLO
是我们自定义的字符串资源ID,buffer
是字符串缓冲区,后面一个参数是缓冲区长度。
Win32中加速键(快捷键)也是以资源方式定义的,我们可以在Visual Studio中添加Accelerator类型的资源,并编辑加速键表。下面例子中,我们创建了一个Shift+A
的加速键。
使用加速键之前,我们需要先加载加速键,这需要对开启消息循环的代码进行一些修改。
HACCEL hAccel = LoadAccelerators(hInstance, IDR_ACCELERATOR1);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, hAccel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
加载加速键资源需要使用LoadAccelerators
函数,这我们很容易理解。在消息循环中,我们还使用了TranslateAccelerator
函数,它用于翻译加速键消息,将加速键消息转化为WM_COMMAND
消息。要想通过WM_COMMAND
接收加速键消息,我们就必须先用TranslateAccelerator
函数进行处理。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
if (LOWORD(wParam) == ID_SHIFTA)
{
MessageBox(hwnd, TEXT("Shift+A被按下了"), TEXT("提示"), MB_OK);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
在消息处理函数中,我们使用WM_COMMAND
接收加速键消息即可,具体使用的哪个加速键可以通过WPARAM的LOWORD判断。