2009-10-28 15 views
50

que unirán de código que encontré en Internet mí clase WH_KEYBOARD_LL ayudante:El uso de gancho de teclado globales (WH_KEYBOARD_LL) en WPF/C#

Coloque el código siguiente a algunos de sus bibliotecas utils, que sea 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); 
} 

que utilizo como esto:

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(); 
    } 
} 

El problema es que deja de funcionar después de golpear las teclas de un tiempo. No se genera ningún error de todos modos, simplemente no obtengo nada después de un tiempo. No puedo encontrar un patrón sólido cuando deja de funcionar.

La reproducción de este problema es muy simple, toca algunas teclas como un loco, por lo general fuera de la ventana.

Sospecho que hay algún mal problema de subprocesamiento detrás, ¿alguien tiene idea de cómo mantener esto funcionando?


lo que ya intentado:

  1. Sustitución return HookCallbackInner(nCode, wParam, lParam); con algo sencillo.
  2. Reemplazando con llamada asincrónica, tratando de poner Sleep 5000ms (etc.).

La llamada asincrónica no hizo que funcionara mejor, parece detenerse siempre cuando el usuario mantiene una sola letra hacia abajo por un tiempo.

+0

podemos interceptar la clave y enviar una clave diferente en lugar del prensado? Por ejemplo, al presionar a se envía la tecla e. – Thunder

+1

Completo, utilizable y documentado. Esto es lo que me gusta en StackOverflow – mico

+1

Quizás esta es una nueva versión, con código agregado para convertir las claves virtuales en cadena, justo lo que estaba por agregar. Al menos, si el interés que obtuve de Google es correcto. http://mureakuha.com/koodikirjasto/1166 – mico

Respuesta

13

Está creando su delegado de devolución de llamada en línea en la llamada al método SetHook. Ese delegado eventualmente obtendrá basura recolectada, ya que no está guardando una referencia en ninguna parte. Y una vez que el delegado es basura recolectada, no recibirá más devoluciones de llamada.

Para evitar eso, debe mantener viva una referencia al delegado mientras el gancho esté en su lugar (hasta que llame a UnhookWindowsHookEx).

+1

¡Sí! Tiene toda la razón, le daré la solución a la pregunta, ya que alguien más podría venir aquí a reflexionar, ojalá no le importe ... – Ciantic

+0

¿podemos interceptar la tecla y enviar una tecla diferente en lugar de la presionada? Por ejemplo, al presionar a se envía la tecla e. – Thunder

+0

No sé por qué, pero parece que esto no funciona en modo de depuración en Microsoft Visual Studio 2010. ¿Alguna idea? –

1

IIRC, al usar ganchos globales, si su DLL no regresa de la devolución de llamada lo suficientemente rápido, se lo elimina de la cadena de devolución de llamadas.

Así que si dices que está funcionando un poco, pero si escribes demasiado rápido deja de funcionar, podría sugerir simplemente almacenar las teclas en algún lugar de la memoria y tirar las teclas más tarde. Por ejemplo, puede verificar la fuente de algunos registradores de pulsaciones, ya que usan esta misma técnica.

Si bien esto puede no resolver su problema directamente, al menos debería descartar una posibilidad.

¿Ha pensado en usar GetAsyncKeyState en lugar de un gancho global para registrar las pulsaciones de teclas? Para su aplicación, podría ser suficiente, hay muchos ejemplos completamente implementados, y fue personalmente más fácil de implementar.

+0

No, se supone que es una aplicación de snippetter universal. GetAsyncKeyState no funciona ... Esto debería funcionar como sniffer de teclado. Por cierto, tu tono es degradante, he hecho lo mismo en C, y funciona como debe ser. Pero voy a seguir tu consejo e investigar este asunto "lo suficientemente rápido" – Ciantic

+2

Me disculpo si mi tono era insultante, no fue intencional. Solo sé que usando GetAsyncKeyState como mi keyl ogger fue mucho más fácil de usar que usar un gancho global debido a problemas de enhebrado/cadena/almacenamiento. – mrduclaw

+0

GetAsyncKeyState no funciona en los sistemas operativos multitarea. Está diseñado para aplicaciones win3.x cuando ningún otro programa puede llamar a GetAsyncKeyState y recibir el bit "recientemente presionado" en lugar de su aplicación. –

2

El ganador es: Capture Keyboard Input in WPF, lo que sugiere hacer:

TextCompositionManager.AddTextInputHandler(this, 
    new TextCompositionEventHandler(OnTextComposition)); 

... y luego simplemente utilizar la propiedad Text del argumento de controlador de eventos:

private void OnTextComposition(object sender, TextCompositionEventArgs e) 
{ 
    string key = e.Text; 
    ... 
} 
+1

El enlace solitario es malo. –

+0

Lo intenté y no funciona –

0

que realmente estaba buscando esto. Gracias por publicar esto aquí.
Ahora, cuando probé tu código encontré algunos errores. El código no funcionó al principio. Y no pudo manejar dos botones, es decir .: CTRL + P.
Lo que he cambiado son esos valores se ven a continuación:
private void HookCallbackInner a

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(); 
     } 
    } 
} 

trabajo este código 100% en Windows 10 para mí :) espero que esta ayuda u

0

He utilizado el Dylan's method para enganchar palabra clave global en la aplicación WPF y refrescar el enlace después de cada pulsación de tecla para evitar que los eventos dejen de disparar después de unos pocos clics. IDK, si es buena o mala práctica, pero hace el trabajo.

 _listener.UnHookKeyboard(); 
     _listener.HookKeyboard(); 

Los detalles de implementación here

Cuestiones relacionadas