📄 10. 菜单及其它资源.txt
字号:
rect.top += cyChar * (1 - iPosition) ;
DrawTextA (hdc, pText, -1, &rect, DT_EXTERNALLEADING) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
FreeResource (hResource) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
POEPOEM.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// TEXT
ANNABELLEE TEXT DISCARDABLE "poepoem.txt"
/////////////////////////////////////////////////////////////////////////////
// Icon
POEPOEM ICON DISCARDABLE "poepoem.ico"
/////////////////////////////////////////////////////////////////////////////
// String Table
STRINGTABLE DISCARDABLE
BEGIN
IDS_APPNAME "PoePoem"
IDS_CAPTION """Annabel Lee"" by Edgar Allan Poe"
IDS_ERRMSG "This program requires Windows NT!"
END
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by PoePoem.rc
#define IDS_APPNAME 1
#define IDS_CAPTION 2
#define IDS_ERRMSG 3
POEPOEM.TXT
It 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 sea
Can 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 side
Of 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_CAPTIONPOEPOEM字符串由LoadString加载内存:
LoadString (hInstance, IDS_APPNAME, szAppName, sizeof (szAppName) /
sizeof (TCHAR)) ;
LoadString (hInstance, IDS_CAPTION, szCaption, sizeof (szCaption) /
sizeof (TCHAR)) ;
注意RegisterClass前面的两个呼叫。如果您在Windows 98下执行Unicode版本的POEPOEM,这两个呼叫就都会失败。因此,LoadStringA比LoadStringW要复杂得多(LoadStringA必须将资源字符串由Unicode转化为ANSI,而LoadStringW仅是直接加载它),LoadStringW在Windows 98下不被支持。这意味着在Windows 98下,当RegisterClassW函数失败时,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」这个名字-我想,这会是一项困难得多的工作。
菜单
您还记得Monty Python有关奶酪店的幽默短剧吗?那故事内容是这样的:一个客人走进奶酪店想买某种奶酪。当然,店里没有这种奶酪。因此他又问有没有另一种奶酪,然后再问另一种,再问另一种,不断的问店家有没有另一种奶酪(最后总共问了40种的奶酪),回答仍然是没有,没有,没有,没有,没有。
这个不幸的事件可以通过菜单的使用来避免。一个菜单是一列可用的选项,它告诉饥饿的用餐者,厨房可以提供哪些服务,并且-对于Windows程序来说-还告诉使用者一个应用程序能够执行哪些操作。
菜单可能是Windows程序提供的一致使用者接口中最重要的部分,而在您的程序中增加菜单,是Windows程序设计中相对简单的部分。您在Developer Studio中定义菜单。每个可选的菜单项被赋予唯一的ID。您在窗口类别结构中指定菜单名称。当使用者选择一个菜单项时,Windows给您的程序发送包含该ID的WM_COMMAND消息。
讨论完菜单后,我还将讨论键盘快捷键,它们是一些键的组合,主要用于启动菜单功能。
菜单概念
窗口的菜单列紧接在标题列的下方显示,这个菜单列有时被称为「主菜单」或「顶层菜单」。列在顶层菜单的项目通常是下拉式菜单,也叫做「弹出式菜单」或「子菜单」。您也可以定义多重嵌套的弹出式菜单,也就是说,在弹出式菜单上的项目可以存取另一个弹出式菜单。有时弹出式菜单上的项目呼叫对话框以获得更多的信息(对话框在下一章介绍)。在标题列的最左端,很多父窗口都显示程序的小图标,这个图标可以启动系统菜单。它实际上是另一个弹出式菜单。
弹出式菜单的各项可以是「被选中的」,这意味着Windows在菜单文字的左端显示一个小的选中标记,选中标记让使用者知道从菜单中选中了哪些选项。这些选项之间可以是互斥的,也可以不互斥。顶层菜单项不能被选中。
顶层菜单或弹出式菜单项可以被「启用」、「禁用」或「无效化」。「启动」和「不启动」有时候被当作「启用」和「禁用」的同义词。被启用或禁用的菜单项在使用者看来是一样的,但是无效化的菜单项是使用灰色文字来显示的。
从使用者的角度来看,启用、禁用和无效化的菜单项都是可以「选择的」(被选择的菜单项目会被加高亮度显示),也就是说,使用者可以使用鼠标选择被禁用的菜单项,将反相显示光标列移动到禁用的菜单项上,或者使用菜单项的关键词母来选择该菜单项。然而,从程序写作者的角度来看,启用、禁用和无效化菜单项的功能是不同的。Windows只为启用的菜单项向程序发送WM_COMMAND消息。要让选项变得无效,可以把那些菜单项禁用和无效化。如果您想让使用者知道选择是无效的,那么您可以让一个菜单项无效化。
菜单结构
当您建立或改变程序中的菜单时,把顶层菜单和每一个弹出式菜单想象成各自独立的菜单是有用的。顶层菜单有一个菜单句柄,在顶层菜单中的每一个弹出式菜单也有它自己的菜单句柄。系统菜单(也是一个弹出式菜单)也有菜单句柄。
菜单中的每一项都有三个特性。第一个特性是菜单中显示什么,它可以是字符串或位图。第二个特性是WM_COMMAND消息中Windows发送给程序的菜单ID,或者是在使用者选择菜单项时Windows显示的弹出式菜单的句柄。第三个特性是菜单项的属性,包括是否被禁用、无效化或被选中。
定义菜单
要使用Developer Studio来给程序资源描述文件添加菜单,可以从Insert菜单中选择 Resource并选择Menu(或者您可能已经知道了)。然后,您可以用交谈式的方式定义菜单。菜单中每一项都有一个相关的 Menu Item Properties对话框,指出该项目的字符串。如果选中了Pop-up复选框,该项目就会呼叫一个弹出式菜单,并且没有ID与此项目相联系。如果没有选中 Pop-up复选框,该项目被选中时就会产生带有特定ID的WM_COMMAND消息。这两类菜单项分别出现在资源描述档的POPUP和MENUITEM叙述中。
当您为菜单中的项目键入文字时,可以键入一个「&」符号,指出后面一个字符在Windows显示菜单时要加底线。这种底线字符是在您使用Alt键选择菜单项时Windows要寻找的比对字符。如果在文字中不包括「&」符号,就不显示任何底线,Windows会将菜单项文字的第一个字母用于Alt键查找。
如果在Menu Items Properties对话框中选中Grayed选项,则菜单项是不能启动的,它的文字是灰色的,该项不产生WM_COMMAND消息。如果选中 Inactive选项,则菜单项也是不能启动的,也不产生WM_COMMAND消息,但是它的文字显示正常。 Checked选项在菜单项边上放置一个选中标记。Separator选项在弹出式菜单上产生一个分栏的横线。
在弹出式菜单的项目上,可以在字符串中使用制表符\t。紧接着\t的文字被放置在距离弹出式菜单的第一列右边新的一列上。在本章后面,会看到在使用键盘快捷键时它起的作用。字符串中的\a使跟着它的文字向右对齐。
您指定的ID值是Windows发送给窗口消息处理程序中菜单消息中的数值。在菜单中ID值应该是唯一的。按照惯例,我使用以IDM(「ID for a Menu」)开头的标识符。
在程序中引用菜单
大多数Windows应用程序在资源描述文件中只有一个菜单。您可以给菜单起一个与程序名称相同的文字的名称。程序写作者经常将程序名用于菜单名称,以便相同的字符串可以用于窗口类别、程序的图标名称和菜单名称。然后,程序在窗口的定义中为菜单引用该名称:
wndclass.lpszMenuName = szAppName ;
虽然存取菜单资源的最常用方法是在窗口类别中指定菜单,您也可以使用其它方法。Windows应用程序可以使用LoadMenu函数将菜单资源加载内存中,如同LoadIcon和LoadCursor函数一样。LoadMenu传回一个菜单句柄。如果您在资源描述档中为菜单使用了名称,叙述如下:
hMenu = LoadMenu (hInstance, TEXT ("MyMenu")) ;
如果使用了数值,那么LoadMenu呼叫采用如下的形式:
hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (ID_MENU)) ;
然后,您可以将这个菜单句柄作为CreateWindow的第九个参数:
hwnd = CreateWindow ( TEXT ("MyClass"), TEXT ("Window Caption"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenu, hInstance, NULL) ;
在这种情况下,CreateWindow呼叫中指定的菜单可以覆盖窗口类别中指定的任何菜单。如果CreateWindow的第九个参数是NULL,那么您可以把窗口类别中的菜单看作是这种窗口类别的窗口内定使用的菜单。这样,您可以为依据同一窗口类别建立的几个窗口使用不同的菜单。
您也可以在窗口类别中指定NULL菜单,并且在CreateWindow呼叫中也指定NULL菜单,然后在窗口被建立后再给窗口指定一个菜单:
SetMenu (hwnd, hMenu) ;
这种形式使您可以动态地修改窗口的菜单。在本章后面的NOPOPUPS程序中我们将会看到这方面的例子。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -