【问题标题】:How to extract the character from WM_KEYDOWN in PreTranslateMessage(MSG*pMsg)如何从 PreTranslateMessage(MSG*pMsg) 中的 WM_KEYDOWN 中提取字符
【发布时间】:2017-06-20 17:58:54
【问题描述】:

在继承自 CViewPreTranslateMessage(MSG *pMsg) 内的 MFC 应用程序中,我有这个:

if (pMsg->message == WM_KEYDOWN) ...

WM_KEYDOWN 中的字段记录在 here 中。虚拟键VK_的值在pMsg->wParampMsg->lParam中包含几个字段,其中16-23位是键盘扫描码。

所以在我的代码中我使用:

const int virtualKey = pMsg->wParam;
const int hardwareScanCode = (pMsg->lParam >> 16) & 0x00ff; // bits 16-23

例如,在我的非美国键盘上,当我按下“#”字符时,我得到以下信息:

virtualKey == 0xde --> VK_OEM_7 "Used for miscellaneous characters; it can vary by keyboard."
hardwareScanCode == 0x29 (41 decimal)

我想“捕获”或以不同方式处理的字符是 ASCII“#”,0x23(十进制 35)。

我的问题

无论语言或键盘布局如何,如何翻译WM_KEYDOWN 信息以获得可以比较的内容?我需要识别# 键,用户是使用标准美式键盘还是其他键盘。

例如,我一直在研究以下函数,例如:

MapVirtualKey(virtualkey, MAPVK_VSC_TO_VK);
// previous line is useless, the key VK_OEM_7 doesn't map to anything without the scan code

ToAscii(virtualKey, hardwareScanCode, nullptr, &word, 0);
// previous line returns zero, and zero is written to `word`

编辑:

长话短说:在美国键盘上,SHIFT+3 = #,而在法语键盘上 SHIFT+3 = /。所以我不想看单独的键,而是想了解角色。

在处理 WM_KEYDOWN 时,如何翻译 lParam 和 wParam(“键”)以找出键盘和 Windows 将要生成的字符?

【问题讨论】:

  • 这很令人困惑,因为这个问题将 keyscharacters 混为一谈,所以我不太明白你在追求哪个。
  • 我明白你为什么感到困惑了。在法语-加拿大键盘上,有一个专门用于“#”字符的键。就像'A'字符有一个键等等。所以键和字符是一回事。我忘记了在不是这种情况的美国键盘上。
  • 如果您想知道字符,您需要观看 WM_CHAR(或相关)消息。但我不认为那些经过预翻译。相反,它们是由 TranslateMessage 生成的。
  • 我需要在预翻译中捕获它,以便我可以使用某些键盘事件并防止它们到达底层窗口。
  • 如果它是一个特定的窗口(或窗口类),您可以对窗口进行子类化以拦截 WM_CHAR 并传递其他所有内容。

标签: windows winapi mfc keyboard-events scancodes


【解决方案1】:

我相信这是一个更好的解决方案。此键盘已使用标准美国键盘布局和加拿大-法语键盘布局进行了测试。

const int wParam = pMsg->wParam;
const int lParam = pMsg->lParam;
const int keyboardScanCode = (lParam >> 16) & 0x00ff;
const int virtualKey = wParam;

BYTE keyboardState[256];
GetKeyboardState(keyboardState);

WORD ascii = 0;
const int len = ToAscii(virtualKey, keyboardScanCode, keyboardState, &ascii, 0);
if (len == 1 && ascii == '#')
{
    // ...etc...
}

尽管帮助页面似乎暗示 keyboardState 对于调用 ToAscii() 是可选的,但我发现它是我试图检测的字符所必需的。

【讨论】:

  • 这实际上看起来像一个理智的解决方案。我从来不需要打电话给ToAscii,但这似乎是一个完美的用例。我想不出这种测试会失败并产生假阳性或假阴性的情况。
  • 对于检测'#',这就足够了。如果您想检测任意字符,它对于某些极端情况缺乏通用性。您可以使用 ToUnicode,但即使这样也带有警告:“提供给 ToUnicode 函数的参数可能不足以转换虚拟键代码......”
