2009-03-02 9 views
54

Chcę przechwycić skrót klawiaturowy w mojej aplikacji i uruchomić okno dialogowe, które pojawi się, jeśli użytkownik naciśnie kombinację klawiszy nawet poza aplikacją. Podobnie jak Ctrl, Ctrl w Google Desktop Search, aby otworzyć okno wyszukiwania.Globalne przechwytywanie klawiatury w aplikacji C#

Próbowałem użyć niektórych modułów hakowania klawiatury, które w zasadzie używają Win32 Interop, aby uzyskać ten efekt, ale każda implementacja, którą próbowałem przywiązuje klawiaturę do pewnego stopnia, do miejsca, w którym zaczynasz robić dziwne zachowania, gdy aplikacja robi coś intensywny. Takich jak ładowanie dużej ilości danych, to spowodowałoby zablokowanie klawiatury i myszy.

Szukam lekkiego rozwiązania, które umożliwiłoby to wykonanie tego zadania bez przymuszania klawiatury i myszy.

+0

możesz określić, które moduły już wypróbowałeś. – Stormenet

+0

Zobacz http://stackoverflow.com/questions/81150/best-way-to-tackle-global-hotkey-processing-in-c/2611761#2611761 –

Odpowiedz

63

Stephen Toub wrote a great article na wdrażanie globalnych zaczepy klawiatury w C#:

using System; 
using System.Diagnostics; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

class InterceptKeys 
{ 
    private const int WH_KEYBOARD_LL = 13; 
    private const int WM_KEYDOWN = 0x0100; 
    private static LowLevelKeyboardProc _proc = HookCallback; 
    private static IntPtr _hookID = IntPtr.Zero; 

    public static void Main() 
    { 
     _hookID = SetHook(_proc); 
     Application.Run(); 
     UnhookWindowsHookEx(_hookID); 
    } 

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

    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); 

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 
    { 
     if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) 
     { 
      int vkCode = Marshal.ReadInt32(lParam); 
      Console.WriteLine((Keys)vkCode); 
     } 

     return CallNextHookEx(_hookID, nCode, wParam, lParam); 
    } 

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId); 

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    [return: MarshalAs(UnmanagedType.Bool)] 
    private static extern bool UnhookWindowsHookEx(IntPtr hhk); 

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); 

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr GetModuleHandle(string lpModuleName); 
} 
+1

Jak mogę korzystać z tej klasy? – VAAA

19

Oto mój kod, który działa:

using System; 
using System.ComponentModel; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 

namespace SnagFree.TrayApp.Core 
{ 
    class GlobalKeyboardHookEventArgs : HandledEventArgs 
    { 
     public GlobalKeyboardHook.KeyboardState KeyboardState { get; private set; } 
     public GlobalKeyboardHook.LowLevelKeyboardInputEvent KeyboardData { get; private set; } 

     public GlobalKeyboardHookEventArgs(
      GlobalKeyboardHook.LowLevelKeyboardInputEvent keyboardData, 
      GlobalKeyboardHook.KeyboardState keyboardState) 
     { 
      KeyboardData = keyboardData; 
      KeyboardState = keyboardState; 
     } 
    } 

    //Based on https://gist.github.com/Stasonix 
    class GlobalKeyboardHook : IDisposable 
    { 
     public event EventHandler<GlobalKeyboardHookEventArgs> KeyboardPressed; 

     public GlobalKeyboardHook() 
     { 
      _windowsHookHandle = IntPtr.Zero; 
      _user32LibraryHandle = IntPtr.Zero; 
      _hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour. 

      _user32LibraryHandle = LoadLibrary("User32"); 
      if (_user32LibraryHandle == IntPtr.Zero) 
      { 
       int errorCode = Marshal.GetLastWin32Error(); 
       throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); 
      } 



      _windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0); 
      if (_windowsHookHandle == IntPtr.Zero) 
      { 
       int errorCode = Marshal.GetLastWin32Error(); 
       throw new Win32Exception(errorCode, $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); 
      } 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       // because we can unhook only in the same thread, not in garbage collector thread 
       if (_windowsHookHandle != IntPtr.Zero) 
       { 
        if (!UnhookWindowsHookEx(_windowsHookHandle)) 
        { 
         int errorCode = Marshal.GetLastWin32Error(); 
         throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); 
        } 
        _windowsHookHandle = IntPtr.Zero; 

