如何从PreTranslateMessage中的WM_KEYDOWN中提取字符(MSG * pMsg)

时间:2017-06-20 17:58:54

标签: windows winapi mfc keyboard-events scancodes

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

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

WM_KEYDOWN中的字段已记录here。虚拟键VK_值位于pMsg->wParam中,pMsg->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即将生成的字符?

3 个答案:

答案 0 :(得分:4)

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

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...
}

即使帮助页面似乎暗示keyboardStateToAscii()的调用是可选的,但我发现需要使用我试图检测到的字符。

答案 1 :(得分: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等其他键的组合。

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

然后DispatchMessage将调度原始的WM_KEYDOWN消息。

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

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

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

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