13 Jan 2025 - Matthias Voigt
Immer wieder brauche ich in einem WPF-Programm globale Hotkeys und jedes Mal suche ich im Netz danach. Da nichts im Internet für immer bleibt, veröffentliche ich hier meine Variante, die unter .NET 8 funktioniert.
Verwendung von HwndSource
In WPF nutze ich HwndSource, um ein Fenster-Handle (hWnd) zu erhalten und eine WndProc-Methode zu registrieren.
Unterstützung mehrerer Modifikator-Tasten
Die ModifierKeys
-Enumeration ist mit dem FlagsAttribute
versehen, sodass mehrere Tasten wie Ctrl + Shift
kombiniert werden können.
Ereignisbasierte Umsetzung
Sobald der Hotkey gedrückt wird, löst KeyboardHook
ein Ereignis aus, das in der MainWindow
-Klasse abgefangen wird.
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Input;
public sealed class KeyboardHook : IDisposable
{
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private const int WM_HOTKEY = 0x0312;
private HwndSource? _hwndSource;
private int _currentId;
private IntPtr _windowHandle;
public KeyboardHook(Window window)
{
window.SourceInitialized += (_, __) =>
{
var helper = new WindowInteropHelper(window);
_windowHandle = helper.Handle;
_hwndSource = HwndSource.FromHwnd(_windowHandle);
_hwndSource?.AddHook(WndProc);
};
}
public void RegisterHotKey(ModifierKeys modifier, Key key)
{
if (_hwndSource == null)
throw new InvalidOperationException("Fenster-Handle ist noch nicht initialisiert!");
_currentId++;
uint virtualKeyCode = (uint)KeyInterop.VirtualKeyFromKey(key);
if (!RegisterHotKey(_windowHandle, _currentId, (uint)modifier, virtualKeyCode))
throw new InvalidOperationException("Konnte Hotkey nicht registrieren.");
}
public event EventHandler<KeyPressedEventArgs>? KeyPressed;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HOTKEY)
{
var key = KeyInterop.KeyFromVirtualKey(((int)lParam >> 16) & 0xFFFF);
var modifier = (ModifierKeys)((int)lParam & 0xFFFF);
KeyPressed?.Invoke(this, new KeyPressedEventArgs(modifier, key));
handled = true;
}
return IntPtr.Zero;
}
public void Dispose()
{
if (_hwndSource != null)
{
for (int i = _currentId; i > 0; i--)
{
UnregisterHotKey(_windowHandle, i);
}
_hwndSource.RemoveHook(WndProc);
_hwndSource = null;
}
}
}
public class KeyPressedEventArgs(ModifierKeys modifier, Key key) : EventArgs
{
public ModifierKeys Modifier { get; } = modifier;
public Key Key { get; } = key;
}
Die Verwendung der KeyboardHook
-Klasse in einer WPF-Anwendung:
public partial class MainWindow : Window
{
private readonly KeyboardHook _keyboardHook;
public MainWindow()
{
InitializeComponent();
_keyboardHook = new KeyboardHook(this);
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
_keyboardHook.KeyPressed += KeyboardHook_KeyPressed;
_keyboardHook.RegisterHotKey(ModifierKeys.Control, Key.F1);
}
private void KeyboardHook_KeyPressed(object? sender, KeyPressedEventArgs e)
{
MessageBox.Show($"Hotkey {e.Modifier} + {e.Key} wurde gedrückt!");
}
}