控制台应用程序中的全局热键

时间:2010-09-06 23:18:25

标签: c# winapi pinvoke console-application

有没有人知道如何在控制台应用程序中使用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);
    }
}

4 个答案:

答案 0 :(得分:62)

您可以在控制台应用程序中创建一个隐藏窗口,用于处理热键通知并举起活动。

代码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!");
    }
  }
}

答案 1 :(得分:6)

我只是想提供另一种解决方案。

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

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

的引用

您可以选择Project Add Reference并检查System.Windows.Forms

来执行此操作

enter image description here

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

答案 2 :(得分:2)

我想出了一个基于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();
    }
}

答案 3 :(得分:0)

更改了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
        }
    }