        // ReSharper disable once DelegateSubtraction 
        _hookProc -= LowLevelKeyboardProc; 
       } 
      } 

      if (_user32LibraryHandle != IntPtr.Zero) 
      { 
       if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1. 
       { 
        int errorCode = Marshal.GetLastWin32Error(); 
        throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}."); 
       } 
       _user32LibraryHandle = IntPtr.Zero; 
      } 
     } 

     ~GlobalKeyboardHook() 
     { 
      Dispose(false); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     private IntPtr _windowsHookHandle; 
     private IntPtr _user32LibraryHandle; 
     private HookProc _hookProc; 

     delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); 

     [DllImport("kernel32.dll")] 
     private static extern IntPtr LoadLibrary(string lpFileName); 

     [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 
     private static extern bool FreeLibrary(IntPtr hModule); 

     /// <summary> 
     /// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. 
     /// You would install a hook procedure to monitor the system for certain types of events. These events are 
     /// associated either with a specific thread or with all threads in the same desktop as the calling thread. 
     /// </summary> 
     /// <param name="idHook">hook type</param> 
     /// <param name="lpfn">hook procedure</param> 
     /// <param name="hMod">handle to application instance</param> 
     /// <param name="dwThreadId">thread identifier</param> 
     /// <returns>If the function succeeds, the return value is the handle to the hook procedure.</returns> 
     [DllImport("USER32", SetLastError = true)] 
     static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId); 

     /// <summary> 
     /// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function. 
     /// </summary> 
     /// <param name="hhk">handle to hook procedure</param> 
     /// <returns>If the function succeeds, the return value is true.</returns> 
     [DllImport("USER32", SetLastError = true)] 
     public static extern bool UnhookWindowsHookEx(IntPtr hHook); 

     /// <summary> 
     /// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain. 
     /// A hook procedure can call this function either before or after processing the hook information. 
     /// </summary> 
     /// <param name="hHook">handle to current hook</param> 
     /// <param name="code">hook code passed to hook procedure</param> 
     /// <param name="wParam">value passed to hook procedure</param> 
     /// <param name="lParam">value passed to hook procedure</param> 
     /// <returns>If the function succeeds, the return value is true.</returns> 
     [DllImport("USER32", SetLastError = true)] 
     static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam); 

     [StructLayout(LayoutKind.Sequential)] 
     public struct LowLevelKeyboardInputEvent 
     { 
      /// <summary> 
      /// A virtual-key code. The code must be a value in the range 1 to 254. 
      /// </summary> 
      public int VirtualCode; 

      /// <summary> 
      /// A hardware scan code for the key. 
      /// </summary> 
      public int HardwareScanCode; 

      /// <summary> 
      /// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level. 
      /// </summary> 
      public int Flags; 

      /// <summary> 
      /// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message. 
      /// </summary> 
      public int TimeStamp; 

      /// <summary> 
      /// Additional information associated with the message. 
      /// </summary> 
      public IntPtr AdditionalInformation; 
     } 

     public const int WH_KEYBOARD_LL = 13; 
     //const int HC_ACTION = 0; 

     public enum KeyboardState 
     { 
      KeyDown = 0x0100, 
      KeyUp = 0x0101, 
      SysKeyDown = 0x0104, 
      SysKeyUp = 0x0105 
     } 

     public const int VkSnapshot = 0x2c; 
     //const int VkLwin = 0x5b; 
     //const int VkRwin = 0x5c; 
     //const int VkTab = 0x09; 
     //const int VkEscape = 0x18; 
     //const int VkControl = 0x11; 
     const int KfAltdown = 0x2000; 
     public const int LlkhfAltdown = (KfAltdown >> 8); 

     public IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam) 
     { 
      bool fEatKeyStroke = false; 

      var wparamTyped = wParam.ToInt32(); 
      if (Enum.IsDefined(typeof(KeyboardState), wparamTyped)) 
      { 
       object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent)); 
       LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o; 

       var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wparamTyped); 

       EventHandler<GlobalKeyboardHookEventArgs> handler = KeyboardPressed; 
       handler?.Invoke(this, eventArguments); 

       fEatKeyStroke = eventArguments.Handled; 
      } 

      return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); 
     } 
    } 
} 

Użycie:

using System; 
using System.Windows.Forms; 

namespace SnagFree.TrayApp.Core 
{ 
    internal class Controller : IDisposable 
    { 
     private GlobalKeyboardHook _globalKeyboardHook; 

     public void SetupKeyboardHooks() 
     { 
      _globalKeyboardHook = new GlobalKeyboardHook(); 
      _globalKeyboardHook.KeyboardPressed += OnKeyPressed; 
     } 

     private void OnKeyPressed(object sender, GlobalKeyboardHookEventArgs e) 
     { 
      //Debug.WriteLine(e.KeyboardData.VirtualCode); 

      if (e.KeyboardData.VirtualCode != GlobalKeyboardHook.VkSnapshot) 
       return; 

      // seems, not needed in the life. 
      //if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown && 
      // e.KeyboardData.Flags == GlobalKeyboardHook.LlkhfAltdown) 
      //{ 
      // MessageBox.Show("Alt + Print Screen"); 
      // e.Handled = true; 
      //} 
      //else 

      if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown) 
      { 
       MessageBox.Show("Print Screen"); 
       e.Handled = true; 
      } 
     } 

     public void Dispose() 
     { 
      _globalKeyboardHook?.Dispose(); 
     } 
    } 
} 
+2

Waw, to jest świetne! Możesz nawet przechwycić alt-F4 i uniemożliwić zamknięcie aplikacji. Użyłeś nawet C# 6.0 w twoim przykładzie :) – Bigjim

+0

Dzięki! Użyłem tego kodu i działa. Ale jeśli po chwili naciskam klawisz, istnieje wyjątek mówiący, że delegat jest zbierany śmieci, a zarządzany kod powinien utrzymywać go przy życiu. następnie występuje zerowy wyjątek odwołania. czy możesz mi z tym pomóc ? – Golnar

+0

Hi @Golnar, mam również ten problem. Upewnij się, że nie masz tam żadnych statycznych rzeczy, jest to bardzo niebezpieczne w kontekście zbierania śmieci. Zwłaszcza wydarzenie. –

Powiązane problemy