[C#][Windows API] 接收全域鍵盤事件(Windows Hook for Keyboard)

  • 15249
  • 0
  • 2011-06-08

[C#][Windows API] 接收全域鍵盤事件(Windows Hook for Keyboard)

以下會利用SetWindowsHookEx原生方法來達到接收全域鍵盤事件的效果。

原生方法的宣告請參考[常用Windows原生方法整理(Windows API)]

原生方法用到的常數請參考[Windows 原生指令常數 (Windows API Constansts)]

原生方法用到的結構請參考[常用Windows原生結構]

使用這個類別時必須注意,在這個類別引發的所有事件中查詢引發事件的按鍵狀態,將會收到與預期不同的結果。

因為範例程式碼中使用的是低階WinHook,因此當Windows都還沒收到訊號時就會執行事件委派,如果在這些委派中透過API查詢按鍵狀態,將不會是最新的按鍵狀態,因為Windows還沒有為其更新狀態。

 

[完整程式碼下載:WindowsHookSample.zip]

 

※全文歡迎轉載但請註明出處,謝謝。※

    {
        /// <summary>
        /// 取得或設定是否獨佔所有鍵盤事件。
        /// </summary>
        public static bool Monopolize { get; set; }

        /// <summary>
        /// 不論是否擁有焦點,當鍵盤按下時引發此事件。
        /// </summary>
        public static event EventHandler<KeyEventArgs> GlobalKeyDown;
        /// <summary>
        /// 不論是否擁有焦點,當鍵盤放開時引發此事件。
        /// </summary>
        public static event EventHandler<KeyEventArgs> GlobalKeyUp;
        /// <summary>
        /// 取得或設定是否開始接收全域鍵盤事件。
        /// </summary>
        public static bool Enabled
        {
            get { return m_Enabled; }
            set
            {
                if (m_Enabled != value)
                {
                    m_Enabled = value;
                    if (value)
                        Install();
                    else
                        Uninstall();
                }
            }
        }
        private static bool m_Enabled = false;

        private static int m_HookHandle = 0;
        private static NativeStructs.HookProc m_HookProc;

        /// <summary>
        /// 向Windows註冊Hook。
        /// </summary>
        private static void Install()
        {
            if (m_HookHandle == 0)
            {
                Process curProcess = Process.GetCurrentProcess();
                ProcessModule curModule = curProcess.MainModule;

                m_HookProc = new NativeStructs.HookProc(HookProc);
                m_HookHandle = NativeMethods.SetWindowsHookEx(NativeContansts.WH_KEYBOARD_LL, m_HookProc, NativeMethods.GetModuleHandle(curModule.ModuleName), 0);

                curModule.Dispose();
                curProcess.Dispose();

                if (m_HookHandle == 0)
                    throw new Exception("Install Hook Faild.");
            }
        }
        private static void Uninstall()
        {
            if (m_HookHandle != 0)
            {
                bool ret = NativeMethods.UnhookWindowsHookEx(m_HookHandle);

                if (ret)
                    m_HookHandle = 0;
                else
                    throw new Exception("Uninstall Hook Faild.");
            }
        }

        /// <summary>
        /// 註冊Windows Hook時用到的委派方法,當全域事件發生時會執行這個方法,並提供全域事件資料。
        /// </summary>
        private static int HookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            KeyEventArgs e = null;
            int wParam_Int32 = wParam.ToInt32();
            if (nCode >= 0)
            {
                NativeStructs.KEYBOARDLLHookStruct keyboardHookStruct = (NativeStructs.KEYBOARDLLHookStruct)Marshal.PtrToStructure(lParam, typeof(NativeStructs.KEYBOARDLLHookStruct));
                if (GlobalKeyDown != null && (wParam_Int32 == NativeContansts.WM_KEYDOWN || wParam_Int32 == NativeContansts.WM_SYSKEYDOWN))
                {
                    e = new KeyEventArgs(keyboardHookStruct.VirtualKeyCode);
                    GlobalKeyDown.Invoke(null, e);
                }
                else if (GlobalKeyUp != null && (wParam_Int32 == NativeContansts.WM_KEYUP || wParam_Int32 == NativeContansts.WM_SYSKEYUP))
                {
                    e = new KeyEventArgs(keyboardHookStruct.VirtualKeyCode);
                    GlobalKeyUp.Invoke(null, e);
                }
            }

            if (Monopolize || (e != null && e.Handled))
                return -1;
            return NativeMethods.CallNextHookEx(m_HookHandle, nCode, wParam, lParam);
        }

        /// <summary>
        /// 提供 GlobalKeyDown 或 GlobalKeyUp 事件的資料。
        /// </summary>
        public class KeyEventArgs : EventArgs
        {
            /// <summary>
            /// 取得或設定值,指出是否處理事件。
            /// </summary>
            public bool Handled { get; set; }
            /// <summary>
            /// 取得值,虛擬鍵盤碼的System.Windows.Forms.Keys表示。
            /// </summary>
            public System.Windows.Forms.Keys Keys { get { return (System.Windows.Forms.Keys)VirtualKeyCode; } }
            /// <summary>
            /// 取得值,虛擬鍵盤碼的System.Windows.Input.Key表示。
            /// </summary>
            public System.Windows.Input.Key Key { get { return System.Windows.Input.KeyInterop.KeyFromVirtualKey(VirtualKeyCode); } }
            /// <summary>
            /// 取得值,指出是否按下 ALT 鍵。
            /// </summary>
            public bool Alt
            {
                get
                {
                    return KeyIsDown((int)System.Windows.Forms.Keys.LMenu) || KeyIsDown((int)System.Windows.Forms.Keys.RMenu);
                }
            }
            /// <summary>
            /// 取得值,指出是否按下 CTRL 鍵。
            /// </summary>
            public bool Control
            {
                get
                {
                    return KeyIsDown((int)System.Windows.Forms.Keys.LControlKey) || KeyIsDown((int)System.Windows.Forms.Keys.RControlKey);
                }
            }
            /// <summary>
            /// 取得值,指出是否按下 SHIFT 鍵。
            /// </summary>
            public bool Shift
            {
                get
                {
                    return KeyIsDown((int)System.Windows.Forms.Keys.LShiftKey) || KeyIsDown((int)System.Windows.Forms.Keys.RShiftKey);
                }
            }
            /// <summary>
            /// 取得值,引發事件的虛擬鍵盤碼。
            /// </summary>
            public int VirtualKeyCode { get; private set; }
            internal KeyEventArgs(int virtualKey)
            {
                this.Handled = false;
                this.VirtualKeyCode = virtualKey;
            }

            private static bool KeyIsDown(int KeyCode)
            {
                if ((NativeMethods.GetKeyState(KeyCode) & 0x80) == 0x80)
                    return true;
                else
                    return false;
            }
        }
    }

分享