📄 见招拆招《windows程序设计》(九) .txt
字号:
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程序中我们将会看到这方面的例子。
当窗口被清除时,与窗口相关的所有菜单都将被清除。与窗口不相关的菜单在程序结束前通过呼叫DestroyMenu主动清除。
菜单和消息
当使用者选择一个菜单项时,Windows通常向窗口消息处理程序发送几个不同的消息。在大多数情况下, 您的程序可以忽略大部分消息,只需把它们传递给DefWindowProc即可。WM_INITMENU就是这一类的消息,它具有下列参数:
wParam: 主菜单句柄
lParam: 0
wParam值是您的主菜单句柄,即使使用者选择的是系统菜单中的项目。Windows程序通常忽略WM_INITMENU消息。尽管在选中该项之前的消息已经给程序提供了修改菜单的机会,但是我们觉得此刻改变顶层菜单是会扰乱使用者的。
程序也会接收到WM_MENUSELECT消息。随着使用者在菜单项中移动光标或者鼠标,程序会收到许多WM_MENUSELECT消息。这对实作那些包含对菜单项的文字描述的状态列是很有帮助的。WM_MENUSELECT的参数如下所示:
LOWORD (wParam):被选中项目:菜单ID或者弹出式菜单句柄
HIWORD (wParam):选择旗标
lParam: 包含被选中项目的菜单句柄
WM_MENUSELECT是一个菜单追踪消息,wParam的值告诉您目前选择的是菜单中的哪一项(加高亮度显示的那个),wParam的高字组中的「选择旗标」可以是下列这些旗标的组合:MF_GRAYED、MF_DISABLED、MF_CHECKED、MF_BITMAP、MF_POPUP、MF_HELP、MF_SYSMENU和MF_MOUSESELECT。如果您需要根据对菜单项的选择来改变窗口显示区域的内容,那么您可以使用WM_MENUSELECT消息。许多程序把该消息发送给DefWindowProc。
当Windows准备显示一个弹出式菜单时,它给窗口消息处理程序发送一个WM_INITMENUPOPUP消息,参数如下:
wParam: 弹出式菜单句柄
LOWORD (lParam):弹出式菜单索引
HIWORD (lParam): 系统菜单为1,其它为0
如果您需要在显示弹出式菜单之前启用或者禁用菜单项,那么这个消息就很重要。例如,假定程序使用弹出式菜单上的 Paste命令从剪贴簿复制文字,当您收到弹出式菜单中的WM_INITMENUPOPUP消息时,应确定剪贴簿内是否有文字存在。如果没有,那么应该使 Paste菜单项无效化。我们将在本章后面修改的POPPAD程序中看到这样的例子。
最重要的菜单消息是WM_COMMAND,它表示使用者已经从菜单中选中了一个被启用的菜单项。第八章中的WM_COMMAND消息也可以由子窗口控件产生。如果您碰巧为菜单和子窗口控件使用同一ID码,那么您可以通过lParam的值来区别它们,菜单项的lParam其值为0,请参见表10-1。
表10-1
菜单
控件
LOWORD (wParam): 菜单ID 控件ID
HIWORD (wParam): 0 通知码
lParam: 0 子窗口句柄
WM_SYSCOMMAND消息类似于WM_COMMAND消息,只是WM_SYSCOMMAND表示使用者从系统菜单中选择一个启用的菜单项:
wParam: 菜单ID
lParam: 0
然而,如果WM_SYSCOMMAND消息是由按鼠标按键产生的,LOWORD(lParam)和HIWORD(lParam)将包含鼠标光标位置的x和y屏幕坐标。
对于WM_SYSCOMMAND,菜单ID指示系统菜单中的哪一项被选中。对于预先定义的系统菜单项,较低的那四个位应该和0xFFF0进行AND运算来屏蔽掉,结果值应该为下列之一:SC_SIZE、SC_MOVE、SC_MINIMIZE、SC_MAXIMIZE、SC_NEXTWINDOW、SC_PREVWINDOW、SC_CLOSE、SC_VSCROLL、SC_HSCROLL、SC_ARRANGE、SC_RESTORE和SC_TASKLIST。此外,wParam可以是SC_MOUSEMENU或SC_KEYMENU。
如果您在系统菜单中添加菜单项,那么wParam的低字组将是您定义的菜单ID。为了避免与预先定义的菜单ID相冲突,应用程序应该使用小于0xF000的值,这对于将一般的WM_SYSCOMMAND消息发送给DefWindowProc是很重要的。如果您不这样做,那么您实际上就是禁用了正常的系统菜单命令。
我们将讨论的最后一个消息是WM_MENUCHAR。实际上,它根本不是菜单消息。在下列两种情况之一发生时,Windows会把这个消息发送到窗口消息处理程序:如果使用者按下Alt和一个与菜单项不匹配的字符时,或者在显示弹出式菜单而使用者按下一个与弹出式菜单里的项目不匹配的字符键时。随WM_MENUCHAR消息一起发送的参数如下所示:
LOWORD (wParam): 字符代码(ASCII或Unicode)
HIWORD (wParam): 选择码
lParam: 菜单句柄
选择码是:
0 不显示弹出式菜单
MF_POPUP 显示弹出式菜单
MF_SYSMENU 显示系统弹出式菜单
Windows程序通常把该消息传递给DefWindowProc,它一般给Windows传回0,这会使Windows发出哔声。在后面的一期中会看到WM_MENUCHAR消息的使用。
范例程序
让我们来看一个简单的例子。程序10-4所示的MENUDEMO程序,在主菜单中有五个选择项-File、Edit、Background、Timer和Help,每一项都与一个弹出式菜单相连。MENUDEMO只完成了最简单、最通用的菜单处理操作,包括拦截WM_COMMAND消息和检查wParam的低字组。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -