Używanie globalnego haka klawiatury (WH_KEYBOARD_LL) w WPF / C #
Zszywałem ze sobą kod znaleziony w InternecieWH_KEYBOARD_LL
klasa pomocnicza:
Umieść następujący kod w niektórych bibliotekach utils, niech tak będzieYourUtils.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));
}
else if (wParam == (IntPtr)InterceptKeys.WM_KEYUP)
{
int vkCode = Marshal.ReadInt32(lParam);
if (KeyUp != null)
KeyUp(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 int WM_KEYUP = 0x0101;
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);
}
Które używam w ten sposób:
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();
}
}
Problem polega na tymprzestaje działać po chwilowym naciśnięciu klawiszy. Żaden błąd nie został podniesiony, po prostu po jakimś czasie niczego nie wyprowadzam. Nie mogę znaleźć solidnego wzoru, gdy przestaje działać.
Powielanie tego problemu jest proste, uderzaj niektóre klawisze jak szalony, zwykle za oknem.
Podejrzewam, że jest jakieś złoproblem z gwintowaniem z tyłu, ktoś wpadł na pomysł, jak to działa?
Co już próbowałem:
Zastępujereturn HookCallbackInner(nCode, wParam, lParam);
z czymś prostym.Zastępując go wywołaniem asynchronicznym, próbując umieścić Sleep 5000ms (itp.).Asynchroniczne wywołanie nie sprawiło, że działało lepiej, wydaje się, że zatrzymanie zawsze następuje, gdy użytkownik przez pewien czas trzyma jedną literę w dół.