在WPF / C#中使用全局键盘钩子(WH_KEYBOARD_LL)
我从互联网上find的代码我自己缝合在一起WH_KEYBOARD_LL
帮手类:
把下面的代码放到一些utils库中,让它成为YourUtils.cs :
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Windows.Input; namespace MYCOMPANYHERE.WPF.KeyboardHelper { public class KeyboardListener : IDisposable { private static IntPtr hookId = IntPtr.Zero; [MethodImpl(MethodImplOptions.NoInlining)] private IntPtr HookCallback( int nCode, IntPtr wParam, IntPtr lParam) { try { return HookCallbackInner(nCode, wParam, lParam); } catch { Console.WriteLine("There was some error somewhere..."); } return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); } private IntPtr HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN) { int vkCode = Marshal.ReadInt32(lParam); if (KeyDown != null) KeyDown(this, new RawKeyEventArgs(vkCode, false)); } } return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam); } public event RawKeyEventHandler KeyDown; public event RawKeyEventHandler KeyUp; public KeyboardListener() { hookId = InterceptKeys.SetHook((InterceptKeys.LowLevelKeyboardProc)HookCallback); } ~KeyboardListener() { Dispose(); } #region IDisposable Members public void Dispose() { InterceptKeys.UnhookWindowsHookEx(hookId); } #endregion } internal static class InterceptKeys { public delegate IntPtr LowLevelKeyboardProc( int nCode, IntPtr wParam, IntPtr lParam); public static int WH_KEYBOARD_LL = 13; public static int WM_KEYDOWN = 0x0100; public static IntPtr SetHook(LowLevelKeyboardProc proc) { using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) { return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0); } } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr GetModuleHandle(string lpModuleName); } public class RawKeyEventArgs : EventArgs { public int VKCode; public Key Key; public bool IsSysKey; public RawKeyEventArgs(int VKCode, bool isSysKey) { this.VKCode = VKCode; this.IsSysKey = isSysKey; this.Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey(VKCode); } } public delegate void RawKeyEventHandler(object sender, RawKeyEventArgs args); }
我使用这样的:
App.xaml :
<Application ... Startup="Application_Startup" Exit="Application_Exit"> ...
App.xaml.cs :
public partial class App : Application { KeyboardListener KListener = new KeyboardListener(); private void Application_Startup(object sender, StartupEventArgs e) { KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown); } void KListener_KeyDown(object sender, RawKeyEventArgs args) { Console.WriteLine(args.Key.ToString()); // I tried writing the data in file here also, to make sure the problem is not in Console.WriteLine } private void Application_Exit(object sender, ExitEventArgs e) { KListener.Dispose(); } }
问题是,按一下键后停止工作 。 没有错误发生,所以我只是没有得到任何东西输出一段时间后。 停止工作时,我找不到稳定的模式。
重现这个问题很简单,像一个疯子一样打一些钥匙,通常在窗外。
我怀疑背后有一些邪恶的线程问题 ,谁知道如何保持这个工作?
我已经尝试过了:
- replace
return HookCallbackInner(nCode, wParam, lParam);
用简单的东西。 - 用asynchronous调用replace它,试图把睡眠5000ms(等)。
asynchronous调用并没有使它工作得更好,当用户将单个字母保持一段时间时,似乎总是停止。
您正在SetHook方法调用中内联创build您的callback委托。 这个委托最终会被垃圾收集,因为你没有在任何地方引用它。 一旦这个代表被垃圾收集,你将不会再有任何callback。
为了防止这种情况,只要挂钩处于就位状态(直到调用UnhookWindowsHookEx),就需要保持对委托的引用。
获胜者是: 在WPF中捕获键盘input ,这表明:
TextCompositionManager.AddTextInputHandler(this, new TextCompositionEventHandler(OnTextComposition));
…然后简单地使用事件处理参数的Text属性:
private void OnTextComposition(object sender, TextCompositionEventArgs e) { string key = e.Text; ... }
IIRC,当使用全局钩子时,如果你的DLL没有足够快的从callback中返回,那么你将从callback链中移除。
所以如果你说它工作了一段时间,但是如果你input得太快,它会停止工作,我可能会build议把密钥存储到内存中的某个位置,然后再把密钥丢掉。 举个例子,你可能会检查一些键盘logging器的源代码,因为他们使用相同的技术。
虽然这可能无法直接解决您的问题,但至less应排除一种可能性。
你有没有想过使用GetAsyncKeyState
而不是全局钩子来logging按键? 对于你的应用程序来说,这可能就足够了,有很多完全实现的例子,并且更容易实现。
我真的在找这个 感谢您在这里张贴这个。
现在,当我testing你的代码时,我发现了一些bug。 代码一开始并不起作用。 它不能处理两个button点击即: CTRL + P。
我已经改变了这些值看下面:
private void HookCallbackInner
来
private void HookCallbackInner(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { if (wParam == (IntPtr)InterceptKeys.WM_KEYDOWN) { int vkCode = Marshal.ReadInt32(lParam); if (KeyDown != null) KeyDown(this, new RawKeyEventArgs(vkCode, false)); } } } using System; using System.Collections.Generic; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using FileManagerLibrary.Objects; namespace FileCommandManager { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { readonly KeyboardListener _kListener = new KeyboardListener(); private DispatcherTimer tm; private void Application_Startup(object sender, StartupEventArgs e) { _kListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown); } private List<Key> _keysPressedIntowSecound = new List<Key>(); private void TmBind() { tm = new DispatcherTimer(); tm.Interval = new TimeSpan(0, 0, 2); tm.IsEnabled = true; tm.Tick += delegate(object sender, EventArgs args) { tm.Stop(); tm.IsEnabled = false; _keysPressedIntowSecound = new List<Key>(); }; tm.Start(); } void KListener_KeyDown(object sender, RawKeyEventArgs args) { var text = args.Key.ToString(); var m = args; _keysPressedIntowSecound.Add(args.Key); if (tm == null || !tm.IsEnabled) TmBind(); } private void Application_Exit(object sender, ExitEventArgs e) { _kListener.Dispose(); } } }
此代码在Windows 10中为我工作100%:)我希望这对你有所帮助
我已经使用Dylan的方法在WPF应用程序中钩住全局关键字,并在每次按键后刷新钩子,以防止点击几下后事件停止触发。 IDK,如果是好的或坏的做法,但完成工作。
_listener.UnHookKeyboard(); _listener.HookKeyboard();
实现细节在这里