如何使用LowLevelKeyboardHook钩取Win + Tab键。

10
简而言之:按下 Win + Tab 后阻止释放 Win 键,这会让 Windows 认为 Win 仍然被按下,所以例如松开 Win 键时按下 S 将打开搜索面板而不仅仅输入 "s"……直到用户再次按下 Win。如果不阻止它,则将显示 Windows 开始菜单。我很为难!
我没有问题去监听使用 Alt + Tab 的快捷键,可以使用 LowLevelKeyboardHook 或者 Win + 某个未绑定的键 快捷键,可以使用 RegisterHotKey。问题只出现在使用 LowLevelKeyboardHook 监听 Win 键时。在下面的示例中,当检测到 Win + Tab 组合键时,我接管了 Win 键的弹起事件。这导致每个后续按键的行为都像 Win 键仍然被按下一样。
        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode != HC_ACTION)
                return CallNextHookEx(_hookID, nCode, wParam, lParam);

            var keyInfo = (Kbdllhookstruct)Marshal.PtrToStructure(lParam, typeof(Kbdllhookstruct));

            if (keyInfo.VkCode == VK_LWIN)
            {
                if (wParam == (IntPtr)WM_KEYDOWN) {
                    _isWinDown = true;
                } else {
                    _isWinDown = false;

                    if (_isWinTabDetected) {
                        _isWinTabDetected = false;
                        return (IntPtr)1;
                    }
                }
            }
            else if (keyInfo.VkCode == VK_TAB && _isWinDown) {
                _isWinTabDetected = true;

                if (wParam == (IntPtr)WM_KEYDOWN) {
                    return (IntPtr)1;
                } else {
                    _isWinTabDetected = true;
                    Console.WriteLine("WIN + TAB Pressed");
                    return (IntPtr)1;
                }
            }

            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
}

请注意,完整的代码可以在此处找到(请注意,它应该替换空WinForms项目中的Program.cs才能运行):https://gist.github.com/christianrondeau/bdd03a3dc32a7a718d62 - 按下 Win + Tab,每次按快捷键时Form标题都会更新。

请注意,钩入此特定组合键的意图是提供一个Alt + Tab的替代方式,而不是要替换 Alt + Tab 本身。如果有回答提供使用 Win + Tab 启动自定义代码的能力,则也将被接受。

以下是我的一些想法,但我无法找到文档。所有这些想法都潜在地可以成功地回答我的问题。

  • 告诉 Windows “取消” Win 的抬起事件而不实际触发它
  • 防止 Windows 启动开始菜单一次
  • 直接钩入Windows的Win+事件,而不是手动钩入按键(如果存在的话,这将是我首选)
2个回答

4
系统需要知道您释放了Windows键。 我检查了我的钩子和您的钩子之间的区别,唯一的区别是这行代码:

if (_isWinTabDetected) {
    _isWinTabDetected = false;
     return (IntPtr)1; //THIS LINE 
}

这个解决方案行不通,因为当 Windows 收到没有快捷键的 <kbd>Win</kbd> 松开时,它会启动 Windows 开始菜单。你在示例中看不到这一点的唯一原因是我使用了一个 MessageBox,似乎 MessageBox 会取消 Windows 菜单。我将更改示例,在 Form1 中写入一些内容... - Christian Rondeau

2

这似乎正是您想要的(如果您愿意,可以省略RWin)。

请在您的应用程序失去焦点时考虑注销此KB钩子!

    [DllImport("user32.dll")]
    static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode == HC_ACTION)
        {
            var keyInfo = (Kbdllhookstruct) Marshal.PtrToStructure(lParam, typeof (Kbdllhookstruct));
            if ((int) wParam == WM_KEYDOWN
                && keyInfo.VkCode == VK_TAB
                && (GetAsyncKeyState(Keys.LWin) < 0 || GetAsyncKeyState(Keys.RWin) < 0))
            {
                _mainForm.Text = "Win + Tab was pressed " + (++_winTabPressCounter) + " times";
                return (IntPtr) 1;
            }
        }

        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

在发现这种技术之前,我尝试了几种方法。这篇文章是最有帮助的https://dev59.com/kHVC5IYBdhLWcg3wbQbA#317550


简而言之:钩住Win键按下,而不是松开,并且不要阻止其他按键 :) 我删除了 if (wParam == (IntPtr)WM_KEYDOWN) { return (IntPtr)1; }if (keyInfo.VkCode == VK_LWIN) 块中的其他 return (IntPtr)1;,这也起作用了,尽管我认为你的代码更简单、更清晰。我被卡了9个月,所以非常感谢你抽出时间来研究这个问题!(解决方案显然很简单...) - Christian Rondeau
嗯,我没有尝试您建议的方法。我考虑过,但是假设捕获winkey键会干扰其他winkey功能,比如winkey + R。您的方法可以允许这样吗? - dss539
是的,它确实如此。换句话说,我“记录”Win Down而不是在Tab Down发生时检查Win Down状态。这只是为了实现相同的行为而使代码更难阅读,所以...你的方法更好! - Christian Rondeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接