1. 程式人生 > >WPF 利用鍵盤鉤子來捕獲鍵盤,做一些不為人知的事情...完整實例

WPF 利用鍵盤鉤子來捕獲鍵盤,做一些不為人知的事情...完整實例

程序猿 通過 不知道 tro color launcher 聲明 leg win

原文:WPF 利用鍵盤鉤子來捕獲鍵盤,做一些不為人知的事情...完整實例

鍵盤鉤子是一種可以監控鍵盤操作的指令。

看到這句話是不是覺得其實鍵盤鉤子可以做很多事情.

場景

當你的程序需要一個全局的快捷鍵時,可以考慮使用鍵盤鉤子,如大家常用qq的截圖快捷鍵,那麽在WPF裏怎麽去實現呢?

當然不是直接在Window窗體裏面去註冊KeyDown、KeyUp,這樣只有在程序是焦點的情況下才能觸發,

我們這裏要做的更為強大,即在非焦點下去獲取到鍵盤的事件(要偷偷記錄女朋友鍵盤記錄的滾粗,當然我是在開玩笑,程序猿哪裏有女朋友,我們只有男朋友(⊙0⊙))

實現

首先我們需要用到這麽幾個WinAPI

//安裝鉤子的函數 
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);


        //卸下鉤子的函數 

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]

        
public static extern bool UnhookWindowsHookEx(int idHook); //下一個鉤掛的函數 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

大致的邏輯為:

安裝鍵盤鉤子,然後通過委托來處理獲取到的鍵盤消息

整理為兩個類(winapi[Win32Api.cs]類和鍵盤鉤子[KeyboardHook.cs]類)如下

public class Win32Api
{
public const int WM_KEYDOWN = 0x100;

        public const int WM_KEYUP = 0x101;

        public const int WM_SYSKEYDOWN = 0x104;

        public const int WM_SYSKEYUP = 0x105;

        public const int WH_KEYBOARD_LL = 13;



        [StructLayout(LayoutKind.Sequential)] //聲明鍵盤鉤子的封送結構類型
        public class KeyboardHookStruct
        {

            public int vkCode; //表示一個在1到254間的虛似鍵盤碼 

            public int scanCode; //表示硬件掃描碼 

            public int flags;

            public int time;

            public int dwExtraInfo;

        }

public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

        //安裝鉤子的函數 
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);


        //卸下鉤子的函數 

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);


        //下一個鉤掛的函數 
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
}
public class KeyboardHook
    {
        int hHook;

        Win32Api.HookProc KeyboardHookDelegate;


/// <summary>
        /// 安裝鍵盤鉤子
        /// </summary>
        public void SetHook()
        {

            KeyboardHookDelegate = new Win32Api.HookProc(KeyboardHookProc);

            ProcessModule cModule = Process.GetCurrentProcess().MainModule;

            var mh = Win32Api.GetModuleHandle(cModule.ModuleName);

            hHook = Win32Api.SetWindowsHookEx(Win32Api.WH_KEYBOARD_LL, KeyboardHookDelegate, mh, 0);

        }

        /// <summary>
        /// 卸載鍵盤鉤子
        /// </summary>
        public void UnHook()
        {

            Win32Api.UnhookWindowsHookEx(hHook);

        }
        
        /// <summary>
        /// 獲取鍵盤消息
        /// </summary>
        /// <param name="nCode"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // 如果該消息被丟棄(nCode<0
            if (nCode >= 0)
            {

                Win32Api.KeyboardHookStruct KeyDataFromHook = (Win32Api.KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(Win32Api.KeyboardHookStruct));

                int keyData = KeyDataFromHook.vkCode;

                //WM_KEYDOWN和WM_SYSKEYDOWN消息,將會引發OnKeyDownEvent事件
                if (OnKeyDownEvent != null && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN))
                {
                        // 此處觸發鍵盤按下事件
                        // keyData為按下鍵盤的值,對應 虛擬碼

                }

                //WM_KEYUP和WM_SYSKEYUP消息,將引發OnKeyUpEvent事件 

                if (OnKeyUpEvent != null && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP))
                {
                          // 此處觸發鍵盤擡起事件
                }

            }

            return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam);

        }
}

當時拿在虛擬碼的時候其實我是拒絕的,因為在接下來我竟然不知道要對這些虛擬碼做什麽處理,難道要一個一個轉換才能在WPF裏進一步的去判斷按下的是什麽鍵?

最後經博客園<Launcher>告知,在System.Windows.Input命名空間下其實已經封裝了轉換的方法

namespace System.Windows.Input
{
    // 摘要: 
    //     提供在 Win32 虛擬鍵和 WPFSystem.Windows.Input.Key 枚舉之間進行轉換的靜態方法。
    public static class KeyInterop
    {
        // 摘要: 
        //     將 Win32 虛擬鍵轉換為 WPFSystem.Windows.Input.Key。
        //
        // 參數: 
        //   virtualKey:
        //     要轉換的虛擬鍵。
        //
        // 返回結果: 
        //     WPF 鍵。
        public static Key KeyFromVirtualKey(int virtualKey);
        //
        // 摘要: 
        //     將 WPFSystem.Windows.Input.Key 轉換為 Win32 虛擬鍵。
        //
        // 參數: 
        //   key:
        //     要轉換的 WPF。
        //
        // 返回結果: 
        //     Win32 虛擬鍵。
        public static int VirtualKeyFromKey(Key key);
    }
}

結果

於是我們開開心心的拿到了基友的鍵盤操作記錄

KeyInterop.KeyFromVirtualKey(KeyData)

nono,我只是拿來做了一個鋼琴鍵盤

代碼已貼,恕不給基友另行提供Demo

WPF 利用鍵盤鉤子來捕獲鍵盤,做一些不為人知的事情...完整實例