Cattura della tastiera globale nell’applicazione C #

Voglio catturare una scorciatoia da tastiera nella mia applicazione e far scattare una finestra di dialogo che appare se l’utente preme una tastiera combinata anche al di fuori dell’app. Simile a Ctrl di Google Desktop Search, Ctrl per far apparire la finestra di dialogo di ricerca.

Ho provato a utilizzare alcuni moduli di aghook tastiera che utilizzano fondamentalmente l’interoperabilità di Win32 per ottenere questo effetto, ma ogni implementazione che ho provato lega la tastiera in una certa misura a dove inizi a comportarti in modo strano quando l’applicazione sta facendo qualcosa di intensivo. Come caricare una grande quantità di dati, questo causerebbe il blocco della tastiera e del mouse.

Sto cercando una soluzione leggera che permetta di fare questo senza stringere la tastiera e il mouse.

Stephen Toub ha scritto un grande articolo sull’implementazione di aghook tastiera globale in 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); } 

Ecco il mio codice che funziona:

 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 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); ///  /// 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. ///  /// hook type /// hook procedure /// handle to application instance /// thread identifier /// If the function succeeds, the return value is the handle to the hook procedure. [DllImport("USER32", SetLastError = true)] static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId); ///  /// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function. ///  /// handle to hook procedure /// If the function succeeds, the return value is true. [DllImport("USER32", SetLastError = true)] public static extern bool UnhookWindowsHookEx(IntPtr hHook); ///  /// 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. ///  /// handle to current hook /// hook code passed to hook procedure /// value passed to hook procedure /// value passed to hook procedure /// If the function succeeds, the return value is true. [DllImport("USER32", SetLastError = true)] static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam); [StructLayout(LayoutKind.Sequential)] public struct LowLevelKeyboardInputEvent { ///  /// A virtual-key code. The code must be a value in the range 1 to 254. ///  public int VirtualCode; ///  /// A hardware scan code for the key. ///  public int HardwareScanCode; ///  /// 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. ///  public int Flags; ///  /// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message. ///  public int TimeStamp; ///  /// Additional information associated with the message. ///  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 handler = KeyboardPressed; handler?.Invoke(this, eventArguments); fEatKeyStroke = eventArguments.Handled; } return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); } } } 

Uso:

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

Se un hotkey globale fosse sufficiente, quindi RegisterHotKey farebbe il trucco