C# 全局键盘事件钩子 - .net 4.0

10
作为我正在开发的媒体播放器应用程序的一部分,我想要挂钩全局按键以控制媒体控制键(播放、快进、快退等)。
我已经搜索了大约2个小时,试图找到解决方案,但我找不到可行的解决方案。我发现了几个关于同样问题的Stack Overflow答案,但它们都没有起作用。
我尝试了MouseKeyHook NuGet包,但它从未触发事件。我也尝试了FMUtils.KeyboardHook包,但是同样的事情发生了,除了它在启动后会在控制台中打印出关闭挂钩的消息 - 即使在查看源代码之后,我也不知道为什么会这样。
我尝试获取这个codeproject项目http://www.codeproject.com/Articles/18638/Using-Window-Messages-to-Implement-Global-System-H,但是我甚至无法运行演示文稿,两个演示文稿都会抛出我无法追踪的奇怪错误。
我的问题是,在.NET 4.0中,有哪些已知可行的方法可以用来捕获键盘按键,以便在我的WinForms应用程序没有焦点时捕获键盘按键?
2个回答

23

这是我在过去 X 年中在多个项目中使用的代码。应该可以在任何Windows上的.NET版本上无问题运行。

public class KeyboardHook : IDisposable
{
    bool Global = false;

    public delegate void LocalKeyEventHandler(Keys key, bool Shift, bool Ctrl, bool Alt);
    public event LocalKeyEventHandler KeyDown;
    public event LocalKeyEventHandler KeyUp;

    public delegate int CallbackDelegate(int Code, int W, int L);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct KBDLLHookStruct
    {
        public Int32 vkCode;
        public Int32 scanCode;
        public Int32 flags;
        public Int32 time;
        public Int32 dwExtraInfo;
    }

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern int SetWindowsHookEx(HookType idHook, CallbackDelegate lpfn, int hInstance, int threadId);

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern bool UnhookWindowsHookEx(int idHook);

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern int CallNextHookEx(int idHook, int nCode, int wParam, int lParam);

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int GetCurrentThreadId();

