📄 10. 菜单及其它资源.txt
字号:
在掌握这些知识之后,让我们看一看使用图示的详细情况。
取得图示句柄
如果您仔细阅读ICONDEMO.RC和RESOURCE.H文件,会看到由Developer Studio产生用于维护文件的一些标记。然而,当编译资源描述档时,只有少数几行是重要的。这些从ICONDEMO.RC和RESOURCE.H文件中摘录下来的关键部分被列在程序10-2中。
ICONDEMO.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Icon
IDI_ICON ICON DISCARDABLE "icondemo.ico"
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by IconDemo.rc
#define IDI_ICON 101
程序10-2 ICONDEMO.RC和RESOURCE.H文件的摘录
程序10-2所显示的ICONDEMO.RC和RESOURCE.H文件与您在普通的文字编辑器中手动建立的很相似,80年代的Windows程序写作者就是这样做的。唯一不同的是AFXRES.H,它是个表头文件,包含了在建立由机器产生的MFC项目时由Developer Studio使用的常用标识符。在本书中,我们不会用到AFXRES.H。
ICONDEMO.RC中的这行
IDI_ICON ICON DISCARDABLE "icondemo.ico"
是资源描述档的ICON叙述。该图示有一个数值标识符IDI_ICON,等于101。由Developer Studio添加的DISCARDABLE关键词指出,必要时Windows可以从内存中丢弃图标,以获得额外的空间。之后不需要程序任何特定的操作,Windows就能够重新加载图示。DISCARDABLE属性是内定的,不需要指定。只有在名称和目录路径包含空格时,Developer Studio才将文件名加上引号。
当资源编译程序将编译的资源储存在ICONDEMO.RES中,并且由连结程序将资源添加到ICONDEMO.EXE中以后,该资源就可以经由一个资源型态(RT_ICON)和一个标识符(IDI_ICON或101)来标识。程序可以通过呼叫LoadIcon函数取得此图示的句柄:
hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;
请注意ICONDEMO在两个地方呼叫这个函数,一次在定义窗口类别时,另一次在窗口消息处理程序中取得图标的句柄用于绘制。LoadIcon传回HICON型态的值,它是图示的句柄。
LoadIcon的第一个参数,是指出资源来自哪个文件的执行实体句柄。使用hInstance表示它来自程序自己的.EXE文件。LoadIcon的第二个参数实际上被定义为指向字符串的指针。待会将会看到,可以使用字符串而不是用数值标识符标识资源。宏MAKEINTRESOURCE(把整数转换成资源字符串)生成指向非数字的指针,如下所示:
#define MAKEINTRESOURCE(i) (LPTSTR) ((DWORD) ((WORD) (i)))
LoadIcon知道,如果第二个参数的高字组为0,那么低字组就为图示的数值标识符。图标的标识符必须为16位值。
本书前面的范例程序使用了预先定义的图示:
LoadIcon (NULL, IDI_APPLICATION) ;
hInstance参数被设定为NULL,因此Windows知道这是预先定义的图示。IDI_APPLICATION也在WINUSER.H中用MAKEINTRESOURCE定义:
#define IDI_APPLICATION MAKEINTRESOURCE(32512)
LoadIcon的第二个参数带来了一个有趣的问题:图标的标识符能可以为字符串吗?答案是可以。方法如下:在 Developer Studio中,在ICONDEMO项目的文件列表上,选择 IDONDEMO.RC。您会看到顶端为「IconDemo Resource」的树状结构,然后是资源型态「Icon」,再下来是「IDI_ICON」。如果用鼠标右键单击图标标识符,并从菜单上选择「 Properties」,您就能改变ID。实际上,您可以把名称放在引号内将其更改为字符串。我用这种方法指定资源名称,并在本书的其它地方也使用该方法。
我喜欢为图示(以及一些其它资源)使用文字名称,因为名称可以是程序的名称。例如,假定文件被命名为MYPROG。如果您使用「Icon Properties」对话框将图标的ID指定为「MyProg」(包括引号),资源描述档将包含下列叙述:
MYPROG ICON DISCARDABLE myprog.ico
然而,在RESOURCE.H中并没有#define叙述,来指出MYPROG是数值标识符。资源描述文件将假定MYPROG是字符串标识符。
在C程序中,使用LoadIcon函数来取得图示句柄。您可能已经有了表示程序名的字符串:
static TCHAR szAppName [] = TEXT ("MyProg") ;
这意味着程序可以使用叙述:
hIcon = LoadIcon (hInstance, szAppName) ;
来加载图标,这比宏MAKEINTRESOURCE更清晰一些。
但是如果您确实想用数字来命名,那么您可以用数字代替标识符或字符串。在「Icon Properties」对话框中,在ID栏中输入数字。资源描述档将有一个类似下面的ICON叙述:
125 ICON DISCARDABLE myprog.ico
可以使用两种方法之一引用图示。明显易读的方式是:
hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (125)) ;
另一个不易阅读的方式是:
hIcon = LoadIcon (hInstance, TEXT ("#125")) ;
Windows识别初始字符#作为ASCII形式中字符数值的开头。
在程序中使用图标
虽然Windows以几种方式用图标来代表程序,但是许多Windows程序仅在用WNDCLASS结构和RegisterClass定义窗口类别时指定一个图示。如我们所看到的,这样作用得很好,尤其当图示文件包含标准和较小的图像大小时,更是如此。Windows在显示图标图像时,它会在图示文件中选择最合适的图像大小。
RegisterClass有一个改进版本叫做RegisterClassEx,它使用名为WNDCLASSEX的结构。WNDCLASSEX有两个附加的字段:cbSize和hIconSm。cbSize字段指出了WNDCLASSEX结构的大小,假设hIconSm被设定为小图标的图标句柄。这样,在WNDCLASSEX结构中,您可以设定与两个图示文件相关的两个图示句柄-一个用于标准图示,一个用于小图示。
有这种必要吗?没有。正如我们看到的,Windows已经从单个图示文件中提取了大小合适的图标图像。RegisterClassEx似乎没有RegisterClass聪明。如果hIconSm字段使用了包含多个图像的图标文件,则只有第一个图像能被利用。它可能是标准大小的图示,使用时才被缩小。RegisterClassEx似乎是为了使用多个图标图像而设计的,每个图像只包含一种图标大小。因为现在可以将多个图示大小包括在同一个图示文件中,所以我建议使用WNDCLASS和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(LoadIcon在/Platform SDK/User Interface Services/Resources/Icons中说明,LoadImage在/Platform SDK/User Interface Services/Resources/Resources中说明)。当然LoadImage更为灵活,但它没有LoadIcon简单。您会注意到,在ICONDEMO中对同一个图示呼叫了LoadIcon两次。这不会产生问题,也没有使用额外的内存。LoadIcon是取得句柄但不需要清除句柄的少数几个函数之一。实际上有一个DestroyIcon函数,但它与CreateIcon、CreateIconIndirect和CreateIconFromResource连在一起使用。这些函数使程序能够动态地建立图标图像。
使用自订光标
在程序中使用自订的鼠标光标与使用自订的图示相似,只是大多数程序写作者总是使用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速度将会很快。
字符串资源
把字符串当成资源的观念一开始可能令人觉得诡异。因为我们在使用原始码中定义为变量的一般字符串时,并没有碰到任何问题。
字符串资源主要是为了让程序转换成其它语言时更为方便。正如后面两章中将看到的一样,菜单和对话框也是资源描述文件的一部分。如果使用字符串资源而不是将字符串直接放入原始码中,那么程序所使用的所有文字将在同一文件-资源描述档中。如果转换了资源描述文件中的文字,那么建立程序的另一种语言版本所需做的一切就是重新连结程序。这种方法比重新组织原始码安全得多(然而,除了下一个范例程序,我在本书的其它程序中不使用字符串表,原因是字符串表使程序代码看起来更为模糊和复杂)。
您可以在「Insert」菜单中选择「Resource」,再选择「 String Table」,建立一个字符串表。字符串会显示在屏幕右边的列表中。通过双击字符串就可以选中它。针对每个字符串,您可以指定标识符和字符串的内容。
在资源描述中,字符串显示在一个多行的叙述中,如下所示:
STRINGTABLE DISCARDABLE
BEGIN
IDS_STRING1, "character string 1"
IDS_STRING2, "character string 2"
其它字符串定义
END
如果您在替早期版本的Windows写程序,并在文字编辑器中手动建立这个字符串表(用Developer Studio来做这件事当然更容易得多了),您可以用左右大括号代替BEGIN和END叙述。
资源描述可以包含多个字符串表,但是每个ID必须唯一表示一个字符串。每个字符串占一行,最多4097个字符。\t可以作为制表符,\n则作为linefeed字符号。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_FILEREADONLY 3
资源描述文件具有此字符串表:
STRINGTABLE
BEGIN
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) ;
return MessageBox ( hwnd, szBuffer, szAppName,
MB_OK | MB_ICONEXCLAMATION) ;
}
为了显示包含「file not found」信息的消息框,程序呼叫:
OkMessage (hwnd, IDS_FILENOTFOUND, szFileName) ;
自订的资源
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
除了我们刚刚生成的BINTYPET资源型态外,这个叙述与ICONDEMO中的ICON叙述一样。有了图示后,您可以对资源名称使用文字的名称,而不是数字的标识符。
当您编译并连结程序,整个BINDATA.BIN文件会被并入MYPROG.EXE文件中。
在程序的初始化(比如,在处理WM_CREATE消息时)期间,您可以获得资源的句柄:
hResource = LoadResource (hInstance,
FindResource (hInstance, TEXT ("BINTYPE"),
MAKEINTRESOURCE (IDR_BINTYPE1))) ;
变量hResource定义为HGLOBAL型态,它是指向内存区块的句柄。不管它的名称是什么,LoadResource不会立即将资源加载内存。把LoadResource和FindResource函数如上例般合在一起使用,在实质上就类似于LoadIcon和LoadCursor函数的做法。事实上,LoadIcon和LoadCursor函数就用到了LoadResource和FindResource函数。
当您需要存取文字时,呼叫LockResource:
pData = LockResource (hResource) ;
LockResource将资源加载内存(如果还没有加载的话),然后它会传回一个指向资源的指标。当结束对资源的使用时,您可以从内存中释放它:
FreeResource (hResource) ;
当您的程序终止时,也会释放资源,即使您没有呼叫FreeResource.。
让我们看一个使用三种资源-一个图标、一个字符串表和一个自订的资源-的范例程序。程序10-3所示的POEPOEM程序在其显示区域显示Edgar Allan Poe的「Annabel Lee」文字。自订的资源是文件POEPOEM.TXT,它包含了一段诗文,此文本文件以反斜线(\)结束。
程序10-3 POEPOEM
POEPOEM.C
/*---------------------------------------------------------------------------
POEPOEM.C -- Demonstrates Custom Resource
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -