C#键盘钩子实现全局快捷键本文介绍如何使用Win32Api创建键盘钩子,并编写一个使用全局快捷键的实例。
工具/原料Visual Studio(本文使用VS2013,其他版本亦可)。
基础知识1使用钩子之前,需要使用SetWindowsHookEx()函数创建钩子,使用完毕之后要UnhookWindowsHookEx()函数卸载钩子,“钩”到消息后操作系统会自动调用在创建钩子时注册的回调函数来处理消息,处理完后调用CallNextHookEx()函数等待或处理下一条消息。
有关钩子的详细信息请见参考--C#鼠标钩子,其中已介绍。
2对于键盘钩子,钩子类型为WH_KEYBOARD_LL=13,只需要设置SetWindowsHookEx的idHook 参数为13即可“钩”到键盘消息。
关于钩子类型的资料见参考资料--钩子类型。
END键盘钩子实例启动VS,新建C# WinForm项目,命名为“Cs键盘钩子”,如下:对主窗口布局,如下:添加Win32Api引用,代码如下:using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;using System.Reflection;using System.Threading;using System.Windows.Forms;using System.Diagnostics;public class Win32Api{#region常数和结构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;}#endregion#region Apipublic 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, Int32wParam, IntPtr lParam);[DllImport("user32")]public static extern int ToAscii(int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState);[DllImport("user32")]public static extern int GetKeyboardState(byte[] pbKeyState);[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern IntPtr GetModuleHandle(string lpModuleName);#endregion//添加新建类KeyboardHook,封装键盘钩子,代码如下:public class KeyboardHook{int hHook;Win32Api.HookProc KeyboardHookDelegate;public event KeyEventHandler OnKeyDownEvent;public event KeyEventHandler OnKeyUpEvent;public event KeyPressEventHandler OnKeyPressEvent;public KeyboardHook() { }public void SetHook(){KeyboardHookDelegate = new Win32Api.HookProc(KeyboardHookProc);Process cProcess = Process.GetCurrentProcess();ProcessModule cModule = cProcess.MainModule;var mh = Win32Api.GetModuleHandle(cModule.ModuleName);hHook = Win32Api.SetWindowsHookEx(Win32Api.WH_KEYBOARD_LL, KeyboardHookDelegate, mh, 0);}public void UnHook(){Win32Api.UnhookWindowsHookEx(hHook);}private List<Keys> preKeysList = new List<Keys>();//存放被按下的控制键,用来生成具体的键private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) {//如果该消息被丢弃(nCode<0)或者没有事件绑定处理程序则不会触发事件if((nCode >= 0) && (OnKeyDownEvent != null|| OnKeyUpEvent != null || OnKeyPressEvent != null)){Win32Api.KeyboardHookStruct KeyDataFromHook = (Win32Api.KeyboardHookStruct)Marshal.PtrToStructure(lParam,typeof(Win32Api.KeyboardHookStruct));Keys keyData = (Keys)KeyDataFromHook.vkCode;//按下控制键if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN)){if(IsCtrlAltShiftKeys(keyData) && preKeysList.IndexOf(keyData) == -1){preKeysList.Add(keyData);}}//WM_KEYDOWN和WM_SYSKEYDOWN消息,将会引发OnKeyDownEvent事件if (OnKeyDownEvent != null && (wParam == Win32Api.WM_KEYDOWN || wParam == Win32Api.WM_SYSKEYDOWN)){KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData));OnKeyDownEvent(this, e);}//WM_KEYDOWN消息将引发OnKeyPressEventif(OnKeyPressEvent != null&& wParam == Win32Api.WM_KEYDOWN) {byte[] keyState = new byte[256];Win32Api.GetKeyboardState(keyState);byte[] inBuffer = new byte[2];if(Win32Api.ToAscii(KeyDataFromHook.vkCode, KeyDataFromHook.scanCode, keyState, inBuffer, KeyDataFromHook.flags) == 1){KeyPressEventArgs e = new KeyPressEventArgs((char)inBuffer[0]);OnKeyPressEvent(this, e);}}//松开控制键if ((OnKeyDownEvent != null || OnKeyPressEvent != null) && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP)){if (IsCtrlAltShiftKeys(keyData)){for (int i = preKeysList.Count - 1; i >= 0; i--){if(preKeysList[i] == keyData) { preKeysList.RemoveAt(i); }}}}//WM_KEYUP和WM_SYSKEYUP消息,将引发OnKeyUpEvent事件if (OnKeyUpEvent != null && (wParam == Win32Api.WM_KEYUP || wParam == Win32Api.WM_SYSKEYUP)){KeyEventArgs e = new KeyEventArgs(GetDownKeys(keyData)); OnKeyUpEvent(this, e);}}return Win32Api.CallNextHookEx(hHook, nCode, wParam, lParam); }//根据已经按下的控制键生成keyprivate Keys GetDownKeys(Keys key){Keys rtnKey = Keys.None;foreach (Keys i in preKeysList){if (i == Keys.LControlKey || i == Keys.RControlKey) { rtnKey = rtnKey | Keys.Control; }if (i == Keys.LMenu || i == Keys.RMenu) { rtnKey = rtnKey | Keys.Alt; }if (i == Keys.LShiftKey || i == Keys.RShiftKey) { rtnKey = rtnKey | Keys.Shift; }}return rtnKey | key;}private Boolean IsCtrlAltShiftKeys(Keys key){if (key == Keys.LControlKey || key == Keys.RControlKey || key == Keys.LMenu || key == Keys.RMenu || key == Keys.LShiftKey || key == Keys.RShiftKey) { return true; }return false;}}}在主窗体中添加代码,如下:using System;using System.Collections.Generic;using ponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Drawing.Printing;namespace条形public partial class Form1 : Form{public Form1(){InitializeComponent();}Win32Api.KeyboardHook kh;private void Form1_Load(object sender, EventArgs e){kh = new Win32Api.KeyboardHook();kh.SetHook();kh.OnKeyDownEvent += kh_OnKeyDownEvent;}void kh_OnKeyDownEvent(object sender, KeyEventArgs e){if (e.KeyData == (Keys.S | Keys.Control)) { this.Show(); }//Ctrl+S 显示窗口if (e.KeyData == (Keys.H | Keys.Control)) { this.Hide(); }//Ctrl+H 隐藏窗口if(e.KeyData == (Keys.C | Keys.Control)) { this.Close(); }//Ctrl+C 关闭窗口if (e.KeyData == (Keys.A | Keys.Control | Keys.Alt)) { this.Text = "你发现了什么?"; }//Ctrl+Alt+A}private void Form1_FormClosing(object sender, FormClosingEventArgs e) {kh.UnHook();}}}代码添加完毕后,运行调试。