【问题标题】:How do you tell LSHIFT apart from RSHIFT in WM_KEYDOWN events?在 WM_KEYDOWN 事件中,您如何区分 LSHIFT 和 RSHIFT?
【发布时间】:2013-04-12 08:34:06
【问题描述】:

根据MSDN wparam 应该保存密钥代码。问题是,按shift的时候,code是16(VK_SHIFT),但是我需要区分VK_LSHIFT和VK_RSHIFT。

对于 VK_CONTROL,似乎有一种解决方法:

if(wParam == VK_CONTROL) {
    if ( lParam&EXTENDED_KEYMASK )
        wParam = VK_RCONTROL;
    else
        wParam = VK_LCONTROL;
}

但是,这对 VK_SHIFT 不起作用:

if(wparam == VK_SHIFT) {
    if ( lParam&EXTENDED_KEYMASK )
        wParam = VK_RSHIFT;
    else
        wParam = VK_LSHIFT;
}

在后一个例子中,它总是假设 LSHIFT。

【问题讨论】:

  • 已经有一段时间了,但据我所知,旧系统无法区分左移或右移键。
  • 只有 GetKeyState()GetAsyncKeyState() 会区分左右按键(来自 WinUser.h)。

标签: c winapi


【解决方案1】:

要区分 Shift、Ctrl 或 Alt 键的左右版本,您必须使用 MapVirtualKey() 函数或与虚拟键消息一起传递的 lParam 中的“扩展键”位。以下函数将为您执行该转换 - 只需从消息中传递虚拟键码和 lParam,您将根据需要取回左/右特定虚拟键码:

WPARAM MapLeftRightKeys( WPARAM vk, LPARAM lParam)
{
    WPARAM new_vk = vk;
    UINT scancode = (lParam & 0x00ff0000) >> 16;
    int extended  = (lParam & 0x01000000) != 0;

    switch (vk) {
    case VK_SHIFT:
        new_vk = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
        break;
    case VK_CONTROL:
        new_vk = extended ? VK_RCONTROL : VK_LCONTROL;
        break;
    case VK_MENU:
        new_vk = extended ? VK_RMENU : VK_LMENU;
        break;
    default:
        // not a key we map from generic to left/right specialized
        //  just return it.
        new_vk = vk;
        break;    
    }

    return new_vk;
}

如果传入的虚拟键码不是映射到左/右版本的键码,则原始键码将原封不动地传回。所以当你需要区分左右变体时,只要通过函数运行WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUP消息参数即可。

通过使用MapVirtualKey(),您无需了解有关左移和右移扫描码是 0x2a 和 0x36 的知识 - API 会处理这些细节。如果它们确实发生了不同(并不是真的会发生这种情况),Windows 将负责处理它,而不是你。

因此,在您的 WM_KEYDOWN/WM_KEYUP/WM_SYSKEYDOWN/WM_SYSKEYUP 处理程序中,您只需添加一行代码,如下所示:

wparam = MapLeftRightKeys(wparam, lparam);

您的其余代码可以作用于特定于左/右的 VK 代码,就好像系统消息一开始就将它们提供给您一样。

【讨论】:

  • 谢谢您,先生!我已经对此进行了测试,并且看起来效果很好。它也比其他建议更清洁、更安全,所以我接受了这个。 :)
【解决方案2】:

你的问题背后有古老的历史。最初的 IBM PC 键盘没有正确的 Alt 和 Ctrl 键。它们后来被添加到扩展键盘布局中,键盘控制器将它们以 0xe0 前缀发送到扫描码,以将它们区分为扩展键。但原来的键盘布局总是有两个 shift 键,所以它们有自己的非扩展扫描码。这就是您的代码不起作用的原因。

大卫的回答是解决您问题的好方法。但是你实际上可以从消息中得到它,那些扫描码是由Windows logo requirements 固定下来的。在 lParam 中可用,左 shift 键的扫描码为 42,右 shift 键为 54。不幸的是,它们的 windows 标题中没有#define,这使它很难看。

【讨论】:

  • 感谢您的历史课。 :)
  • 如何获取扫描码? unsigned char scancode = (unsigned char) (lParam >> 16); 似乎不起作用,或者至少给出不同的数字。抱歉,我没有经常使用 WinAPI。
  • 扫描码存储在16-23位,所以是(lParam & 0xff0000) >> 16。
  • 实际上,我分别得到 42 和 54 作为 lshift 和 rshift 的扫描码。在网上快速检查也同意这些数字。你能更新你的答案吗?
  • ... 我看的越多,我得到的这些扫描码的矛盾值就越多。你确定它们是“一成不变的”吗?
【解决方案3】:

通过VK_LSHIFTVK_RSHIFT 调用GetKeyState

【讨论】:

  • 好主意,我显然更喜欢我的示例中的“hack”之类的东西,但如果没有类似的东西,这肯定会起作用。
  • 这是一种非常干净的方法。使用同步键状态 API。
  • 只要我们可以假设键状态总是在 WM_KEYDOWN 消息被分派之前更新就很清楚了。我之前不确定,但您似乎是对的,从我可以在 GetKeyState 文档中进一步阅读的内容来看。
  • 其实我只是想到了一个问题:如果你同时按住另一个shift键,就没有办法知道是哪个键被按下了。并不是说它可能会出现,但问题是存在的。
  • @cib:你什么意思?如果两个 shift 键都按下了,GetKeyState 可以告诉你两者都按下了。还是您想确定两个 Shift 键的按下顺序?
【解决方案4】:

“根据 MSDN”:

  • VK_LSHIFT
  • VK_RSHIFT
  • VK_LCONTROL
  • VK_RCONTROL
  • VK_LMENU
  • VK_RMENU

这些左右可区分的常量只能通过 GetKeyboardState、SetKeyboardState、GetAsyncKeyState、GetKeyState 和 MapVirtualKey 函数用于应用程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-09
    • 1970-01-01
    • 2019-10-13
    • 1970-01-01
    • 1970-01-01
    • 2017-06-15
    • 1970-01-01
    相关资源
    最近更新 更多