10.1 图标、鼠标指针、字符串和自定义资源

来源:互联网 发布:c:foreach标签循环数组 编辑:程序博客网 时间:2024/06/15 22:02

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P333

        使用资源的一个好处是可以把程序的很多组件都绑定到程序的 .EXE 文件中。如果没有资源的概念,一个诸如图标图像的二进制文件将不得不作为一个单独的文件来保存,.EXE 文件需要将该文件读入内存才能使用。或者,图标需要在程序中定义为一个字节的数组(这会使得我们难以将实际图标图像可视化)。而作为一种资源,图标在开发者的机器上被存为一个单独的可编辑文件,然后在程序编译过程中被绑定到 .EXE 文件内

10.1.1  向程序添加图标

        向程序添加资源需要用到 Visual C++ Developer Studio 的一些附加功能。对图标来讲,可以使用 Image Editor(也叫 Graphics Editor)来绘制图标的图像。这个图像保存在一个后缀名为 .ICO 的图标文件中。Developer Studio 还会生成一个资源脚本,亦即后缀名为 .RC 的文件,有时也称作资源定义文件,该文件列出了程序的所有资源,并保护一个名为 RESOURCE.H 的头文件,应用程序通过这个头文件可以引用这些资源。

        让我们创建一个名为 ICONDEMO 的新项目来看看这些文件如何配合。与以往一样,在 Developer Studio 的 File 菜单中选择 New,选中 Projects 选项卡,然后选 Win32 Application。在 Project Name 框中输入ICONDEMO 并单击 OK按钮。此时,Developer Studio 会生成 5 个用来维护工作区和项目的文件,包括文本文件 ICONDEMO.DSW,ICONDEMO.DSP 和 ICONDEMO.MAK。(Visual Studio 2013 与之不同)(选择 Tools 菜单中的 Options,在打开的 Options 对话框中,请确认选中了 Build 选项卡内的 Export makefile when saviing project file。)

        现在让我们像以往一样创建一个  C 源代码文件。从 File 菜单下选择 New,选择 Files 选项卡,然后单击选中 C++ Source File。在 File Name 框中输入 ICONDEMO.C,然后单击 OK 按钮。这是,Developer Studio 生成了一个空白的 ICONDEMO.C 文件。输入如下所示的程序,或者选择 Insert 菜单下的 File As Text 选项卡来复制本书配套光盘(CD-ROM)中的源代码。

/*--------------------------------------------------------ICONDEMO.C -- Icon Demonstration Program     (c) Charles Petzold, 1998--------------------------------------------------------*/#include "stdafx.h"#include "resource.h"LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[] = TEXT("IconDemo");HWND         hwnd;MSG          msg;WNDCLASS     wndclass;wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = NULL;wndclass.lpszClassName = szAppName;if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("This program requires Windows NT!"),szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, TEXT("Icon Demo"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,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){static HICONhIcon;static intcxIcon, cyIcon, cxClient, cyClient;HDChdc;HINSTANCEhInstance;PAINTSTRUCTps;intx, y;switch (message){case WM_CREATE:hInstance = ((LPCREATESTRUCT)lParam)->hInstance;hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));cxIcon = GetSystemMetrics(SM_CXICON);cyIcon = GetSystemMetrics(SM_CYICON);return 0;case WM_SIZE:cxClient = LOWORD(lParam);cyClient = HIWORD(lParam);return 0;case WM_PAINT:hdc = BeginPaint(hwnd, &ps);for (y = 0; y < cyClient; y += cyIcon)for (x = 0; x < cxClient; x += cxIcon)DrawIcon(hdc, x, y, hIcon);EndPaint(hwnd, &ps);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}

        如果你试图编译这个程序,会得到一个编译错误,因为程序开始处引用的 RESOURCHES.H(Visual Studio已经自动创建了这个文件)文件还不存在。但是,没有必要直接创建此文件,而应该让 Developer Studio 来生成此文件

        可以通过在项目中加入资源脚本来完成此任务。在 File 菜单下选择 New,选择 Files 选项卡,单击 Resource Script,然后在 File Name 框中输入 ICONDEMO,单击 OK 按钮。此时,Developer Studio 生成了两个新文本文件:资源脚本文件 ICONDEMO.RC 和头文件 RESOURCH.H,这个头文件允许 C 源代码文件和资源脚本引用定义相同的标识符。不要试图直接编辑这两个文件,让 Developer Studio 来替你维护它们。如果你想查看资源脚本和 RESOURCH.H,但又不想用 Developer Studio 的界面,那么可以用记事本程序打开它们不要在记事本中改变它们,除非你确实知道自己要做什么。另外还要记住,Developer Studio 只有在你明确指定或重建项目时才会保存这些文件的新版本。

        资源脚本是文本文件。它包含文本表达式,用来表示那些可以用文本形式表达的资源,例如菜单和对话框。资源脚本也包含对二进制文件的引用,这些文件包含非文本资源,例如图标和自定义的鼠标指针。

        现在有了 RESOURCE.H 文件,可以试着再次编译 ICONDEMO 程序。这次得到一个 IDI_ICON 没有定义的错误信息。这个标识符在下面语句中第一次出现:

wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
ICONDEMO 中的这条语句代替了本书前面程序中的如下语句:

wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
可以理解我们为什么改变这条语句,因为我们之前一直在应用程序中使用标准图标,而此处我们的目的是使用自定义图标。

        所以让我们来创建一个图标。在 Developer Studio 的 File View 窗口(Visual Studio 的【解决方案资源管理器】->【资源文件】)中,现在可以看到两个文件——ICONDEMO.C 和 ICONDEMO.RC。单击 ICONDEMO.C 时,可以编辑源代码。而单击 ICONDEMO.RC 时,可以向该文件添加资源或编辑现有资源。要加入图标,应在 Insert 菜单下选择 Resource。单击你想要加入的资源,这里是图标,然后单击 New 按钮。

        这时会显示一个可涂色编辑的大小为 32*32 像素的空白图标。你会看到一个保护一系列绘图工具的浮动工具条以及很多可选颜色。注意,颜色工具条中有两个可选项其实并非真正意义上的颜色。它们有时被称为“屏幕色”和“反转屏幕色”。当一个像素被涂为“屏幕色”,它实际上时透明的。该图标后面的任何表明都会显示出来。这可以允许你创建看起来像是非矩形的图标

        在继续下一步之前,先双击围绕图标的区域。这会得到一个 Icon Properties 对话框,它允许你修改图标的 ID 和文件名。Developer Studio 可能已经将 ID 设置为 IDI_ICON1.将其改为 IDI_ICON。因为这时 ICONDEMO 程序所引用的 ID。(IDI 前缀代表 “id for an icon”。)另外,将文件名也改为 ICONDEMO.ICO。

        现在, 我想让你选择一个明显的颜色(比如红色),然后在这个图标上绘制一个大 B(代表“big”)。它不一定要像图 10-2 那样漂亮。

图 10-2  Developer Studio (Visual Studio 2013)显示的标准(32*32)ICONDEMO 图标

        这个程序现在可以正常编译和运行了。Developer Studio 在 ICONDEMO.RC 资源脚本中加入了一行代码,将图标文件 ICONDEMO.ICO 和标识符 IDI_ICON 等同起来。RESOURCE.H 头文件包含 IDI_ICON 标识符的定义。(我们很快将对此进行深入讨论。)

        Developer Studio 使用资源编译器 RC.EXE 来编译资源。文本形式的资源脚本被转换成二进制格式,它是一个扩展名为 .RES 的文件。编译过的资源文件在 LINK 阶段和 .OBJ 及 .LIB 文件一起被指定。这就是资源如何被加入最终的 .EXE 文件的过程

        运行 ICOONDEMO 时,程序的图标显示在标题栏的左上角和任务栏中。如果将此程序添加到【开始】菜单,或在桌面上添加一个快捷方式,那么也能在那里看到该图标。

        ICONDEMO 也会在其客户区显示该图标,并在水平和垂直方向重复排列。程序用如下语句获得图标的句柄:

hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
下面的语句用来获得图标的大小:

cxIcon = GetSystemMetrics(SM_CXICON);cyIcon = GetSystemMetrics(SM_CYICON);
程序然后多次调用下面的语句来显示图标:

DrawIcon(hdc, x, y, hIcon);
其中 x 和 y 是图标显示位置的左上角坐标。

        对目前使用的大多数视频显示适配器,带有 SM_CXICON 和 SM_CYICON 标志的 GetSystemMetrics 会报告图标的大小为 32 * 32 像素。这是我们在 Developer Studio 中所创建的图标的大小。它也是该图标出现在桌面上的大小以及在 ICONDEMO 程序的客户区中所显示图标的大小。但它不是在程序的标题栏和任务栏中显示的大小。更小的图标的尺寸可以通过调用带有 SM_CXSMSIZE 和 SM_CYSMSIZE 标志的 GetSystemMetrics 得到。(第一个“SM”表示“system metrics”;中间的“SM”代表“small”。)对目前使用的大多数显示适配器,小图标的大小是 16 * 16 像素。

        这可能会产生问题。当 Windows 将 32 * 32 图标缩小为 16 * 16 大小时,它必须去掉隔行隔列的像素。对一些复杂的图标图像,这会引起失真。由于这个原因,在缩小图标不可行的情况下,你也许应当创建一个特定的 16 * 16 大小的图标。在 Developer Studio 中图标图像上面有一个标识为 Device 的组合框。在它右面是一个按钮。单击该按钮会激活一个 New Icon Image 对话框。选择 Small(16 * 16)。现在你可以绘制另外一个图标。在这里使用S(代表“small”),如图 10-3 所示。

