■为什么使用快捷键
你可能会问:为什么我应该使用快捷键?为什么我不能捕捉WM_KEYDOWN或 WM_CHAR消息并自己复制菜单功能?它有什么优点?对一个单窗口程序,你当然可以捕捉键盘消息。但是使用快捷键的一个简单优点是,你不需要重写菜单和快捷键处理逻辑。
对有多个窗口和窗口过程的应用程序,快捷键变得十分重要。正如我们所见,对拥有当前输入焦点的窗口,Windows会将键盘消息发送给它的窗口过程。然而,对快捷键而言,Windows会将WM_COMMAND消息发送给一个句柄在Windows函数 TranslateAccelerator中指定的窗口程序。通常,这会是你的主窗口,亦即拥有菜单的那个窗口,这意味着响应快捷键的逻辑不需要被复制到每一个窗口过程。
如果你使用非模态对话框(在第十章讨论)或在主窗口的客户区内使用子窗口,这个优 点就变得极其重要。如果给多个窗口定义了同一个快捷键,只需在其中一个窗口包含这个逻辑即可。子窗口不会接收到来自快捷键的WM_COMMAND消息。
■指定加速键的一些规则
理论上,你可以使用几乎任何虚拟键或字符键与Shift键、Ctrl键或Alt键的组合来定义快捷键。然而,你应该试图和其他应用程序保持一致,以避免干扰Windows对键盘的使用。你应该避免使用Tab、回车键、Esc和空格键作为快捷键,因为它们通常保留给系统功能。
快捷键最通常的用途是用于程序中Edit菜单的菜单项。对这些菜单项推荐使用的快捷键在Windows 3.0和Windows 3.1之间有变化,因此同时支持新旧两种快捷键很常见,如下表所示:
功能 |
旧快捷键 |
新快捷键 |
Undo(撤销) |
Alt+Backspace |
Ctrl+Z |
Cut(剪切) |
Shilft+Del |
Ctrl+X |
Copy(复制) |
Ctrl+Ins |
Ctrl+C |
Paste(粘贴) |
Shilft+Ins |
Ctrl+V |
Delete或Clear(删除) |
Del |
Del |
另一个很常见的快捷键是F1功能键,它用来激活帮助。应该避免使用F4、F5和F6 键,因为它们经常被多文档界面(Multiple Document Interface, MDI)程序用作特殊功能,我们会在第十八章中讨论MDI。
■加速键表
你可以在VS中定义快捷键表。为了方便在你的程序中加载快捷键表,可以使它与应用程序同名(这也适用于菜单和图标)。
每个快捷键有一个ID和一个击键组合,这些你可以在Accelerator Properties对话框中 定义。如果你已经定义了菜单,菜单ID会存在于组合框中,你不需要重新输入它们。
快捷键可以是虚拟键代码或ASCII字符与Shift、Ctrl或Alt键的组合。你可以在字母前加A来指定ASCII字符与Ctrl键的组合。你也可以从组合框中选择虚拟键代码。
在为菜单项定义快捷键时,应当在菜单文本中包含相应的键组合。Tab字符(\t)能将文本和快捷键分开,这样快捷键会排列在第二列。为了在菜单中标识快捷键,可以使用文字Ctrl、Shift或Alt,后面跟着一个加号和键名(例如Shift+F6或Ctrl+F6)。
■加载快捷键表
在应用程序中,应使用LoadAccelerators函数来把快捷键表加载到内存并获得它的句柄。LoadAccelerators 语句和 Loadlcon、LoadCursor 及 LoadMenu 语句很相似。
首先用类型HANDLE定义一个快捷键表的句柄:
HANDLE hAccel;
然后加载快捷键表:
hAccel = LoadAccelerators (hInstance, TEXT ("MyAccelerators")) ;
和图标、鼠标指针及菜单一样,你也可以用数字来命名加速键表,然后在LoadAccelerators 中通过MAKEINTRESOURCE宏来使用该数字,或者将数字用引号括起来并加上前缀#字符。
■翻译按键
我们现在要改动三行代码,这三行代码是本书中迄今为止对所有Windows程序都通用 的。这三行代码就是标准消息循环:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
下面显示了我们如何改变它来使用快捷键:
while (GetMessage (&msg, NULL, 0, 0))
{
if (!TranslateAccelerator (hwnd, hAccel, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
Translate Accelerator函数确定保存在msg消息结构中的消息是否是键盘消息。如果是,该函数在快捷键表中寻找句柄为hAccel的匹配值。如果找到匹配值,它会调用句柄为hwnd 的窗口过程。如果快捷键ID对应系统菜单的一个菜单项,则相应消息为 WM_SYSCOMMAND;否则,消息是 WM_COMMAND。
当TranslateAccelerator返回时,如果消息被翻译过(并且已被发送给窗口过程),则返回值为非零值,否则返回值为零。如果TranslateAccelerator返回非零值,你就不应该再调用 TranslateMessage 和 DispatchMessage,而是应该返回 GetMessage 循环。
TranslateAccelerator中的hwnd参数看起来不太合适,因为消息循环中的三个函数都不需要它。并且,消息结构自身(结构变量msg)有一个名字为hwnd的成员,它也是一个窗口句柄。
这个函数之所以有些不同的原因是:msg结构的字段是由GetMessage调用来填充的。 当GetMessage的第二个参数是NULL时,它检索属于该应用程序的所有窗口的消息。当 GetMessage返回时,msg结构的hwnd成员是将会得到该消息的窗口句柄。然而,当 TranslateAccelerator 将键盘消息翻译成 WM_COMMAND 或 WM_SYSCOMMAND 消息时, 它将msg.hwnd窗口句柄替换成该函数第一个参数所指定的窗口句柄。于是Windows会将所有快捷键消息发送给同一个窗口,即使程序中另外一个窗口当前拥有输入焦点。当模态对话框或消息框拥有输入焦点时,TranslateAccelerator不翻译键盘消息,因为这些窗口的消息不通过程序的消息循环。
某些情况下,当应用程序中另一个窗口(比如非模态对话框)拥有输入焦点时,你并不想翻译键盘消息。如何处理这种情况将在第十一章介绍。
■接收加速键消息
当一个快捷键对应系统菜单的一个菜单项时,TranslateAccelerator会向窗口过程发送一条WM_SYSCOMMAND消息;否则,TranslateAccelerator向窗口过程发送一条 WM_COMMAND消息。下表列出了对于快捷键、菜单命令和子窗口控件你会接收到的WM_COMMAND消息的类型:
快捷键 |
菜单 |
控件 |
|
LOWORD(wParam) |
快捷键ID |
菜单ID |
控件ID |
HIWORD(wParam) |
1 |
0 |
通知码 |
lParam |
0 |
0 |
子窗口句柄 |
如果快捷键对应某个菜单项,那么窗口过程还会接收到WM_INITMENU,M_INITMENUPOPUP和WM_MENUSELECT消息,就像菜单项被选择了一样。在处理 WM_INITMENUPOPUP时,程序通常可以启用或禁用弹出菜单的菜单项。使用快捷键时,你仍然拥有这种机制。当一个快捷键对应一个禁用或变灰的菜单项时, TranslateAccelerator 不会向窗口过程发送 WM_COMMAND 或 WM_SYSCOMMAND 消息。
如果当前窗口被最小化,对于映射到启用的系统菜单项的快捷键, TranslateAccelerator 将向窗口过程发送 WM_S YSCOMMAND 消息而不是 WM_COMMAND 消息。对于没有映射到任何菜单项的快捷键,TranslateAccelerator也会向窗口过程发送 WM_COMMAND 消息。
9.3.2 第62练:带有菜单和快捷键的简单记事本程序
/*------------------------------------------------------------------------
062 WIN32 API 每日一练
第62个例子POPPAD2.C:带有菜单和加速键的简单记事本程序
TranslateAccelerator函数
LoadAccelerators函数
自定义函数AskConfirmation函数
WM_INITMENUPOPUP消息
EnableMenuItem函数
IsClipboardFormatAvailable 函数
WM_QUERYENDSESSION消息
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
#