4

PreTranslateMessage(MSG *pMsg)从 a 继承的 MFC 应用程序中CView,我有这个:

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

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

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

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 将要生成的字符?

4

3 回答 3

7

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

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(),但我发现它是我试图检测的字符所必需的。

于 2017-06-20T23:13:02.437 回答
0

找到了我需要的神奇 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 等其他键的组合。

于 2017-06-20T19:23:26.067 回答
0

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

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

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

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

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

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

于 2017-06-20T23:28:28.080 回答