    public enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }

    private int HookID = 0;
    CallbackDelegate TheHookCB = null;

    //Start hook
    public KeyboardHook(bool Global)
    {
        this.Global = Global;
        TheHookCB = new CallbackDelegate(KeybHookProc);
        if (Global)
        {
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, TheHookCB,
                0, //0 for local hook. eller hwnd til user32 for global
                0); //0 for global hook. eller thread for hooken
        }
        else
        {
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD, TheHookCB,
                0, //0 for local hook. or hwnd to user32 for global
                GetCurrentThreadId()); //0 for global hook. or thread for the hook
        }
    }

    bool IsFinalized = false;
    ~KeyboardHook()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }
    public void Dispose()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }

    //The listener that will trigger events
    private int KeybHookProc(int Code, int W, int L)
    {
        KBDLLHookStruct LS = new KBDLLHookStruct();
        if (Code < 0)
        {
            return CallNextHookEx(HookID, Code, W, L);
        }
        try
        {
            if (!Global)
            {
                if (Code == 3)
                {
                    IntPtr ptr = IntPtr.Zero;

                    int keydownup = L >> 30;
                    if (keydownup == 0)
                    {
                        if (KeyDown != null) KeyDown((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    if (keydownup == -1)
                    {
                        if (KeyUp != null) KeyUp((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    //System.Diagnostics.Debug.WriteLine("Down: " + (Keys)W);
                }
            }
            else
            {
                KeyEvents kEvent = (KeyEvents)W;

                Int32 vkCode = Marshal.ReadInt32((IntPtr)L); //Leser vkCode som er de første 32 bits hvor L peker.

                if (kEvent != KeyEvents.KeyDown && kEvent != KeyEvents.KeyUp && kEvent != KeyEvents.SKeyDown && kEvent != KeyEvents.SKeyUp)
                {
                }
                if (kEvent == KeyEvents.KeyDown || kEvent == KeyEvents.SKeyDown)
                {
                    if (KeyDown != null) KeyDown((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
                if (kEvent == KeyEvents.KeyUp || kEvent == KeyEvents.SKeyUp)
                {
                    if (KeyUp != null) KeyUp((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
            }
        }
        catch (Exception)
        {
            //Ignore all errors...
        }

        return CallNextHookEx(HookID, Code, W, L);

    }

    public enum KeyEvents
    {
        KeyDown = 0x0100,
        KeyUp = 0x0101,
        SKeyDown = 0x0104,
        SKeyUp = 0x0105
    }

    [DllImport("user32.dll")]
    static public extern short GetKeyState(System.Windows.Forms.Keys nVirtKey);

    public static bool GetCapslock()
    {   
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.CapsLock)) & true;
    }
    public static bool GetNumlock()
    { 
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.NumLock)) & true;
    }
    public static bool GetScrollLock()
    { 
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.Scroll)) & true;
    }
    public static bool GetShiftPressed()
    { 
        int state = GetKeyState(System.Windows.Forms.Keys.ShiftKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetCtrlPressed()
    { 
        int state = GetKeyState(System.Windows.Forms.Keys.ControlKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetAltPressed()
    { 
        int state = GetKeyState(System.Windows.Forms.Keys.Menu);
        if (state > 1 || state < -1) return true;
        return false;
    }
}

测试应用:

static class Program
{
    [STAThread]
    static void Main()
    {
        var kh = new KeyboardHook(true);
        kh.KeyDown += Kh_KeyDown;
        Application.Run();
    }

    private static void Kh_KeyDown(Keys key, bool Shift, bool Ctrl, bool Alt)
    {
        Debug.WriteLine("The Key: " + key);
    }
}

代码需要进行一些清理,但由于它正常工作,所以我并没有费心。


目前在C#中可用的钩子只有低级别的:WH_KEYBOARD_LL和WH_MOUSE_LL。 因此,本示例中的非全局钩子将无法使用。 相信我或者相信微软 - Gh61
非常好!谢谢! - Virus721
1
很遗憾,它似乎无法在.NETCore 2.1下编译 - System.Windows.Forms不存在。 - brewmanz
编译KLUDGE尝试定义以下常量并调整错误行... public const Int32 SystemWindowsFormsKeysCapsLock = 20; public const Int32 SystemWindowsFormsKeysNumLock = 144; public const Int32 SystemWindowsFormsKeysScroll = 145; public const Int32 SystemWindowsFormsKeysShiftKey = 16; public const Int32 SystemWindowsFormsKeysControlKey = 17; public const Int32 SystemWindowsFormsKeysMenu = 18; - brewmanz
2
我遇到了一个错误 - "System.AccessViolationException: '尝试读取或写入受保护的内存。这通常表示其他内存已损坏。'"在 Int32 vkCode = Marshal.ReadInt32((IntPtr)L) 这一行上。有什么想法吗? - GDutton

2
这是一个修复后的类版本,不会出现访问违规错误:
public class KeyboardHook : IDisposable
{
    bool Global = false;

    public delegate void ErrorEventHandler(Exception e);
    public delegate void LocalKeyEventHandler(Keys key, bool Shift, bool Ctrl, bool Alt);
    public event LocalKeyEventHandler KeyDown;
    public event LocalKeyEventHandler KeyUp;
    public event ErrorEventHandler OnError;

    public delegate int CallbackDelegate(int Code, IntPtr W, IntPtr L);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct KBDLLHookStruct
    {
        public Int32 vkCode;
        public Int32 scanCode;
        public Int32 flags;
        public Int32 time;
        public Int32 dwExtraInfo;
    }

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr SetWindowsHookEx(HookType idHook, CallbackDelegate lpfn, IntPtr hInstance, int threadId);
    
  
    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern bool UnhookWindowsHookEx(IntPtr idHook);

    [DllImport("user32", CallingConvention = CallingConvention.StdCall)]
    private static extern int CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int GetCurrentThreadId();
    
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);

    public enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }

    private IntPtr HookID = IntPtr.Zero;
    CallbackDelegate TheHookCB = null;

    //Start hook
    public KeyboardHook(bool Global)
    {
        this.Global = Global;
        TheHookCB = new CallbackDelegate(KeybHookProc);
        if (Global)
        {
            IntPtr hInstance = LoadLibrary("User32");
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD_LL, TheHookCB,
                hInstance, //0 for local hook. or hwnd to user32 for global
                0); //0 for global hook. eller thread for hooken
        }
        else
        {
            HookID = SetWindowsHookEx(HookType.WH_KEYBOARD, TheHookCB,
                IntPtr.Zero, //0 for local hook. or hwnd to user32 for global
                GetCurrentThreadId()); //0 for global hook. or thread for the hook
        }
    }

    public void test()
    {
        if (OnError != null) OnError(new Exception("test"));
    }
    bool IsFinalized = false;
    ~KeyboardHook()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }
    public void Dispose()
    {
        if (!IsFinalized)
        {
            UnhookWindowsHookEx(HookID);
            IsFinalized = true;
        }
    }
    [STAThread]
    //The listener that will trigger events
    private int KeybHookProc(int Code, IntPtr W, IntPtr L)
    {
        
        KBDLLHookStruct LS = new KBDLLHookStruct();
        if (Code < 0)
        {
            return CallNextHookEx(HookID, Code, W, L);
        }
        try
        {
            if (!Global)
            {
                if (Code == 3)
                {
                    IntPtr ptr = IntPtr.Zero;

                    int keydownup = L.ToInt32() >> 30;
                    if (keydownup == 0)
                    {
                        if (KeyDown != null) KeyDown((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    if (keydownup == -1)
                    {
                        if (KeyUp != null) KeyUp((Keys)W, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                    }
                    //System.Diagnostics.Debug.WriteLine("Down: " + (Keys)W);
                }
            }
            else
            {
                KeyEvents kEvent = (KeyEvents)W;

                Int32 vkCode = Marshal.ReadInt32((IntPtr)L); //Leser vkCode som er de første 32 bits hvor L peker.

                if (kEvent != KeyEvents.KeyDown && kEvent != KeyEvents.KeyUp && kEvent != KeyEvents.SKeyDown && kEvent != KeyEvents.SKeyUp)
                {
                }
                if (kEvent == KeyEvents.KeyDown || kEvent == KeyEvents.SKeyDown)
                {
                    if (KeyDown != null) KeyDown((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
                if (kEvent == KeyEvents.KeyUp || kEvent == KeyEvents.SKeyUp)
                {
                    if (KeyUp != null) KeyUp((Keys)vkCode, GetShiftPressed(), GetCtrlPressed(), GetAltPressed());
                }
            }
        }
        catch (Exception e)
        {
            if (OnError != null) OnError(e);
            //Ignore all errors...
        }

        return CallNextHookEx(HookID, Code, W, L);

    }

    public enum KeyEvents
    {
        KeyDown = 0x0100,
        KeyUp = 0x0101,
        SKeyDown = 0x0104,
        SKeyUp = 0x0105
    }

    [DllImport("user32.dll")]
    static public extern short GetKeyState(System.Windows.Forms.Keys nVirtKey);

    public static bool GetCapslock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.CapsLock)) & true;
    }
    public static bool GetNumlock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.NumLock)) & true;
    }
    public static bool GetScrollLock()
    {
        return Convert.ToBoolean(GetKeyState(System.Windows.Forms.Keys.Scroll)) & true;
    }
    public static bool GetShiftPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.ShiftKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetCtrlPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.ControlKey);
        if (state > 1 || state < -1) return true;
        return false;
    }
    public static bool GetAltPressed()
    {
        int state = GetKeyState(System.Windows.Forms.Keys.Menu);
        if (state > 1 || state < -1) return true;
        return false;
    }
}

变量 LS 从未被使用。这行代码:KBDLLHookStruct LS = new KBDLLHookStruct(); 为什么要包含它? - stigzler

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