图 10-3  Developer Studio(Visual Studio 2013) 显示的小号(16 * 16)ICONDEMO 图标

        在程序中无需做其他任何事情。第二个图标图像被保存在同一个 ICONDEMO.ICO 文件中,并通过同一个 IDI_ICON 标识符被引用。Windows 会再适当的时候自动使用小图标,例如在标题栏和任务栏中。在桌面上显示快捷方式以及在程序调用 DrawIcon 装饰其客户区时,Windows 会使用大图像。

        现在既然已经有了使用经验,那就让我们来深入研究一下内部的工作过程。

10.1.2  获得图标的句柄

        如果看一下 ICONDEMO.RC 和 RESOURCE.H 文件,你便会看到一大堆 Developer Studio 自动生成的来帮助它维护文件的东西。然而,在编译资源脚本时,只有几行是重要的。下面显示了 ICONDEMO.RC 和 RESOURCE.H 中的这些关键节选。

ICONDEMO.RC (excerpts)// Microsoft Visual C++ generated resource script.//#include "resource.h"///////////////////////////////////////////////////////////////////////////////// Icon//// Icon with lowest ID value placed first to ensure application icon// remains consistent on all systems.IDI_ICON                ICON                    "ICONDEMO.ICO"

RESOURCE.H (excerpts)// Microsoft Visual C++ 生成的包含文件。// 供 IconDemo.rc 使用#define IDI_ICON                        132

        如上显示的 ICONDEMO.RC 和 RESOURCE.H 文件,看起来像是你在普通文本编辑器中手工创建的,就像 Windows 程序员在 20 世纪 80 年代早期所做的一样。唯一的实际区别在于 AFXRES.H 文件的存在,它是一个包含许多公告标识符的头文件,这些标识符被 Developer Studio 用来产生机器自动生成的 MFC 项目。在本书中我不会用到 AFXRES.H 文件。

        ICONDEMO.RC 中的下面这行是资源脚本 ICON 语句:

IDI_ICON ICON DISCARDABLE "ICONDEMO.ICO"
该图标有一个数字标识符 IDI_ICON,其值为 101。Developer Studio 加入的 DISCARDABLE 关键字表明 Windows 在必要时可以将此图标从内存中丢弃,以获得一些额外空间。无需程序采取任何特殊操作,Windows 随时可以重新加载该图标。DISCARDABLE 属性是默认值,不需要特别指定。为了预防名字或路径包含空格,Developer Studio 将文件名用引号括起来

        在资源编译器保存编译过的资源到 ICONDEMO.RES 时,以及在链接器添加资源到 ICONDEMO.EXE 时,该资源仅通过一个资源类型(这里是 RT_ICON)和一个标识符(这里是 IDI_ICON 或 132)来识别。程序可以调用 LoadIcon 函数来获得此图标的句柄:

hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
注意,ICONDEMO 在两个地方调用了这个函数——一处是在定义窗口类时,另一处是再窗口过程中获取该图标的句柄来绘图时。LoadIcon 返回一个 HICON 类型的值,即该图标的句柄。

        LoadIcon 的第一个参数是表示资源来自何文件的实例句柄。使用 hInstance 表明它来自程序本身的 .EXE 文件。LoadIcon 的第二个参数实际上定义为字符串指针。我们马上会看到,可以用字符串而不是数字标识符来标识资源,MAKEINTRESOURCE 宏(这个名称表示“make an integer into a resource string”)是这样用数字来产生指针的:

#define MAKEINTRESOURCE(i) ((LPTSTR)((DWORD)((WORD)(i))))
LoadIcon 函数知道如果第二个参数的高位字为 0,那么低位字就是图标的数字标识符。图标的标识符必须是一个 16 位值

        本书以前的范例程序使用的是预定义的图标:

LoadIcon(NULL, IDI_APPLICATION);
Windows 知道这是一个预定义图标,因为 hInstance 参数是 NULL。IDI_APPLICATION 恰好也在 WINUSER.H 中用 MAKEINTRESOURCE 的形式进行了定义:

#define IDI_APPLICATION     MAKEINTRESOURCE(32512)
        LoadIcon 的第二个参数提出了一个有趣的问题:图标标识符能是一个字符串吗?答案是肯定的,它是这样实现的:在 ICONDEMO 项目的 Developer Studio 文件列表中,选择 ICONDEMO.RC 文件。你会看到一个树状结构,上面开始处是 IconDemo Resources,然后是资源类型的图标,再是图标 IDI_ICON。如果你右击图标标识符并从菜单中选择 Properties,你就可以改变它的 ID。实际上,你可以将它改成字符串,只需将名字用引号括起来。这是我倾向于使用的给资源命名的办法,我会在本书后面经常使用。

        我倾向于给图标(和其他一些资源)使用文本名字,因为这个名字可以使程序的名字。例如,假设程序名字是 MYPROG。如果你用 Icon Properties 对话框将图标 ID 指定为 “MyProg”(包括引号),那么资源脚本会包含下面的语句:

MyProg                  ICON                    "ICONDEMO.ICO"
不过,RESOURCE.H 中将没有#define 语句来表明 MyProg 是一个数字标识符。相反,资源脚本会假定 MyProg 是一个字符串标识符。

        在 C 程序中,应使用 LoadIcon 函数来获得图标句柄。回忆一下,你可能已经有一个字符串变量来表示程序的名字了:

static TCHAR szAppName [] = TEXT ("MyProg");
这意味着程序能使用下面的语句来加载图标,这看起来比 MAKEINTRESOURCE 宏精炼许多:

hIcon = LoadIcon (hInstance, szAppName);
        但如果真想使用数字而非名字,那么也可以使用它们来代替标识符或字符串。在 Icon Properties 对话框中,在 ID 框中输入一个数字。资源脚本将有一个类似于下面的 ICON 语句:

125                     ICON                    "ICONDEMO.ICO"
此时引用图标有两种方法。直观的办法是:

hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (125));
非直观的办法是:

hIcon = LoadIcon (hInstance, TEXT("#125"));

Windows 将起始的 # 字符识别为 ASCII 格式的数字前缀。

10.1.3  在应用程序中使用图标

        虽然 Windows 可以通过多种方式来用图标表示一个程序,但是许多 Windows 程序只是在定义一个带有 WNDCLASS 结构并使用 RegisterClass 的窗口类时才指定图标。正如我们所见过的,这工作得很好,尤其是当图标文件同时包含标准大小和小号图像时。在需要显示图标图像时,Windows 会在图标文件中选择大小最合适的图像。

        RegisterClass 有一个增强版本,名为 RegisterClassEx,它使用一个名为 WNDCLASSEX 的结构。WNDCLASSEX 结构有两个额外的字段:cbSize 和 bIconSm。cbSize 字段表示 WNDCLASSEX 结构的大小,而 hIconSm 应该被设为小图标的句柄。因此,在 WNDCLASSEX 结构中你需要设定与两个图标文件相关联的两个图标句柄——一个是标准图标而另一个是小图标。

        这有必要吗?答案是不必要,因为 Windows 已经从单个图标文件中提取了正确尺寸的图标图像。RegisterClassEx 似乎丢掉了 RegisterClass 的智能性。如果 hIconSm 字段引用的是一个包含多个图像的图标文件,那么只有第一个会被使用。这可以使一个标准尺寸的图标,只不过之后会被缩小。RegisterClassEx 似乎是为使用多个图标图像设计的,这些图像每个只包含一个图标尺寸。因为我们现在可以在同一个文件中包含多个图标尺寸,所以我的建议是使用 WMDCLASS 和 RegisterClass

        如果想在程序运行时动态改变程序的图标,可以通过调用 SetClassLong 函数来实现。比如,如果有另一个和标识符 IDI_ALTICON 相关联的图标文件,便可以使用下面的语句切换到那个图标:

SetClassLong (hwnd, GCL_HICON,     LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ALTICON)));

        如果不想保存程序图标的句柄,而是使用 DrawIcon 函数来在某处显示它,那么你可以调用 GetClassLong 函数来获得句柄。比如:

DrawIcon (hdc, x, y, GetClassLong (hwnd,, GCL_HICON));

        在 Windows 文档的某些地方,LoadIcon 被描述为“已过时”,而 LoadImage 被推荐使用。LoadImage 当然更加灵活,但它目前还无法代替 LoadIcon 的简洁性。你会注意到在 ICONDEMO 中 LoadIcon 对同一图标被调用了两次。这不是个问题,不会因此有更多内存被使用。LoadIcon 是少有的这样几个函数之一:它获得一个句柄,但不要求该句柄被销毁。实际上确实存在一个 DestroyIcon 函数,但它是和 CreateIcon、CreateIconIndirect 以及 CreateIconFromResource 配套使用的。这些函数允许程序用算法动态生成图标。

10.1.4  使用自定义鼠标指针

        在程序中使用自定义的鼠标指针类似于使用自定义图标,但大多数程序员发现 Windows 提供的鼠标指针好像足够用了。自定义鼠标指针一般是单色的,大小为 32 * 32 像素。在 Developer Studio 中创建鼠标指针和创建图标的方法一样(亦即从 Insert 菜单中选择 Resource,然后选择 Cursor),但是不要忘了定义热点。

        你可以使用如下语句在类定义中设置自定义鼠标指针:

wndclass.hCursor = LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR));
或者,如果是用文本名字来定义的鼠标指针:

wndclass.hCursor = LoadCursor (hInstance,szCursor);
每当鼠标定位在基于此类创建的窗口上时,与 IDC_CURSOR 或 szCursor 相关联的自定义鼠标指针就会显示出来。

        如果你使用了子窗口,则可能会想让鼠标指针随着它下面的子窗口的不同而变换它的样子。如果你的程序对这些子窗口定义了窗口类,那么通过对每个窗口类设置适当的 hCursor 字段,就可以对每个类使用不同的鼠标指针。而如果你使用了预定义的子窗口控件,那么还可以使用如下语句来更改窗口类的 hCursor 字段:

SetClassLong (hwndChild, GCL_HCURSOR,              LoadCursor (hInstance, TEXT ("childcursor"));

         如果你将客户区分割成小的逻辑区域而不是使用子窗口,那就可以使用 SetCursor 来改变鼠标指针

SetCursor (hCursor);
你应该在处理 WM_MOUSEMOVE 消息时调用 SetCursor 函数。否则,当鼠标移动时,Windows 会使用窗口类中指定的鼠标指针来重绘鼠标。官方文档指出,当鼠标指针无需更换时 SetCursor 会运行得很快

10.1.5  字符串资源

        对字符串使用资源乍看起来有些怪。我们在源代码里直接使用正常的老式字符串变量至今为止还没有出现过问题。

        字符串资源主要是方便将你的程序翻译成其他语言。在本章后面及下一章,你会发现菜单和对话框也是资源脚本的一部分。如果你使用字符串资源而不是将字符串直接放进源代码,那么程序使用的所有文本都会在一个文件里——即资源脚本。如果资源脚本中的文本被翻译成了另一种语言,生成程序的非英语版本所需的全部工作就是重新链接程序。这个方法比乱改你的源代码程序要安全得多。(然而,除了下一个示例程序,我不会在本书中的任何一个其他程序中使用字符串表。原因是字符串会使代码看起来更加晦涩和复杂,而不是清晰明了。)

        要创建字符串表,应从 Insert 菜单中选择 Resource,然后选择 String Table。字符串将会显示在屏幕右侧的列表里。双击一个字符串可以选中它。对每个字符串,都需要制定标识符和字符串本身。

        在资源脚本里,字符串显示为类似于下面的多行语句:

STRINGTABLEBEGIN    IDS_STRING1, "character string 1"    IDS_STRING2, "character string 2"    [其他字符串定义]END
如果你是给早期的 Windows 编程并使用文本编辑器来手工创建字符串表(你可能会正确地猜到这比在 Developer Studio 里创建字符串表容易),你可以使用左右大括号来代替 BEGIN 和 END 语句。

        资源脚本可以有多个字符串表,但每个 ID 必须唯一地标识一个字符串。每个字符串只能有一行,最多可以有 4097 个字符。使用\t 和\n 来代表制表符和换行。在 DrawText 和 MessageBox 中可以使用这些控制字符。

        程序可以调用 LoadString 来复制字符串资源到程序数据区的缓存中:

LoadString (hInstance, id, szBuffer, iMaxLength);
id 参数代表资源脚本中每个字符串前面的 ID 号;szBuffer 是一个指向接受字符串的字符数组的指针;iMaxLength 是 szBuffer 可以接收的最大字符数。此函数返回字符串中字符的数目。

        每个字符串前面的字符串 ID 号通常是定义在头文件里的宏标识符。许多 Windows 程序员使用前缀 IDS_ 来表示一个字符串的 ID 号。有时文件名或者其他信息必须在显示字符串时嵌入到字符串里。这种情况下,你可以在字符串中加入 C 语言的格式设置字符并在 wsprintf 中将它作为格式设置字符串。

        所有资源文本,包括字符串表中的文本,都以 Unicode 格式保存在编译过的 .RES 资源文件以及最终的 .EXE 文件中。LoadStringW 函数直接加载 Unicode 文本。LoadStringA 函数(Windows 98 中唯一可用函数)则执行从 Unicode 到本地代码页的文本转换。

        让我们来看一个函数例子,它使用三个字符串在消息框中显示三个错误信息。正如你在下面看到的,RESOURCE.H 头文件包含这些消息的三个标识符。

#define IDS_FILENOTFOUND 1#define IDS_FILETOOBIG   2#define IDS_FILEREDAONLY 3
资源脚本含有如下字符串表:

STRINGTABLEBEGIN    IDS_FILENOTFOUND, "File %s not found."    IDS_FILETOOBIG,   "File %s too large to edit."    IDS_FILEREADONLY, "File %s is read-only."END

        C 源代码文件也包含这个头文件,并且定义了一个函数来显示消息框。(我还假设 szAppName 是一个包含程序名字的全局变量。)

OkMessage (HWND hwnd, int iErrorNumber, TCHAR *szFileName){    TCHAR szFormat[40];    TCHAR szBuffer[60];       LoadString (hInst, iErrorNumber, szFormat, 40);    wsprintf (szBuffer, szFormat, szFilename);    reutrn MessageBox (hwnd, szBuffer, szAppName,                       MB_OK | MB_ICONEXCLAMATION);}
为了显示含有 “File not found”(没有找到文件)的消息框,程序可调用:

OkMessage (hwnd, iErrorNumber, szFileName);

10.1.5  自定义资源

        Windows 也定义了一种“自定义资源”,也叫“用户定义资源”(这里的用户指的就是你——程序员,而不是要使用你程序的那个幸运的家伙)。自定义资源对于向你的 .EXE 文件附加其他数据以及在程序中访问这些附加数据都很方便。这些数据可以使你想要的任何格式。程序用来访问自定义资源的 Windows 函数会让 Windows 把该数据加载到内存,并返回一个指向它的指针。你可以对该数据做任何事情。和将数据保存到外部文件并用文件输入函数来访问相比,你可能会发现这是一个储存和访问私有数据的更方便办法

        比如,假设你有一个叫做 BINDATA.BIN 的文件,它包含程序显示所需的一堆数据。这个文件可以使你选择使用的任何格式。如果你在 MYPROG 项目中有 MYPROG.RC 资源脚本,则可以在 Developer Studio 中生成自定义资源。从 Insert 菜单中选择 Resource,然后单击 Custom 按钮。输入一个标识资源的类型名字,比如 BINTYPE。然后 Developer Studio 会生成一个资源名(在这种情况下,是 IDR_BINTYPE1),并显示一个窗口让你来输入二进制数据。但你不需那样做。用鼠标右键单击 IDR_BINTYPE1 名字,再选择 Properties。然后你可以输入一个文件名,比如,BINDATA.BIN。

        资源脚本此后会包含如下语句:

IDR_BINTYPE1 BINTYPE BINDATA.BIN
这条语句看上去很像 ICONDEMO 中的 ICON 语句,除了资源类型 BINTYPE 是我们刚生成的。像图标一样,你可以使用文本名字而不是数字标识符来命名资源。

        编译和链接程序时,整个 BINDATA.BIN 文件会被绑定到 MYPROG.EXE 文件。

        在程序初始化时(比如,在处理 WM_CREATE 消息时),你可以获得此资源的句柄:

hResource = LoadResource(hInstance,             FindResource(hInstance, MAKEINTRESOURCE(IDR_BINTYPE1),                         TEXT("BINTYPE")));
hResource 变量被定义成 HGLOBAL 类型,即指向内存块的句柄。尽管名字是 LoadResource,但它并不真正将资源加载到内存。像这样一起使用 LoadResource 和 FindResource 函数本质上和 LoadIcon 及 LoadCursor 一样。实际上,LoadIcon 和 LoadCursor 就是调用了 LoadResource 和 FindResource 函数。

        需要访问文本时,应调用 LockResource:

pData = LockResource (hResource);
LockResource 把资源加载到内存(如果它还没有被加载),并返回一个指向它的指针。使用完资源时,你可以将它从内存中释放:

FreeResource (hResource);
如果程序终止,资源也会被释放,即使你没有调用 FreeResource。

        让我们看一个示例程序,它用到了三种资源——图标,字符串表和自定义资源。PREPOEM 程序在客户区显示了埃德加●爱伦●坡的“Annabel Lee”。自定义资源时 POEPOEM.TXT 文件,它包含诗的正文。该文本文件以反斜杠结尾。

/*--------------------------------------------------------POEPOEM.C -- Demonstration Custom Resource(c) Charles Petzold, 1998--------------------------------------------------------*/#include "stdafx.h"#include "resource.h"LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);HINSTANCE hInst;int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow){static TCHAR szAppName[16], szCaption[64], szErrMsg[64];HWND         hwnd;MSG          msg;WNDCLASS     wndclass;LoadString(hInstance, IDS_APPNAME, szAppName,sizeof(szAppName) / sizeof(TCHAR));LoadString(hInstance, IDS_CAPTION, szCaption,sizeof(szCaption) / sizeof(TCHAR));wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(hInstance, szAppName);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = NULL;wndclass.lpszClassName = szAppName;if (!RegisterClass(&wndclass)){LoadStringA(hInstance, IDS_APPNAME, (char *)szAppName,sizeof(szAppName));LoadStringA(hInstance, IDS_ERRMSG, (char *)szErrMsg,sizeof(szErrMsg));MessageBoxA(NULL, (char *)szErrMsg,(char *)szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, szCaption,WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,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){static char *pText;static HWNDhScroll;static intiPosition, cxChar, cyChar, cyClient, iNumLines, xScroll;HDChdc;PAINTSTRUCTps;RECTrect;TEXTMETRICtm;char *pData;HRSRChResInfo;DWORDdwSize;HGLOBAL        hResource;switch (message){case WM_CREATE:hdc = GetDC(hwnd);GetTextMetrics(hdc, &tm);cxChar = tm.tmAveCharWidth;cyChar = tm.tmHeight + tm.tmExternalLeading;ReleaseDC(hwnd, hdc);xScroll = GetSystemMetrics(SM_CXVSCROLL);hScroll = CreateWindow(TEXT("scrollbar"), NULL,WS_CHILD | WS_VISIBLE | SBS_VERT,0, 0, 0, 0,hwnd, (HMENU)1, hInst, NULL);//获取文本句柄hResInfo = FindResource(hInst, TEXT("AnnabelLee"), TEXT("TEXT")); //rc头文件中会自动生成宏定义,要删掉才可以正确获得值hResource = LoadResource(hInst, hResInfo); //资源是只读数据dwSize = SizeofResource(hInst, hResInfo);pText = pData = (char*)calloc(dwSize, sizeof(char));memcpy_s(pText, dwSize, (char*)LockResource(hResource), dwSize); //内存拷贝FreeResource(hResInfo);//读取文本行数iNumLines = 0;while ((*pData != '\\') && (*pData != '\0')) //遇\或\0结束{if (*pData == '\n')iNumLines++;pData = AnsiNext(pData);}*pData = '\0';SetScrollRange(hScroll, SB_CTL, 0, iNumLines, FALSE);SetScrollPos(hScroll, SB_CTL, 0, FALSE);iPosition = 0;return 0;case WM_SIZE:MoveWindow(hScroll, LOWORD(lParam) - xScroll, 0,xScroll, cyClient = HIWORD(lParam), TRUE);SetFocus(hwnd);return 0;case WM_SETFOCUS:SetFocus(hScroll);return 0;case WM_VSCROLL:switch (wParam){case SB_TOP:iPosition = 0;break;case SB_BOTTOM:iPosition = iNumLines;break;case SB_LINEUP:iPosition -= 1;break;case SB_LINEDOWN:iPosition += 1;break;case SB_PAGEUP:iPosition -= cyClient / cyChar;break;case SB_PAGEDOWN:iPosition += cyClient / cyChar;break;case SB_THUMBPOSITION:iPosition = HIWORD(wParam);break;}iPosition = max(0, min(iPosition, iNumLines));if (iPosition != GetScrollPos(hScroll, SB_CTL)){SetScrollPos(hScroll, SB_CTL, iPosition, TRUE);InvalidateRect(hwnd, NULL, TRUE);}return 0;case WM_PAINT:hdc = BeginPaint(hwnd, &ps);GetClientRect(hwnd, &rect);rect.left += cxChar;rect.top += cyChar * (1 - iPosition);DrawTextA(hdc, pText, -1, &rect, DT_EXTERNALLEADING);EndPaint(hwnd, &ps);return 0;case WM_DESTROY:if (pText != NULL) free(pText);PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam);}POEPOEM.RC (excerpts)// Microsoft Visual C++ generated resource script.//#include "resource.h"///////////////////////////////////////////////////////////////////////////////// TEXT//ANNABELLEE              TEXT                    "POEPOEM.TXT"///////////////////////////////////////////////////////////////////////////////// Icon//IDI_SMALL               ICON                    "small.ico"POEPOEM                 ICON                    "POEPOEM.ICO"///////////////////////////////////////////////////////////////////////////////// String Table//STRINGTABLEBEGIN    IDS_APPNAME             "PoePoem"    IDS_CAPTION             """Annabel Lee"" by Edgar Allan Poe"    IDS_ERRMSG              "This program requires Windows NT!"ENDRESOURCE.H (excerpts)// Microsoft Visual C++ 生成的包含文件。// 供 Poepoem.rc 使用//#define IDS_APPNAME                     104#define IDS_CAPTION                     105#define IDS_ERRMSG                      106POEPOEM.TXTIt was many and many a year ago,   In a kingdom by the sea,That a maiden there lived whom you may know   By the name of Annabel Lee;And this maiden she lived with no other thought   Than to love and be loved by me. I was a child and she was a child   In this kingdom by the sea,But we loved with a love that was more than love --   I and my Annabel Lee --With a love that the winged seraphs of Heaven   Coveted her and me. And this was the reason that, long ago,   In this kingdom by the sea,A wind blew out of a cloud, chilling   My beautiful Annabel Lee;So that her highborn kinsmen came   And bore her away from me,To shut her up in a sepulchre   In this kingdom by the sea. The angels, not half so happy in Heaven,   Went envying her and me --Yes! that was the reason (as all men know,   In this kingdom by the sea)That the wind came out of the cloud by night,   Chilling and killing my Annabel Lee. But our love it was stronger by far than the love   Of those who were older than we --   Of many far wiser than we --And neither the angels in Heaven above   Nor the demons down under the seaCan ever dissever my soul from the soul   Of the beautiful Annabel Lee: For the moon never beams, without bringing me dreams   Of the beautiful Annabel Lee;And the stars never rise, but I feel the bright eyes   Of the beautiful Annabel Lee:And so, all the night-tide, I lie down by the sideOf my darling -- my darling -- my life and my bride,   In her sepulchre there by the sea --   In her tomb by the sounding sea.                                        [May, 1849]\
POEPOEM.ICO

        在 POEPOEM.RC 资源脚本中,用户定义资源被指定为 TEXT 类型,文本名字为 “ANNABELLEE”:

ANNABELLEE              TEXT                    "POEPOEM.TXT"
在 WndProc 中处理 WM_CREATE 消息时,该资源的句柄通过 FindResource 和 LoadResource 来获得。LockResource 将该资源锁定,一个小的例程将文件尾部的反斜杠(\)替换为 0。这是为了方便在后面的 WM_PAINT 消息中使用 DrawText 函数。

        注意,我们使用了子窗口滚动条控件而不是窗口滚动条。该子窗口滚动条控件有一个自动键盘接口,所以在 POEPOEM 中不需要处理 WM_KEYDOWN 消息。

        POEPOEM 还使用了三个字符串,它们的 ID 在 RESOURCE.H 头文件中定义。在程序的开始,IDS_APPNAME 和 IDS_CAPTION 字符串被 LoadString 加载到内存:

LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName) / sizeof(TCHAR));LoadString(hInstance, IDS_CAPTION, szCaption, sizeof(szCaption) / sizeof(TCHAR));
注意,这两个调用在 RegisterClass 之前。如果你在 Windows 98 下使用 POEPOEM 的 Unicode 版本,这两个函数调用会失败。尽管 LoadStringA 比 LoadStringW 更复杂(因为 LoadStringA 必须将资源字符串从 Unicode 转换成 ANSI,而 LoadStringW 则直接加载),但LoadStringW 不是 WIndwos 98 所支持的仅有的几个字符串函数之一。这意味着 RegisterClassW 函数在 Windows 98 中失败时,MessageBoxW 函数(在 Windows 98 中支持)将不能使用由 LoadStringW 加载到程序的字符串。基于此,程序用 LoadStringA 加载 IDS_APPNAME 和 IDS_ERRMSG 字符串,并用 MessageBoxA 来显示自定义消息框:

if (!RegisterClass(&wndclass)){LoadStringA(hInstance, IDS_APPNAME, (char *)szAppName, sizeof(szAppName));LoadStringA(hInstance, IDS_ERRMSG, (char *)szErrMsg, sizeof(szErrMsg));MessageBoxA(NULL, (char *)szErrMsg, (char *)szAppName, MB_ICONERROR);return 0;}
注意从 TCHAR 字符串变量到 char 指针的强制转换。

        由于 POEPOEM 使用的所有字符串都被定义为资源,所以程序很容易就能让翻译人员转换为非英语版本。当然,他们同时也要翻译“Annabel Lee”的文字——我怀疑,这可能是个更难的任务。

0 0