鼠标和键盘操作的全局钩子库
注意只能用于AnyCPU、.NET版本一致、只能用于Winform。
简化后的代码如下:
using System; using System.ComponentModel; using System.Reflection; using System.Runtime.InteropServices; using System.Windows.Forms; namespace UserActivityMonitor { /// <summary> /// This class monitors all mouse activities globally (also outside of the application) /// and provides appropriate events. /// </summary> public static class HookManager { /// <summary> /// Occurs when the mouse pointer is moved. /// </summary> public static event MouseEventHandler MouseMove { add { EnsureSubscribedToGlobalMouseEvents(); s_MouseMove += value; } remove { s_MouseMove -= value; TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when a click was performed by the mouse. /// </summary> public static event MouseEventHandler MouseClick { add { EnsureSubscribedToGlobalMouseEvents(); s_MouseClick += value; } remove { s_MouseClick -= value; TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when the mouse a mouse button is pressed. /// </summary> public static event MouseEventHandler MouseDown { add { EnsureSubscribedToGlobalMouseEvents(); s_MouseDown += value; } remove { s_MouseDown -= value; TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when a mouse button is released. /// </summary> public static event MouseEventHandler MouseUp { add { EnsureSubscribedToGlobalMouseEvents(); s_MouseUp += value; } remove { s_MouseUp -= value; TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when the mouse wheel moves. /// </summary> public static event MouseEventHandler MouseWheel { add { EnsureSubscribedToGlobalMouseEvents(); s_MouseWheel += value; } remove { s_MouseWheel -= value; TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when a double clicked was performed by the mouse. /// </summary> public static event MouseEventHandler MouseDoubleClick { add { EnsureSubscribedToGlobalMouseEvents(); if (s_MouseDoubleClick == null) { s_DoubleClickTimer = new Timer { Interval = GetDoubleClickTime(), Enabled = false }; s_DoubleClickTimer.Tick += DoubleClickTimeElapsed; MouseUp += OnMouseUp; } s_MouseDoubleClick += value; } remove { if (s_MouseDoubleClick != null) { s_MouseDoubleClick -= value; if (s_MouseDoubleClick == null) { MouseUp -= OnMouseUp; s_DoubleClickTimer.Tick -= DoubleClickTimeElapsed; s_DoubleClickTimer = null; } } TryUnsubscribeFromGlobalMouseEvents(); } } /// <summary> /// Occurs when a key is released. /// </summary> public static event KeyEventHandler KeyUp { add { EnsureSubscribedToGlobalKeyboardEvents(); s_KeyUp += value; } remove { s_KeyUp -= value; TryUnsubscribeFromGlobalKeyboardEvents(); } } /// <summary> /// Occurs when a key is preseed. /// </summary> public static event KeyEventHandler KeyDown { add { EnsureSubscribedToGlobalKeyboardEvents(); s_KeyDown += value; } remove { s_KeyDown -= value; TryUnsubscribeFromGlobalKeyboardEvents(); } } /// <summary> /// Occurs when a key is pressed. /// </summary> /// <remarks> /// Key events occur in the following order: /// <list type="number"> /// <item>KeyDown</item> /// <item>KeyPress</item> /// <item>KeyUp</item> /// </list> ///The KeyPress event is not raised by noncharacter keys; however, the noncharacter keys do raise the KeyDown and KeyUp events. ///Use the KeyChar property to sample keystrokes at run time and to consume or modify a subset of common keystrokes. ///To handle keyboard events only in your application and not enable other applications to receive keyboard events, /// set the KeyPressEventArgs.Handled property in your form's KeyPress event-handling method to <b>true</b>. /// </remarks> public static event KeyPressEventHandler KeyPress { add { EnsureSubscribedToGlobalKeyboardEvents(); s_KeyPress += value; } remove { s_KeyPress -= value; TryUnsubscribeFromGlobalKeyboardEvents(); } } private static event MouseEventHandler s_MouseMove; private static event MouseEventHandler s_MouseClick; private static event MouseEventHandler s_MouseDown; private static event MouseEventHandler s_MouseUp; private static event MouseEventHandler s_MouseWheel; private static event MouseEventHandler s_MouseDoubleClick; private static MouseButtons s_PrevClickedButton; private static Timer s_DoubleClickTimer; private static void DoubleClickTimeElapsed(object sender, EventArgs e) { s_DoubleClickTimer.Enabled = false; s_PrevClickedButton = MouseButtons.None; } private static void OnMouseUp(object sender, MouseEventArgs e) { if (e.Clicks < 1) { return; } if (e.Button.Equals(s_PrevClickedButton)) { if (s_MouseDoubleClick != null) { s_MouseDoubleClick.Invoke(null, e); } s_DoubleClickTimer.Enabled = false; s_PrevClickedButton = MouseButtons.None; } else { s_DoubleClickTimer.Enabled = true; s_PrevClickedButton = e.Button; } } private static event KeyPressEventHandler s_KeyPress; private static event KeyEventHandler s_KeyUp; private static event KeyEventHandler s_KeyDown; private const int WH_MOUSE_LL = 14; private const int WH_KEYBOARD_LL = 13; private const int WM_LBUTTONDOWN = 0x201; private const int WM_RBUTTONDOWN = 0x204; private const int WM_LBUTTONUP = 0x202; private const int WM_RBUTTONUP = 0x205; private const int WM_LBUTTONDBLCLK = 0x203; private const int WM_RBUTTONDBLCLK = 0x206; private const int WM_MOUSEWHEEL = 0x020A; private const int WM_KEYDOWN = 0x100; private const int WM_KEYUP = 0x101; private const int WM_SYSKEYDOWN = 0x104; private const int WM_SYSKEYUP = 0x105; private const byte VK_SHIFT = 0x10; private const byte VK_CAPITAL = 0x14; [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern int CallNextHookEx( int idHook, int nCode, int wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] private static extern int SetWindowsHookEx( int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] private static extern int UnhookWindowsHookEx(int idHook); [DllImport("user32")] private static extern int GetDoubleClickTime(); [DllImport("user32")] private static extern int ToAscii( int uVirtKey, int uScanCode, byte[] lpbKeyState, byte[] lpwTransKey, int fuState); [DllImport("user32")] private static extern int GetKeyboardState(byte[] pbKeyState); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern short GetKeyState(int vKey); [StructLayout(LayoutKind.Sequential)] private struct Point { internal readonly int X; internal readonly int Y; } [StructLayout(LayoutKind.Sequential)] private struct MouseLLHookStruct { internal Point Point; internal readonly int MouseData; private readonly int Flags; private readonly int Time; private readonly int ExtraInfo; } [StructLayout(LayoutKind.Sequential)] private struct KeyboardHookStruct { internal readonly int VirtualKeyCode; internal readonly int ScanCode; internal readonly int Flags; private readonly int Time; private readonly int ExtraInfo; } private delegate int HookProc(int nCode, int wParam, IntPtr lParam); private static HookProc s_MouseDelegate; private static int s_MouseHookHandle; private static int m_OldX; private static int m_OldY; private static int MouseHookProc(int nCode, int wParam, IntPtr lParam) { if (nCode >= 0) { var mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct)); var button = MouseButtons.None; short mouseDelta = 0; var clickCount = 0; var mouseDown = false; var mouseUp = false; switch (wParam) { case WM_LBUTTONDOWN: mouseDown = true; button = MouseButtons.Left; clickCount = 1; break; case WM_LBUTTONUP: mouseUp = true; button = MouseButtons.Left; clickCount = 1; break; case WM_LBUTTONDBLCLK: button = MouseButtons.Left; clickCount = 2; break; case WM_RBUTTONDOWN: mouseDown = true; button = MouseButtons.Right; clickCount = 1; break; case WM_RBUTTONUP: mouseUp = true; button = MouseButtons.Right; clickCount = 1; break; case WM_RBUTTONDBLCLK: button = MouseButtons.Right; clickCount = 2; break; case WM_MOUSEWHEEL: mouseDelta = (short)((mouseHookStruct.MouseData >> 16) & 0xffff); break; } var e = new MouseEventExtArgs( button, clickCount, mouseHookStruct.Point.X, mouseHookStruct.Point.Y, mouseDelta); if (s_MouseUp != null && mouseUp) { s_MouseUp.Invoke(null, e); } if (s_MouseDown != null && mouseDown) { s_MouseDown.Invoke(null, e); } if (s_MouseClick != null && clickCount > 0) { s_MouseClick.Invoke(null, e); } if (s_MouseDoubleClick != null && clickCount == 2) { s_MouseDoubleClick.Invoke(null, e); } if (s_MouseWheel != null && mouseDelta != 0) { s_MouseWheel.Invoke(null, e); } if ((s_MouseMove != null) && (m_OldX != mouseHookStruct.Point.X || m_OldY != mouseHookStruct.Point.Y)) { m_OldX = mouseHookStruct.Point.X; m_OldY = mouseHookStruct.Point.Y; if (s_MouseMove != null) { s_MouseMove.Invoke(null, e); } } if (e.Handled) { return -1; } } return CallNextHookEx(s_MouseHookHandle, nCode, wParam, lParam); } private static void EnsureSubscribedToGlobalMouseEvents() { if (s_MouseHookHandle != 0) { return; } s_MouseDelegate = MouseHookProc; s_MouseHookHandle = SetWindowsHookEx( WH_MOUSE_LL, s_MouseDelegate, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0); if (s_MouseHookHandle != 0) { return; } throw new Win32Exception(Marshal.GetLastWin32Error()); } private static void TryUnsubscribeFromGlobalMouseEvents() { if (s_MouseClick == null && s_MouseDown == null && s_MouseMove == null && s_MouseUp == null && s_MouseWheel == null) { ForceUnsunscribeFromGlobalMouseEvents(); } } private static void ForceUnsunscribeFromGlobalMouseEvents() { if (s_MouseHookHandle == 0) { return; } var result = UnhookWindowsHookEx(s_MouseHookHandle); s_MouseHookHandle = 0; s_MouseDelegate = null; if (result != 0) { return; } throw new Win32Exception(Marshal.GetLastWin32Error()); } private static HookProc s_KeyboardDelegate; private static int s_KeyboardHookHandle; private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) { var handled = false; if (nCode >= 0) { var MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct)); if (s_KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) { var keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode; var e = new KeyEventArgs(keyData); s_KeyDown.Invoke(null, e); handled = e.Handled; } if (s_KeyPress != null && wParam == WM_KEYDOWN) { var isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80); var isDownCapslock = (GetKeyState(VK_CAPITAL) != 0); var keyState = new byte[256]; GetKeyboardState(keyState); var inBuffer = new byte[2]; if (ToAscii(MyKeyboardHookStruct.VirtualKeyCode, MyKeyboardHookStruct.ScanCode, keyState, inBuffer, MyKeyboardHookStruct.Flags) == 1) { var key = (char)inBuffer[0]; if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key)) key = Char.ToUpper(key); var e = new KeyPressEventArgs(key); s_KeyPress.Invoke(null, e); handled = handled || e.Handled; } } if (s_KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)) { var keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode; var e = new KeyEventArgs(keyData); s_KeyUp.Invoke(null, e); handled = handled || e.Handled; } } if (handled) { return -1; } return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam); } private static void EnsureSubscribedToGlobalKeyboardEvents() { if (s_KeyboardHookHandle != 0) { return; } s_KeyboardDelegate = KeyboardHookProc; s_KeyboardHookHandle = SetWindowsHookEx( WH_KEYBOARD_LL, s_KeyboardDelegate, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0); if (s_KeyboardHookHandle != 0) { return; } throw new Win32Exception(Marshal.GetLastWin32Error()); } private static void TryUnsubscribeFromGlobalKeyboardEvents() { if (s_KeyDown != null || s_KeyUp != null || s_KeyPress != null) { return; } if (s_KeyboardHookHandle == 0) { return; } var result = UnhookWindowsHookEx(s_KeyboardHookHandle); s_KeyboardHookHandle = 0; s_KeyboardDelegate = null; if (result != 0) { return; } throw new Win32Exception(Marshal.GetLastWin32Error()); } } internal class MouseEventExtArgs : MouseEventArgs { internal MouseEventExtArgs(MouseButtons buttons, int clicks, int x, int y, int delta) : base(buttons, clicks, x, y, delta) { } internal bool Handled { get; set; } } }