【问题标题】:Global hotkey in console application控制台应用程序中的全局热键
【发布时间】:2023-03-31 12:11:01
【问题描述】:

有谁知道如何在控制台应用程序中使用 RegisterHotKey/UnregisterHotKey API 调用?我假设设置/删除热键是相同的,但是当按键被按下时我如何得到回调?

我看到的每个示例都是针对 Winforms 的,并且使用 protected override void WndProc(ref Message m){...},这对我来说是不可用的。


更新: 我所拥有的内容如下,但该事件从未命中。我认为这可能是因为当您加载 ConsoleShell 时,它确实会阻止进一步执行,但即使我将 SetupHotkey 放入不同的线程中也不会发生任何事情。有什么想法吗?
class Program
{
    static void Main(string[] args)
    {
        new Hud().Init(args);
    }
}

class Hud
{
    int keyHookId;


    public void Init(string[] args)
    {
        SetupHotkey();
        InitPowershell(args);
        Cleanup();
    }

    private void Cleanup()
    {
        HotKeyManager.UnregisterHotKey(keyHookId);
    }

    private void SetupHotkey()
    {
        keyHookId = HotKeyManager.RegisterHotKey(Keys.Oemtilde, KeyModifiers.Control);
        HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
    }

    void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
    {
        //never executed
        System.IO.File.WriteAllText("c:\\keyPressed.txt", "Hotkey pressed");
    }

    private static void InitPowershell(string[] args)
    {
        var config = RunspaceConfiguration.Create();
        ConsoleShell.Start(config, "", "", args);
    }
}

