简体   繁体   English

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

[英]Global hotkey in console application

Does anyone know how to use the RegisterHotKey/UnregisterHotKey API calls in a console application? 有谁知道如何在控制台应用程序中使用RegisterHotKey / UnregisterHotKey API调用? I assume that setting up/removing the hotkey is the same, but how do I get the call back when the key was pressed? 我假设设置/删除热键是相同的,但是如何在按下键时收到回叫?

Every example I see is for Winforms, and uses protected override void WndProc(ref Message m){...} , which isn't available to me. 我看到的每个例子都是Winforms,并使用protected override void WndProc(ref Message m){...} ,这对我来说是不可用的。


update: what I have is below, but the event is never hit. 更新:我的内容如下,但事件永远不会被击中。 I thought it could be because when you load ConsoleShell it does block further execution, but even if I put SetupHotkey into a different thread nothing happens. 我认为这可能是因为当你加载ConsoleShell时它会阻止进一步执行,但即使我将SetupHotkey放入另一个线程也没有任何反应。 Any thoughts? 有什么想法吗?

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

What you can do is Create a hidden window in your Console application which is used to handle the hotkey notification and raise an event. 您可以做的是在控制台应用程序中创建一个隐藏窗口,用于处理热键通知并引发事件。

The code HERE demonstrates the principal. 这里的代码演示了委托人。 HERE is an article on handling messages in a Console application, using this you should be able to enhance HotKeyManager to run in a Console Application. 是一篇关于在控制台应用程序中处理消息的文章,使用它可以增强HotKeyManager以在控制台应用程序中运行。

The following update to the HotKeyManager creates a background thread which runs the message loop and handles the windows messages. 以下对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
  }
}

Here is an example of using HotKeyManager from a Console application 以下是从Console应用程序中使用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!");
    }
  }
}

I just wanted to offer an alternative solution. 我只是想提供一种替代解决方案。

I was answering a question for someone who was using this script and I figured this might help someone else who has trouble setting up a global key hook. 我正在为使用此脚本的人回答一个问题,我认为这可能有助于设置全局密钥钩子的其他人。

Edit: Don't forget to add a reference to System.Windows.Forms 编辑:不要忘记添加对System.Windows.Forms的引用

You can do this by selecting Project 🢂 Add Reference and checking 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
    }
}
}

I came up with a solution based on Chris' answer that uses WPF instead of WinForms: 我想出了一个基于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
}

To use this, you need to add references to PresentationFramework.dll and WindowsBase.dll. 要使用它,您需要添加对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();
    }
}

Changed the HotKeyManager class 更改了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;
    }

Class HotKeyEventArgs: 类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);
        }
    }

And class: HotKeyEventArgs 和类:HotKeyEventArgs

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM