I recently had the problem to register a global hotkey, but my “old” Win32-API calls didn’t work with WPF. I looked around the web and found the “Managed Windows API”, but I didn’t want to add another external dependency to my project, so I extracted the core functions for registering hotkeys and condensed the code to a new version.
As the original “Managed Windows API” is licensed by the GNU Lesser General Public License (LGPL) I want to provide my modifications here.
First we need a Window which can dispatch the window messages to our event handlers:
/// <summary>
/// A Win32 native window that delegates window messages to
/// handlers. So several
/// components can use the same native window to save
/// "USER resources". This class
/// is useful when writing your own components.
/// </summary>
public sealed class EventDispatchingNativeWindow : NativeWindow
{
private static readonly Object MyLock = new Object();
private static EventDispatchingNativeWindow _instance;
/// <summary>
/// Create your own event dispatching window.
/// </summary>
public EventDispatchingNativeWindow()
{
CreateHandle(new CreateParams());
}
/// <summary>
/// A global instance which can be used by components
/// that do not need their own window.
/// </summary>
public static EventDispatchingNativeWindow Instance
{
get
{
lock (MyLock)
{
return
_instance ??
(_instance =
new EventDispatchingNativeWindow());
}
}
}
/// <summary>
/// Attach your event handlers here.
/// </summary>
public event WndProcEventHandler EventHandler;
/// <summary>
/// Parse messages passed to this window and send
/// them to the event handlers.
/// </summary>
/// <param name="m">A System.Windows.Forms.Message
/// that is associated with the
/// current Windows message.</param>
protected override void WndProc(ref Message m)
{
bool handled = false;
if (EventHandler != null)
EventHandler(ref m, ref handled);
if (!handled)
base.WndProc(ref m);
}
}
Now we can write our global hotkey handler class:
/// <summary>
/// Specifies a class that creates a global keyboard hotkey.
/// </summary>
public class GlobalHotkey
{
private static readonly Object MyStaticLock = new Object();
private static int _hotkeyCounter = 0xA000;
private readonly int _hotkeyIndex;
private readonly IntPtr _hWnd;
/// <summary>
/// Initializes a new instance of this class.
/// </summary>
/// <param name="keys">The keys.</param>
/// <param name="ctrl">if <c>true</c> [CTRL].</param>
/// <param name="alt">if <c>true</c> [alt].</param>
/// <param name="shift">if <c>true</c> [shift].</param>
/// <param name="winKey">if <c>true</c> [win key].</param>
public GlobalHotkey(Keys keys, bool ctrl,
bool alt, bool shift, bool winKey)
{
KeyCode = keys;
Ctrl = ctrl;
Alt = alt;
Shift = shift;
WindowsKey = winKey;
EventDispatchingNativeWindow.Instance.EventHandler
+= NwEventHandler;
lock (MyStaticLock)
{
_hotkeyIndex = ++_hotkeyCounter;
}
_hWnd = EventDispatchingNativeWindow.Instance.Handle;
Enable();
}
/// <summary>
/// The key code of the hotkey.
/// </summary>
public Keys KeyCode { get; private set; }
/// <summary>
/// Whether the shortcut includes the Control modifier.
/// </summary>
public bool Ctrl { get; private set; }
/// <summary>
/// Whether this shortcut includes the Alt modifier.
/// </summary>
public bool Alt { get; private set; }
/// <summary>
/// Whether this shortcut includes the shift modifier.
/// </summary>
public bool Shift { get; private set; }
/// <summary>
/// Whether this shortcut includes the Windows key modifier.
/// </summary>
public bool WindowsKey { get; private set; }
~GlobalHotkey()
{
Disable();
EventDispatchingNativeWindow.Instance.EventHandler
-= NwEventHandler;
}
/// <summary>
/// Enables the hotkey. When the hotkey is enabled,
/// pressing it causes a
/// <c>HotkeyPressed</c> event instead of being
/// handled by the active application.
/// </summary>
private void Enable()
{
// register hotkey
int fsModifiers = 0;
if (Shift) fsModifiers += ModShift;
if (Ctrl) fsModifiers += ModControl;
if (Alt) fsModifiers += ModAlt;
if (WindowsKey) fsModifiers += ModWin;
bool success =
RegisterHotKey(_hWnd, _hotkeyIndex,
fsModifiers, (int) KeyCode);
if (!success)
throw new
Exception(
"Could not register hotkey (already in use).");
}
/// <summary>
/// Disables this instance.
/// </summary>
private void Disable()
{
// unregister hotkey
UnregisterHotKey(_hWnd, _hotkeyIndex);
}
/// <summary>
/// Occurs when the hotkey is pressed.
/// </summary>
public event EventHandler HotkeyPressed;
private void NwEventHandler(ref Message m, ref bool handled)
{
if (handled) return;
if (m.Msg != WmHotkey ||
m.WParam.ToInt32() != _hotkeyIndex)
return;
if (HotkeyPressed != null)
HotkeyPressed(this, EventArgs.Empty);
handled = true;
}
#region PInvoke Declarations
private const int ModAlt = 0x0001;
private const int ModControl = 0x0002;
private const int ModShift = 0x0004;
private const int ModWin = 0x0008;
private const int WmHotkey = 0x0312;
[DllImport("user32.dll", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd,
int id, int fsModifiers, int vlc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd,
int id);
#endregion
}
Now we can easily register a global hotkey or use it as an observable in F#:
/// system-wide keyboard hook
let hotkey = new GlobalHotkey(Keys.Q,true,false,false,false)
let hookObserver =
hotkey.HotkeyPressed
|> Observable.subscribe (fun _ -> printfn "Hotkey pressed")