【解决方案2】:

找到了我需要的神奇 API 调用:GetKeyNameText()

if (pMsg->message == WM_KEYDOWN)
{
    char buffer[20];
    const int len = GetKeyNameTextA(pMsg->lParam, buffer, sizeof(buffer));
    if (len == 1 && buffer[0] == '#')
    {
        // ...etc...
    }
}

不,该代码仅适用于具有显式“#”键的键盘布局。不适用于像标准美国布局这样的布局,其中“#”是 SHIFT + 3 等其他键的组合。

【讨论】:

  • “我需要确定# 键用户是使用标准美式键盘还是其他键盘。”但是美国键盘没有# 键。您可以通过 Shift 和 3 键的组合键入 # 字符,但此代码不会告诉您。
  • @AdrianMcCarthy 这令人担忧。我没有美式键盘布局,只有法裔加拿大人,其中“#”本身就是一个键。我将不得不获得一个“标准”美国键盘,这样我才能找到一个无论键盘类型如何都能工作的解决方案。
  • 您不必使用物理键盘来进行测试。只需安装美式英语键盘layout
【解决方案3】:

我不是 MFC 专家,但大致我认为它的消息循环如下所示:

while (::GetMessage(&msg, NULL, 0, 0) > 0) {
    if (!app->PreTranslateMessage(&msg)) {    // the hook you want to use
        TranslateMessage(&msg);  // where WM_CHAR messages are generated
        DispatchMessage(&msg);  // where the original message is dispatched
    }
}

假设美国用户(3# 在同一个键上)按下该键。

PreTranslateMessage 挂钩将看到 WM_KEYDOWN 消息。

如果它允许消息通过,则 TranslateMessage 将生成 WM_CHAR 消息(或该消息系列中的某些内容)并直接发送它。 PreTranslateMessage 永远不会看到 WM_CHAR。

WM_CHAR 是 '3' 还是 '#' 取决于键盘状态,特别是当前是否按下了 Shift 键。但是 WM_KEYDOWN 消息不包含所有键盘状态。 TranslateMessage 通过记录通过它的键盘消息来跟踪状态,因此它知道 Shift(或 Ctrl 或 Alt)是否已经按下。

然后 DispatchMessage 将分发原始的 WM_KEYDOWN 消息。

如果您只想捕获 '#' 而不是 '3's,那么您有两种选择:

  1. 让您的 PreTranslateMessage 挂钩跟踪所有键盘状态(就像 TranslateMessage 通常会做的那样)。它必须监视所有键盘消息以跟踪键盘状态并将其与键盘布局结合使用来确定当前消息是否会正常生成'#'。然后,您必须手动发送 WM_KEYDOWN 消息并返回 TRUE(这样就不会发生正常的翻译/发送)。您还必须小心过滤相应的 WM_KEYUP 消息,以免混淆 TranslateMessage 的内部状态。这需要大量的工作和大量的测试。

  2. 找一个地方拦截 TranslateMessage 生成的 WM_CHAR 消息。

对于第二个选项,您可以对目标窗口进行子类化,让它在字符为 '#' 时拦截 WM_CHAR 消息并传递其他所有内容。这似乎要简单得多,而且目标明确。

【讨论】:

  • 系统已经为您跟踪键盘状态。如果您需要了解(同步)密钥状态,请调用GetKeyState API。无需通过自己跟踪键盘状态来使事情进一步复杂化。
  • 我仍然认为选项 2 工作量更少,更容易测试。
  • 我没有评论这些方法的相对难度。
  • 我在选项 1 上很冗长,以强调涉及多少工作以及如何——即使操作系统提供了一些 API 来帮助——这实际上涉及复制操作系统已经完成的一些工作。我只是想确保人们不会阅读有关 GetKeyState 的内容,并假设它简化了选项 1,使其成为选项 2 的合理替代方案。
  • 为什么不使用 ToAscii() 来获取我昨天在解决方案中写的字符并避免选项 1 和 2?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-20
  • 2018-05-31
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 2012-03-03
  • 1970-01-01
相关资源
最近更新 更多