65.9K
CodeProject 正在变化。 阅读更多。
Home

将加速键提取为人类可读的文本

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (15投票s)

2004年8月24日

BSD

5分钟阅读

viewsIcon

85195

downloadIcon

849

本文介绍了如何提取加速键并将其转换为人类可读的文本,使用了当前用户的语言设置。

Sample Image - getacceltext.png

引言

我最近遇到了在自定义绘制菜单中绘制加速键的问题。在这篇简短的文章中,我将介绍我解决这个问题的方法。

加速键和菜单

加速键本质上是键盘快捷键,可以映射到应用程序中的不同功能。加速键映射到的功能是控件 ID。与按钮、组合框、列表框以及菜单项相同的 ID。通过这种方式,可以使不同但功能相似的 GUI 对象表示同一个函数。

从生理学角度来看,使用菜单然后使用鼠标是一个坏主意,因此,我们必须让用户可以选择最小化鼠标的使用。这就是为什么在菜单中显示加速键是一个好主意;它告知用户存在快捷键,并且不一定非要使用鼠标。

加速键使用的极致是使用户可以自定义所有按键组合。但这超出了本文的范围。

加速键表

加速键表基本上是一个由三列组成的表,正如此结构定义所示

typedef struct tagACCEL {
    BYTE fVirt;
    WORD key;
    WORD cmd;
} ACCEL, *LPACCEL;

fVirt 是一个位组,其中包含按键组合是否包含 Ctrl、Alt 或 Shift。它还指示 key 是虚拟键码还是 ASCII 码。cmd 成员是此加速键关联的 ID。

加速键通常存在于二进制文件的资源部分。在 MFC 应用程序中,您甚至很少看到它们。MFC 和 WTL 允许加速键表和主框架窗口共享同一个 ID。当主框架创建时,它会自动使用自身相同的 ID 加载加速键表。要手动加载加速键,您需要使用 Win32 函数

HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableName)

hInstance 通常是您可执行文件的实例句柄,但也可能是您动态加载的 DLL 的句柄。lpTableName 是资源名称。由于在典型的 MFC 项目中,您不会有任何名称可用,只有整数资源 ID,因此您需要使用宏 MAKEINTRESOURCE()。该函数的返回值是加速键的句柄。您无法以文档记录的方式使用此句柄,而只能使用 Win32 提供的函数。

要访问该表本身,您需要将其内容复制到您提供的缓冲区中。该函数

int CopyAcceleratorTable(      
    HACCEL  hAccelSrc,
    LPACCEL lpAccelDst,
    int     cAccelEntries
);

将为您完成此操作。为了分配保存整个表的缓冲区,您必须首先确定表的行数。这可以通过调用 CopyAcceleratorTable(hAccelSrc, 0, 0) 来完成。然后,该函数将返回表的行数。然后分配缓冲区,再次调用该函数,但这次传递您的缓冲区地址及其表项中的长度。

这样,我们现在就掌握了如何从资源中提取加速键数据的知识。

虚拟键码、扫描码和名称

键使用虚拟键码或扫描码来识别。扫描码是一种低级代码,用于标识键盘上的键,精确到可以告诉您它在键盘上的确切位置。如果您按下数字键盘上的 insert 键,扫描码将表示该键,而不是箭头键上方的 insert 键。相比之下,虚拟键码并不总是区分这一点。由于这个事实,某些虚拟键码会转换为多个扫描码。当将键转换为文本时,这会成为一个小问题。

我们进行此练习的原因是,我们想将加速键表中的虚拟键码转换为人类可读的文本。我们也希望转换能够符合用户的键盘语言设置。在浏览 MSDN 文档后,您会发现没有虚拟键码到文本的函数可以帮助您完成这项任务。但是,有一个扫描码到文本的函数

int GetKeyNameText(      
    LONG   lParam,
    LPTSTR lpString,
    int    nSize
);

这会将原始问题转换为将虚拟键码映射到扫描码的问题。如前所述,这是一个问题。虚拟键码到扫描码的转换并非总是明确的。如果您调用该函数

UINT MapVirtualKey(      
    UINT uCode,
    UINT uMapType
);

insert 的虚拟键码映射到扫描码,然后使用扫描码与 GetKeyNameText() ,您将获得文本“NUM INS”。显然,这并非您希望在菜单中显示的内容。“NUM”部分会混淆用户,而且可以说是一种谎言,因为任何 insert 键都可以。

这个问题可以追溯到 AT 兼容键盘推出之时。较旧的 XT 兼容键盘在字母数字键盘和数字键盘之间没有键(并且只有 10 个功能键,但这对我们来说没问题)。如果您仔细查看键盘,您会发现在数字键盘上的键布局与“扩展”部分的键盘布局相同。为了区分这两组之间的扫描码,在扫描码中添加了一个 *扩展位*。

扩展位(28,256d,100h)将用于键盘扩展部分上的那些键。如果将此位与 GetKeyNameText() 一起使用,将从文本中删除任何“NUM”,一切看起来都很棒。

因此,要将加速键转换为非混淆的人类可读格式,您可以这样做

// pseudo code - see source code for real code
String s;

if(accel[i].fVirt & FALT)
   s += GetKeyNameText(MapVirtualKey(VK_MENU));
   
if(accel[i].fVirt & FCONTROL) {
   if(s) s += "+";
   s += GetKeyNameText(MapVirtualKey(VK_CONTROL));
}

if(accel[i].fVirt & FSHIFT) {
   if(s) s += "+";
   s += GetKeyNameText(MapVirtualKey(VK_SHIFT));
}
   
if(accel[i].fVirt & FVIRTKEY) {
   scancode = MapVirtualKey(accel[i].key);
   switch(accel[i].key) {
      case VK_INSERT:
      case VK_DELETE:
      case VK_HOME:
      case VK_END:
      case VK_NEXT:  // Page down
      case VK_PRIOR: // Page up
      case VK_LEFT:
      case VK_RIGHT:
      case VK_UP:
      case VK_DOWN:
         scancode |= 0x100; // Add extended bit
   }
   s += GetKeyNameText(scancode);
} else { // ASCII key code
   s += (char)accel[i].key;
}

// s is now Shift+Ctrl+Ins for instance

源代码

本文附带的源代码包含三个函数

  • bool GetAcceleratorTexts(HACCEL hAccel, std::map<UINT, CString>& mapId2AccelText);
  • bool GetAcceleratorTexts(HINSTANCE hInst, LPCTSTR lpszAccelRes, std::map<UINT, CString>& mapId2AccelText);
  • bool GetAcceleratorTexts(HINSTANCE hInst, int nId, std::map<UINT, CString>& mapId2AccelText);

如您所见,它依赖于 CString,因此您需要 MFC、ATL 或 WTL 才能使用此代码。只需稍作修改,您就可以使其与 std::basic_string<> 一起正常工作。所有三个函数都做同样的事情,只是接受不同的输入。

第三个参数 mapId2AccelText 将在函数成功返回时保存文本。顾名思义,它将 ID 与每个加速键文本进行映射。

© . All rights reserved.