【问题讨论】:

    标签: c# winapi pinvoke console-application


    【解决方案1】:

    您可以在控制台应用程序中创建一个隐藏窗口,用于处理热键通知并引发事件。

    代码HERE 演示了主体。 HERE 是一篇关于在控制台应用程序中处理消息的文章,使用它您应该能够增强 HotKeyManager 以在控制台应用程序中运行。

    HotKeyManager 的以下更新创建了一个后台线程,该线程运行消息循环并处理 Windows 消息。

    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    namespace ConsoleHotKey
    {
      public static class HotKeyManager
      {
        public static event EventHandler<HotKeyEventArgs> HotKeyPressed;
    
        public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
        {
          _windowReadyEvent.WaitOne();
          int id = System.Threading.Interlocked.Increment(ref _id);
          _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
          return id;
        }
    
        public static void UnregisterHotKey(int id)
        {
          _wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
        }
    
        delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
        delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);
    
        private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
        {      
          RegisterHotKey(hwnd, id, modifiers, key);      
        }
    
        private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
        {
          UnregisterHotKey(_hwnd, id);
        }    
    
        private static void OnHotKeyPressed(HotKeyEventArgs e)
        {
          if (HotKeyManager.HotKeyPressed != null)
          {
            HotKeyManager.HotKeyPressed(null, e);
          }
        }
    
        private static volatile MessageWindow _wnd;
        private static volatile IntPtr _hwnd;
        private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
        static HotKeyManager()
        {
          Thread messageLoop = new Thread(delegate()
            {
              Application.Run(new MessageWindow());
            });
          messageLoop.Name = "MessageLoopThread";
          messageLoop.IsBackground = true;
          messageLoop.Start();      
        }
    
        private class MessageWindow : Form
        {
          public MessageWindow()
          {
            _wnd = this;
            _hwnd = this.Handle;
            _windowReadyEvent.Set();
          }
    
          protected override void WndProc(ref Message m)
          {
            if (m.Msg == WM_HOTKEY)
            {
              HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
              HotKeyManager.OnHotKeyPressed(e);
            }
    
            base.WndProc(ref m);
          }
    
          protected override void SetVisibleCore(bool value)
          {
            // Ensure the window never becomes visible
            base.SetVisibleCore(false);
          }
    
          private const int WM_HOTKEY = 0x312;
        }
    
        [DllImport("user32", SetLastError=true)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
    
        [DllImport("user32", SetLastError = true)]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
    
        private static int _id = 0;
      }
    
    
      public class HotKeyEventArgs : EventArgs
      {
        public readonly Keys Key;
        public readonly KeyModifiers Modifiers;
    
        public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
        {
          this.Key = key;
          this.Modifiers = modifiers;
        }
    
        public HotKeyEventArgs(IntPtr hotKeyParam)
        {
          uint param = (uint)hotKeyParam.ToInt64();
          Key = (Keys)((param & 0xffff0000) >> 16);
          Modifiers = (KeyModifiers)(param & 0x0000ffff);
        }
      }
    
      [Flags]
      public enum KeyModifiers
      {
        Alt = 1,
        Control = 2,
        Shift = 4,
        Windows = 8,
        NoRepeat = 0x4000
      }
    }
    

    这是从控制台应用程序使用 HotKeyManager 的示例

    using System;
    using System.Windows.Forms;
    
    namespace ConsoleHotKey
    {
      class Program
      {
        static void Main(string[] args)
        {
          HotKeyManager.RegisterHotKey(Keys.A, KeyModifiers.Alt);
          HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
          Console.ReadLine();      
        }
    
        static void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
        {
          Console.WriteLine("Hit me!");
        }
      }
    }
    

    【讨论】:

    • 我已经尝试了很多次,但事件似乎永远不会被触发。我还有什么遗漏的吗?
    • @joe,我添加了一个更新,其中包括重新设计的 HotKeyManager 以支持控制台应用程序。
    • 你刚刚拯救了我的一天!谢谢!
    • @M0HS3N - 这是底层 Windows 热键支持的工作方式。为任何热键生成 WM_HOTKEY 消息,然后检查参数以确定按下了哪个热键。包装器遵循此模式,当引发 HotKeyPressed 事件时,您可以检查 HotKeyEventArgs 以确定触发事件的热键组合。您当然可以对此进行更高级别的抽象,但该示例提供了您所需的一切。
    • @GooliveR - 评论可能不是这样做的地方。你想发布一个完整的问题,然后我或更有知识的人可以回答。
    【解决方案2】:

    我只是想提供一个替代解决方案。

    我正在为使用此脚本的人回答一个问题,我认为这可能会帮助其他无法设置全局密钥挂钩的人。

    编辑:不要忘记添加对System.Windows.Forms的引用

    您可以通过选择Project?Add Reference 并选中System.Windows.Forms 来做到这一点

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace ConsoleKeyhook
    {
    class Hooky
    {
        ///////////////////////////////////////////////////////////
        //A bunch of DLL Imports to set a low level keyboard hook
        ///////////////////////////////////////////////////////////
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);
    
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    
        ////////////////////////////////////////////////////////////////
        //Some constants to make handling our hook code easier to read
        ////////////////////////////////////////////////////////////////
        private const int WH_KEYBOARD_LL = 13;                    //Type of Hook - Low Level Keyboard
        private const int WM_KEYDOWN = 0x0100;                    //Value passed on KeyDown
        private const int WM_KEYUP = 0x0101;                      //Value passed on KeyUp
        private static LowLevelKeyboardProc _proc = HookCallback; //The function called when a key is pressed
        private static IntPtr _hookID = IntPtr.Zero;
        private static bool CONTROL_DOWN = false;                 //Bool to use as a flag for control key
    
        public static void Main()
        {
            _hookID = SetHook(_proc);  //Set our hook
            Application.Run();         //Start a standard application method loop
        }
    
        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }
    
        private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    
        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) //A Key was pressed down
            {
                int vkCode = Marshal.ReadInt32(lParam);           //Get the keycode
                string theKey = ((Keys)vkCode).ToString();        //Name of the key
                Console.Write(theKey);                            //Display the name of the key
                if (theKey.Contains("ControlKey"))                //If they pressed control
                {
                    CONTROL_DOWN = true;                          //Flag control as down
                }
                else if (CONTROL_DOWN && theKey == "B")           //If they held CTRL and pressed B
                {
                    Console.WriteLine("\n***HOTKEY PRESSED***");  //Our hotkey was pressed
                }
                else if (theKey == "Escape")                      //If they press escape
                {
                    UnhookWindowsHookEx(_hookID);                 //Release our hook
                    Environment.Exit(0);                          //Exit our program
                }
            }
            else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP) //KeyUP
            {
                int vkCode = Marshal.ReadInt32(lParam);        //Get Keycode
                string theKey = ((Keys)vkCode).ToString();     //Get Key name
                if (theKey.Contains("ControlKey"))             //If they let go of control
                {
                    CONTROL_DOWN = false;                      //Unflag control
                }
            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam); //Call the next hook
        }
    }
    }
    

    【讨论】:

    • 我使用了同样的方法,发现映射在其他应用程序中的热键将使用钩子传播。因此,例如,Chrome 中的 F12 会提升检查器。使用 user32.RegisterHotKey 应该可以防止这种情况(根据其他来源)。
    • 这是一个全局 keyhook - 它允许从任何其他应用程序传播。
    • 如果您想阻止其他应用程序(如 Chrome)看到您的热键,请在检测到热键代码后添加return (IntPtr) 0;。 (我认为 - 未经测试,但 IIRC 你就是这样做的)。是01 - 不记得是哪个了。
    • 此解决方案在同一进程的上下文中执行,因此在尝试修改控件时不会遇到违规。谢谢!!
    【解决方案3】:

    我根据 Chris 的回答提出了一个使用 WPF 而不是 WinForms 的解决方案:

    public sealed class GlobalHotkeyRegister : IGlobalHotkeyRegister, IDisposable
    {
        private const int WmHotkey = 0x0312;
    
        private Application _app;
        private readonly Dictionary<Hotkey, Action> _hotkeyActions;
    
        public GlobalHotkeyRegister()
        {
            _hotkeyActions = new Dictionary<Hotkey, Action>();
            var startupTcs = new TaskCompletionSource<object>();
    
            Task.Run(() =>
            {
                ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreProcessMessage;
    
                _app = new Application();
                _app.Startup += (s, e) => startupTcs.SetResult(null);
                _app.Run();
            });
    
            startupTcs.Task.Wait();
        }
    
        public void Add(Hotkey hotkey, Action action)
        {
            _hotkeyActions.Add(hotkey, action);
    
            var keyModifier = (int) hotkey.KeyModifier;
            var key = KeyInterop.VirtualKeyFromKey(hotkey.Key);
    
            _app.Dispatcher.Invoke(() =>
            {
                if (!RegisterHotKey(IntPtr.Zero, hotkey.GetHashCode(), keyModifier, key))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            });       
        }
    
        public void Remove(Hotkey hotkey)
        {
            _hotkeyActions.Remove(hotkey);
    
            _app.Dispatcher.Invoke(() =>
            {
                if (!UnregisterHotKey(IntPtr.Zero, hotkey.GetHashCode()))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            });
        }
    
        private void OnThreadPreProcessMessage(ref MSG msg, ref bool handled)
        {
            if (msg.message != WmHotkey)
                return;
    
            var key = KeyInterop.KeyFromVirtualKey(((int) msg.lParam >> 16) & 0xFFFF);
            var keyModifier = (KeyModifier) ((int) msg.lParam & 0xFFFF);
    
            var hotKey = new Hotkey(keyModifier, key);
            _hotkeyActions[hotKey]();
        }
    
        public void Dispose()
        {
            _app.Dispatcher.InvokeShutdown();
        }
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
    }
    
    public class Hotkey
    {
        public Hotkey(KeyModifier keyModifier, Key key)
        {
            KeyModifier = keyModifier;
            Key = key;
        }
    
        public KeyModifier KeyModifier { get; }
        public Key Key { get; }
    
        #region ToString(), Equals() and GetHashcode() overrides
    }
    
    [Flags]
    public enum KeyModifier
    {
        None = 0x0000,
        Alt = 0x0001,
        Ctrl = 0x0002,
        Shift = 0x0004,
        Win = 0x0008,
        NoRepeat = 0x4000
    }
    

    要使用它,您需要添加对 PresentationFramework.dll 和 WindowsBase.dll 的引用。

    public static void Main()
    {
        using (var hotkeyManager = new GlobalHotkeyManager())
        {
            var hotkey = new Hotkey(KeyModifier.Ctrl | KeyModifier.Alt, Key.S);
            hotkeyManager.Add(hotkey, () => System.Console.WriteLine(hotkey));
    
            System.Console.ReadKey();
        }
    }
    

    【讨论】:

      【解决方案4】:

      更改了 HotKeyManager 类

      public static class HotKeyManager
          {
              public static event EventHandler<HotKeyEventArgs> HotKeyPressed;
      
              public static int RegisterHotKey(Keys key, HotKeyEventArgs.KeyModifiers modifiers)
              {
                  _windowReadyEvent.WaitOne();
                  _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, Interlocked.Increment(ref _id), (uint)modifiers, (uint)key);
                  return Interlocked.Increment(ref _id);
              }
      
              public static void UnregisterHotKey(int id)
              {
                  _wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
              }
      
              private delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
              private delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);
      
              private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
              {
                  RegisterHotKey(hWnd: hwnd, id: id, fsModifiers: modifiers, vk: key);
              }
      
              private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
              {
                  UnregisterHotKey(_hwnd, id);
              }
      
              private static void OnHotKeyPressed(HotKeyEventArgs e)
              {
                  HotKeyPressed?.Invoke(null, e);
              }
      
              private static volatile MessageWindow _wnd;
              private static volatile IntPtr _hwnd;
              private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
      
              static HotKeyManager()
              {
                  new Thread(delegate ()
                              {
                                  Application.Run(new MessageWindow());
                              })
                  {
                      Name = "MessageLoopThread",
                      IsBackground = true
                  }.Start();
              }
      
              private class MessageWindow : Form
              {
                  public MessageWindow()
                  {
                      _wnd = this;
                      _hwnd = Handle;
                      _windowReadyEvent.Set();
                  }
      
                  protected override void WndProc(ref Message m)
                  {
                      if (m.Msg == WM_HOTKEY)
                      {
                          var e = new HotKeyEventArgs(hotKeyParam: m.LParam);
                          OnHotKeyPressed(e);
                      }
      
                      base.WndProc(m: ref m);
                  }
      
                  protected override void SetVisibleCore(bool value)
                  {
                      // Ensure the window never becomes visible
                      base.SetVisibleCore(false);
                  }
      
                  private const int WM_HOTKEY = 0x312;
              }
      
              [DllImport("user32", SetLastError = true)]
              private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
      
              [DllImport("user32", SetLastError = true)]
              private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
      
              private static int _id = 0;
          }
      

      类 HotKeyEventArgs:

      public partial class HotKeyEventArgs : EventArgs
          {
              public readonly Keys Key;
              public readonly KeyModifiers Modifiers;
      
              public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
              {
                  Key = key;
                  Modifiers = modifiers;
              }
      
              public HotKeyEventArgs(IntPtr hotKeyParam)
              {
                  Key = (Keys)(((uint)hotKeyParam.ToInt64() & 0xffff0000) >> 16);
                  Modifiers = (KeyModifiers)((uint)hotKeyParam.ToInt64() & 0x0000ffff);
              }
          }
      

      还有类:HotKeyEventArgs

      public partial class HotKeyEventArgs
          {
              [Flags]
              public enum KeyModifiers
              {
                  Alt = 1,
                  Control = 2,
                  Shift = 4,
                  Windows = 8,
                  NoRepeat = 0x4000
              }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-02
        • 1970-01-01
        • 2019-11-22
        • 2010-09-23
        • 1970-01-01
        • 2016-07-12
        • 2013-06-12
        • 1970-01-01
        相关资源
        最近更新 更多