最近遇到了一个需要处理键盘按键释放消息的问题,我在使用重写ProcessCmdKey之后,发现其无法响应KeyUp消息,不知是被什么东西拦截了,查阅了网上的一些资料,使用全局钩子解决了这个问题,在此把过程记录下来。

首先,在使用钩子前我们先来了解一下要使用到的API函数。

第一步:声明API

       //安装钩子
        [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")]
        static extern int GetCurrentThreadId();

        //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetModuleHandle(string name);

第二步:声明一个委托,该委托在捕获键盘消息是会自动调用其对应的方法

        public event KeyEventHandler KeyUpEvent;

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

        static int hKeyboardHook = 0; //声明键盘钩子处理的初始值,钩子是否安装成功

        public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
        HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型

第三步:安装和卸载钩子

         //安装钩子
         public void Start()
        {
            if (hKeyboardHook == 0)
            {
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure,
 GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);             
                /*关于SetWindowsHookEx的第三个参数hInstance,
                如果threadId标识当前进程创建的一个线程,而且子程代码位于当前进程,
                hInstance必须为NULL*/

                /*关于SetWindowsHookEx的第四个参数threadId,如果为全局钩子,
                则应该设置为0,如果是线程钩子,则使用GetCurrentThreadId获取子线程的ID*/
                //************************************
                //键盘线程钩子
                //SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要监听的线程idGetCurrentThreadId(),

                //************************************
                //如果SetWindowsHookEx失败
                if (hKeyboardHook == 0)
                {
                    Stop();
                    throw new Exception("安装键盘钩子失败");
                }
            }
        }

        //卸载钩子
        public void Stop()
        {
            bool retKeyboard = true;

            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
        }

第四步:钩子 子线程

在这之前我们先来看下子程要用的三个参数int类型的 nCode, Int32类型的 wParam, IntPtr类型的 lParam,nCode参数是钩子代码,钩子子程使用这个参数来确定任务。wParam和lParam参数包含了消息信息,我们可以从中提取需要的信息,为了将这两个参数转成我们更容易理解的消息,在这定义了一个结构体(对于键盘来说),将wParam和lParam转化成我们更加容易看懂的信息。

         //键盘结构
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
            public int scanCode; // 指定的硬件扫描码的关键
            public int flags;  // 键标志
            public int time; // 指定的时间戳记的这个讯息
            public int dwExtraInfo; // 指定额外信息相关的信息
        }
        private const int WM_KEYUP = 0x101;//KEYUP

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // 侦听键盘事件
            if (nCode >= 0 )
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                // 键盘抬起
                if (KeyUpEvent != null&&wParam == WM_KEYUP)
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    //if (keyData == Keys.Up)
                    //{ 
                        //使用EventHandler来传递数据
                        KeyEventArgs e = new KeyEventArgs(keyData);
                        KeyUpEvent(this, e);
                        //MessageBox.Show("捕捉到了按键释放");
                    //}
                }
            }
            //如果返回1,则结束消息,这个消息到此为止,不再传递。
            //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }

在上面的子程里面,我们使用EventHandler来向外部传递数据,那么我们就要先声明一个事件

        public event KeyEventHandler KeyUpEvent;

至此,钩子类已经基本完成,以下是整个类的代码:

    class KeyboardHook
    {
        public event KeyEventHandler KeyUpEvent;

        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
        static int hKeyboardHook = 0; //声明键盘钩子处理的初始值
        public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
        HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型

        //键盘结构
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254
            public int scanCode; // 指定的硬件扫描码的关键
            public int flags;  // 键标志
            public int time; // 指定的时间戳记的这个讯息
            public int dwExtraInfo; // 指定额外信息相关的信息
        }

        //使用此功能,安装了一个钩子
        [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")]
        static extern int GetCurrentThreadId();

        public void Start()
        {
            // 安装键盘钩子
            if (hKeyboardHook == 0)
            {
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0);
                //************************************
                //键盘线程钩子
                //SetWindowsHookEx(13, KeyboardHookProcedure, IntPtr.Zero, GetCurrentThreadId());//指定要监听的线程idGetCurrentThreadId(),

                //************************************
                //如果SetWindowsHookEx失败
                if (hKeyboardHook == 0)
                {
                    Stop();
                    throw new Exception("安装键盘钩子失败");
                }
            }
        }
        public void Stop()
        {
            bool retKeyboard = true;


            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }

            if (!(retKeyboard)) throw new Exception("卸载钩子失败!");
        }
        private const int WM_KEYUP = 0x101;//KEYUP

        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            // 侦听键盘事件
            if (nCode >= 0 )
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                // 键盘抬起
                if (KeyUpEvent != null&&wParam == WM_KEYUP)
                {
                    Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
                    //if (keyData == Keys.Up)
                    //{
                        KeyEventArgs e = new KeyEventArgs(keyData);
                        KeyUpEvent(this, e);
                        //MessageBox.Show("捕捉到了按键释放");
                    //}
                }
            }
            //如果返回1,则结束消息,这个消息到此为止,不再传递。
            //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者
            return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
        }
        ~KeyboardHook()
        {
            Stop();
        }
    }

第五步:接下来就是调用了。

首先我们声明数据传递事件 并且初始化钩子类

        private KeyEventHandler myKeyEventHandeler = null;//按键钩子
        private KeyboardHook k_hook = new KeyboardHook();

接下来,写两个函数分别负责安装和卸载钩子,其中hook_KeyUp是钩子捕捉到消息后具体需要实现的方法

         public void start()
         {
           myKeyEventHandeler = new KeyEventHandler(hook_KeyUp);
           k_hook.KeyUpEvent += myKeyEventHandeler;//钩住键按下
           k_hook.Start();//安装键盘钩子
         }

        public void stop()
         {
           if (myKeyEventHandeler != null)
           {
               k_hook.KeyUpEvent -= myKeyEventHandeler;//取消按键事件
               myKeyEventHandeler = null;
               k_hook.Stop();//关闭键盘钩子
           }
         }
         
        private void hook_KeyUp(object sender, KeyEventArgs e)
        {
            //  这里写具体实现
            textBox1.Text = "捕捉到了" + e.KeyCode.ToString() + "键释放";
        }

总结:突然发现没什么好说的,该说的都在上面了....

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