【问题标题】:how to create non window bound keyboard shortcuts如何创建非窗口绑定的键盘快捷键
【发布时间】:2025-07-14 03:05:02
【问题描述】:

我正在创建一个 C# 应用程序,可以是 WinForm,但最好是控制台应用程序,即使应用程序不在前台,它也需要捕获键盘快捷键。如何做到这一点,我知道这是可能的,即 Songbird 可以做到这一点。

此键盘快捷键的格式为 ctrl+-> 到目前为止,我还没有任何代码,因为我什至不知道如何在全局范围内注册键盘快捷键。

【问题讨论】:

    标签: c# windows keyboard-shortcuts global


    【解决方案1】:

    您应该使用RegisterHotkey 而不是键盘挂钩。

    当您只需要全局热键时使用全局键盘挂钩是对 api 的滥用。它还会对性能产生负面影响,因为每个键都需要在到达目标程序之前由您的程序处理。

    【讨论】:

    • 这在控制台应用程序中不起作用,这是“首选”应用程序样式。 RegisterHotKey 将热键映射到特定窗口的消息队列中,但控制台应用程序中没有消息泵。添加一个完整的消息泵来处理这个问题,IMO,比使用 LL 键盘钩子更滥用。
    • 如果我没记错的话,LL 键盘钩子也需要你的线程来处理消息。
    • @CodeInChaos:不。它直接使用回调。有关详细信息,请参阅 SetWindowsHookEx - msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx
    • 挂钩是通过回调处理的,而不是 Windows 消息。
    • MSDN on RegisterHotkey:“如果此参数[hWnd 参数] 为NULL,WM_HOTKEY 消息将发布到调用线程的消息队列中,并且必须在消息循环中进行处理。”所以它不需要 HWND,它只需要你的线程来处理消息。
    【解决方案2】:

    这是一个示例代码,希望对您有所帮助...(祝你好运!!!)

    用法:

                _hotKey0 = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler);
    

    ...

            // ******************************************************************
        private void OnHotKeyHandler(HotKey hotKey)
        {
            SystemHelper.SetScreenSaverRunning();
        }
    

    类:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net.Mime;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interop;
    
    namespace UnManaged
    {
        public class HotKey : IDisposable
        {
            private static Dictionary<int, HotKey> _dictHotKeyToCalBackProc;
    
            [DllImport("user32.dll")]
            private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);
    
            [DllImport("user32.dll")]
            private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
    
            public const int WmHotKey = 0x0312;
    
            private bool _disposed = false;
    
            public Key Key { get; private set; }
            public KeyModifier KeyModifiers { get; private set; }
            public Action<HotKey> Action { get; private set; }
            public int Id { get; set; }
    
            // ******************************************************************
            public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true)
            {
                Key = k;
                KeyModifiers = keyModifiers;
                Action = action;
                if (register)
                {
                    Register();
                }
            }
    
            // ******************************************************************
            public bool Register()
            {
                int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
                Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
                bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);
    
                if (_dictHotKeyToCalBackProc == null)
                {
                    _dictHotKeyToCalBackProc = new Dictionary<int, HotKey>();
                    ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
                }
    
                _dictHotKeyToCalBackProc.Add(Id, this);
    
                Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
                return result;
            }
    
            // ******************************************************************
            public void Unregister()
            {
                HotKey hotKey;
                if (_dictHotKeyToCalBackProc.TryGetValue(Id, out hotKey))
                {
                    UnregisterHotKey(IntPtr.Zero, Id);
                }
            }
    
            // ******************************************************************
            private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
            {
                if (!handled)
                {
                    if (msg.message == WmHotKey)
                    {
                        HotKey hotKey;
    
                        if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
                        {
                            if (hotKey.Action != null)
                            {
                                hotKey.Action.Invoke(hotKey);
                            }
                            handled = true;
                        }
                    }
                }
            }
    
            // ******************************************************************
            // Implement IDisposable.
            // Do not make this method virtual.
            // A derived class should not be able to override this method.
            public void Dispose()
            {
                Dispose(true);
                // This object will be cleaned up by the Dispose method.
                // Therefore, you should call GC.SupressFinalize to
                // take this object off the finalization queue
                // and prevent finalization code for this object
                // from executing a second time.
                GC.SuppressFinalize(this);
            }
    
            // ******************************************************************
            // Dispose(bool disposing) executes in two distinct scenarios.
            // If disposing equals true, the method has been called directly
            // or indirectly by a user's code. Managed and unmanaged resources
            // can be _disposed.
            // If disposing equals false, the method has been called by the
            // runtime from inside the finalizer and you should not reference
            // other objects. Only unmanaged resources can be _disposed.
            protected virtual void Dispose(bool disposing)
            {
                // Check to see if Dispose has already been called.
                if (!this._disposed)
                {
                    // If disposing equals true, dispose all managed
                    // and unmanaged resources.
                    if (disposing)
                    {
                        // Dispose managed resources.
                        Unregister();
                    }
    
                    // Note disposing has been done.
                    _disposed = true;
                }
            }
        }
    
        // ******************************************************************
        [Flags]
        public enum KeyModifier
        {
            None = 0x0000,
            Alt = 0x0001,
            Ctrl = 0x0002,
            NoRepeat = 0x4000,
            Shift = 0x0004,
            Win = 0x0008
        }
    
        // ******************************************************************
    }
    

    【讨论】:

      【解决方案3】:

      一种可能的方法是使用 Windows Hooks。 但为了做到这一点,您需要创建一个本地 DLL,并在其中编写回调函数以作为挂钩安装。

      查找SetWindowsHookEx 作为起点。网上也有很多关于如何使用的示例。

      【讨论】:

      • 这可以通过 P/Invoke 完成,而无需使用本机 DLL。有关示例代码,请参阅我链接的文章...
      • 如果您使用 LowLevelKeyboardHook,则不需要本机 dll,因为这样就不需要注入其他进程。低级挂钩是在您安装挂钩的过程中处理的,而不是在接收输入的过程中。对于其他一些钩子,尤其是非低级键盘钩子,本机 dll 是必需的。
      • @CodeInChaos:有趣...感谢您的更正。我认为所有全局挂钩都需要在 DLL 中。
      【解决方案4】:

      这可以通过低级键盘钩子通过 Windows 钩子捕获 WH_KEYBOARD_LL 来完成。

      这是一个CodeProject article on setting up a Global Hook in C#,它演示了整个过程。

      【讨论】:

      • 因为它的目的是错误的 api,我不希望看到更多的程序这样做。
      • @CodeInChaos:您在控制台应用程序中没有窗口句柄。 RegisterHotKey 适用于 Windows 窗体应用程序,但不适用于任何通用应用程序。它需要一个目标 HWND 来接收它的消息队列中的通知消息。
      • 低级键盘钩子也有同样的问题。一个非低级的keyboardhook不会有这个问题,但它有更多的问题,所以更糟糕。