Hotkeys in WPF are not as easy as in Windows Forms, so I have written a HotKeyHost
class which encapsulates all the complicated stuff and makes its usage even easier than in Windows Forms.
Each HotKey
-Instance has to be enabled and added to the HotKeyHost
to work properly.
When you want to perform hotkey dependend actions, you can either use the HotKeyPressed
-Event or (as in the example below) override the OnHotKeyPressed
method in your own hotkey-class. In the last case you should mark you class as serializable, override GetObjectData
and add a protected constructor for deserialization.
If two HotKey
-instances have the same Key
- and Modifiers
-Property, they are equal to each other as the HotKey
-class overrides the Equals
-Method.
Example usage:
using HDLibrary.Wpf.Input;
[...]
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HotKeyHost hotKeyHost = new HotKeyHost((HwndSource)HwndSource.FromVisual(App.Current.MainWindow));
hotKeyHost.AddHotKey(new CustomHotKey("ShowPopup", Key.Q, ModifierKeys.Control | ModifierKeys.Shift, true));
hotKeyHost.AddHotKey(new CustomHotKey("ClosePopup", Key.F2, ModifierKeys.Control, true));
}
[...]
[Serializable]
public class CustomHotKey : HotKey
{
public CustomHotKey(string name, Key key, ModifierKeys modifiers, bool enabled)
: base(key, modifiers, enabled)
{
Name = name;
}
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
OnPropertyChanged(name);
}
}
}
protected override void OnHotKeyPress()
{
MessageBox.Show(string.Format("'{0}' has been pressed ({1})", Name, this));
base.OnHotKeyPress();
}
protected CustomHotKey(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
Name = info.GetString("Name");
}
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("Name", Name);
}
}
When creating the
HotKeyHost
, the handle of the window has to be initialized, so it is recommended using the
Loaded
-event of the main window.
The classes:
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Runtime.Serialization;
using System.Windows.Interop;
namespace HDLibrary.Wpf.Input
{
public class HotKeyEventArgs : EventArgs
{
public HotKey HotKey { get; private set; }
public HotKeyEventArgs(HotKey hotKey)
{
HotKey = hotKey;
}
}
[Serializable]
public class HotKeyAlreadyRegisteredException : Exception
{
public HotKey HotKey { get; private set; }
public HotKeyAlreadyRegisteredException(string message, HotKey hotKey) : base(message) { HotKey = hotKey; }
public HotKeyAlreadyRegisteredException(string message, HotKey hotKey, Exception inner) : base(message, inner) { HotKey = hotKey; }
protected HotKeyAlreadyRegisteredException(
SerializationInfo info,
StreamingContext context)
: base(info, context) { }
}
[Serializable]
public class HotKey : INotifyPropertyChanged, ISerializable, IEquatable<HotKey>
{
public HotKey() { }
public HotKey(Key key, ModifierKeys modifiers) : this(key, modifiers, true) { }
public HotKey(Key key, ModifierKeys modifiers, bool enabled)
{
Key = key;
Modifiers = modifiers;
Enabled = enabled;
}
private Key key;
public Key Key
{
get { return key; }
set
{
if (key != value)
{
key = value;
OnPropertyChanged("Key");
}
}
}
private ModifierKeys modifiers;
public ModifierKeys Modifiers
{
get { return modifiers; }
set
{
if (modifiers != value)
{
modifiers = value;
OnPropertyChanged("Modifiers");
}
}
}
private bool enabled;
public bool Enabled
{
get { return enabled; }
set
{
if (value != enabled)
{
enabled = value;
OnPropertyChanged("Enabled");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public override bool Equals(object obj)
{
HotKey hotKey = obj as HotKey;
if (hotKey != null)
return Equals(hotKey);
else
return false;
}
public bool Equals(HotKey other)
{
return (Key == other.Key && Modifiers == other.Modifiers);
}
public override int GetHashCode()
{
return (int)Modifiers + 10 * (int)Key;
}
public override string ToString()
{
return string.Format("{0} + {1} ({2}Enabled)", Key, Modifiers, Enabled ? "" : "Not ");
}
public event EventHandler<HotKeyEventArgs> HotKeyPressed;
protected virtual void OnHotKeyPress()
{
if (HotKeyPressed != null)
HotKeyPressed(this, new HotKeyEventArgs(this));
}
internal void RaiseOnHotKeyPressed()
{
OnHotKeyPress();
}
protected HotKey(SerializationInfo info, StreamingContext context)
{
Key = (Key)info.GetValue("Key", typeof(Key));
Modifiers = (ModifierKeys)info.GetValue("Modifiers", typeof(ModifierKeys));
Enabled = info.GetBoolean("Enabled");
}
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Key", Key, typeof(Key));
info.AddValue("Modifiers", Modifiers, typeof(ModifierKeys));
info.AddValue("Enabled", Enabled);
}
}
public sealed class HotKeyHost : IDisposable
{
public HotKeyHost(HwndSource hwndSource)
{
if (hwndSource == null)
throw new ArgumentNullException("hwndSource");
this.hook = new HwndSourceHook(WndProc);
this.hwndSource = hwndSource;
hwndSource.AddHook(hook);
}
#region HotKey Interop
private const int WM_HotKey = 786;
[DllImport("user32", CharSet = CharSet.Ansi,
SetLastError = true, ExactSpelling = true)]
private static extern int RegisterHotKey(IntPtr hwnd,
int id, int modifiers, int key);
[DllImport("user32", CharSet = CharSet.Ansi,
SetLastError = true, ExactSpelling = true)]
private static extern int UnregisterHotKey(IntPtr hwnd, int id);
#endregion
#region Interop-Encapsulation
private HwndSourceHook hook;
private HwndSource hwndSource;
private void RegisterHotKey(int id, HotKey hotKey)
{
if ((int)hwndSource.Handle != 0)
{
RegisterHotKey(hwndSource.Handle, id, (int)hotKey.Modifiers, KeyInterop.VirtualKeyFromKey(hotKey.Key));
int error = Marshal.GetLastWin32Error();
if (error != 0)
{
Exception e = new Win32Exception(error);
if (error == 1409)
throw new HotKeyAlreadyRegisteredException(e.Message, hotKey, e);
else
throw e;
}
}
else
throw new InvalidOperationException("Handle is invalid");
}
private void UnregisterHotKey(int id)
{
if ((int)hwndSource.Handle != 0)
{
UnregisterHotKey(hwndSource.Handle, id);
int error = Marshal.GetLastWin32Error();
if (error != 0)
throw new Win32Exception(error);
}
}
#endregion
public event EventHandler<HotKeyEventArgs> HotKeyPressed;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HotKey)
{
if (hotKeys.ContainsKey((int)wParam))
{
HotKey h = hotKeys[(int)wParam];
h.RaiseOnHotKeyPressed();
if (HotKeyPressed != null)
HotKeyPressed(this, new HotKeyEventArgs(h));
}
}
return new IntPtr(0);
}
void hotKey_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var kvPair = hotKeys.FirstOrDefault(h => h.Value == sender);
if (kvPair.Value != null)
{
if (e.PropertyName == "Enabled")
{
if (kvPair.Value.Enabled)
RegisterHotKey(kvPair.Key, kvPair.Value);
else
UnregisterHotKey(kvPair.Key);
}
else if (e.PropertyName == "Key" || e.PropertyName == "Modifiers")
{
if (kvPair.Value.Enabled)
{
UnregisterHotKey(kvPair.Key);
RegisterHotKey(kvPair.Key, kvPair.Value);
}
}
}
}
private Dictionary<int, HotKey> hotKeys = new Dictionary<int, HotKey>();
public class SerialCounter
{
public SerialCounter(int start)
{
Current = start;
}
public int Current { get; private set; }
public int Next()
{
return ++Current;
}
}
public IEnumerable<HotKey> HotKeys { get { return hotKeys.Values; } }
private static readonly SerialCounter idGen = new SerialCounter(1);
public void AddHotKey(HotKey hotKey)
{
if (hotKey == null)
throw new ArgumentNullException("value");
if (hotKey.Key == 0)
throw new ArgumentNullException("value.Key");
if (hotKeys.ContainsValue(hotKey))
throw new HotKeyAlreadyRegisteredException("HotKey already registered!", hotKey);
int id = idGen.Next();
if (hotKey.Enabled)
RegisterHotKey(id, hotKey);
hotKey.PropertyChanged += hotKey_PropertyChanged;
hotKeys[id] = hotKey;
}
public bool RemoveHotKey(HotKey hotKey)
{
var kvPair = hotKeys.FirstOrDefault(h => h.Value == hotKey);
if (kvPair.Value != null)
{
kvPair.Value.PropertyChanged -= hotKey_PropertyChanged;
if (kvPair.Value.Enabled)
UnregisterHotKey(kvPair.Key);
return hotKeys.Remove(kvPair.Key);
}
return false;
}
#region Destructor
private bool disposed;
private void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
hwndSource.RemoveHook(hook);
}
for (int i = hotKeys.Count - 1; i >= 0; i--)
{
RemoveHotKey(hotKeys.Values.ElementAt(i));
}
disposed = true;
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~HotKeyHost()
{
this.Dispose(false);
}
#endregion
}
}