[C#.NET] 透過 HwndSource 攔截全域快捷鍵 (Hot Key)
續上篇,[C#.NET][Winform] 全域等級的快捷鍵 (Hot Key),我們在Winform裡使用覆寫了 WndProc 方法來處理 RegisterHotKey function,但除了使用 WndProc 之外,我們還可以利用 HwndSource 類別 來 Hook 全域快捷鍵,HwndSourceHook 方法就是用來捕捉全域等級的 Hot Key,HwndSource.AddHook 方法 則是處理Hook的委派方法
以下是 HwndSource 類別 的片斷程式碼使用方式,片斷程式碼中的 HwndSourceHook 方法就很像咱們在 Winform 裡處理的 WndProc 方法一樣
private readonly int WM_HOTKEY = 0x0312;
//event
public event EventHandler<HotKeyPressedEventArgs> HotKeyPressed;
//fields
private bool m_Disposed = false;
private int m_RegisterIndex = 0;
private readonly Dictionary<HotKey, int> m_RegisteredList;
private HwndSource m_HandleSource;
//win32 api define
[DllImport("user32.dll", EntryPoint = "RegisterHotKey")]
private static extern bool s_RegisterHotKey(IntPtr handle, int id, uint modifiers, uint virtualCode);
[DllImport("user32.dll", EntryPoint = "UnregisterHotKey")]
private static extern bool s_UnregisterHotKey(IntPtr handle, int id);
//constructor
public HotKeyProvider()
{
if (this.m_RegisteredList == null)
{
this.m_RegisteredList = new Dictionary<HotKey, int>();
}
if (this.m_HandleSource == null)
{
this.m_HandleSource = new HwndSource(new HwndSourceParameters());
this.m_HandleSource.AddHook(HwndSourceHook);
}
}
protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HOTKEY)
{
var modifiers = (ushort)lParam;//Modifiers key code
var vk = (ushort)(lParam.ToInt32() >> 16);//virtual key code
var vkey = KeyInterop.KeyFromVirtualKey(vk);
var modifiersKey = (ModifierKeys)(modifiers);
OnKeyPressed(new HotKeyPressedEventArgs(new HotKey(modifiersKey, vkey)));
handled = true;
return new IntPtr(1);
}
return IntPtr.Zero;
}
如此一來便能為自己的應用程式攔截熱鍵。我在這裡用了 Dictionary<HotKey, int> 用來裝載該應用程式註冊過的熱鍵
PS.未來時間允許的話,可以再上熱鍵是否被作業系統使用過的判斷。
再來看看 HotKey 類別,在這裡我利用序列化覆寫了 Equals,也處理了operator ==,對於下面寫法有問題的朋友們,可參考 [C#.NET] 利用序列化 比較兩物件是否相等,[C#]Effective C# 條款九: 理解幾個相等判斷之間的關係
PS.本來想寫 GetHashCode & Equals 對於 Dictionary 影響,但我忘了,有機會再補上吧,
public class HotKey
{
private Key _key;
private ModifierKeys _modifierKeys;
public Key Key
{
get { return _key; }
set { _key = value; }
}
public ModifierKeys ModifierKeys
{
get { return _modifierKeys; }
set { _modifierKeys = value; }
}
public HotKey(ModifierKeys modifiers, Key key)
{
this.Key = key;
this.ModifierKeys = modifiers;
}
public override bool Equals(object obj)
{
var targetArray = getObjectByte(this);
var expectedArray = getObjectByte(obj);
var equals = expectedArray.SequenceEqual(targetArray);
return equals;
}
private static byte[] getObjectByte(object model)
{
if (model == null)
{
return null;
}
using (MemoryStream memory = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(memory, model);
var array = memory.ToArray();
return array;
}
}
public static bool operator ==(HotKey left, HotKey right)
{
var leftArray = getObjectByte(left);
if (leftArray == null)
{
return true;
}
var reightArray = getObjectByte(right);
if (reightArray == null)
{
return false;
}
if (leftArray == null && reightArray == null)
{
return false;
}
var equals = leftArray.SequenceEqual(reightArray);
return equals;
}
public static bool operator !=(HotKey left, HotKey right)
{
if (left == right)
{
return false;
}
else
{
return true;
}
}
public override int GetHashCode()
{
return this.Key.GetHashCode() ^ this.ModifierKeys.GetHashCode();
}
public override string ToString()
{
var mk = (int)ModifierKeys;
var modifierKey = ModifierKeys.ToString().Replace(" ", "");
if (modifierKey.IndexOf(",") > 0)
{
var split = modifierKey.Split(',');
if (split != null && split.Length > 0)
{
modifierKey = split[0] + "+" + split[1];
}
}
var hotKey = string.Format("{0}+{1}", modifierKey, this.Key.ToString());
return hotKey;
}
}
覆寫HotKey 的 GetHashCode & Equals 方法後我便可以使用 Dictionary<HotKey, int>.ContainsKey 方法用來判斷熱鍵是否被使用過
{
if (this.m_RegisteredList.ContainsKey(hotKey))
{
return false;
}
this.m_RegisterIndex++;
this.m_RegisteredList.Add(hotKey, this.m_RegisterIndex);
var virtualCode = KeyInterop.VirtualKeyFromKey(hotKey.Key);
return s_RegisterHotKey(this.m_HandleSource.Handle, this.m_RegisterIndex, (uint)hotKey.ModifierKeys, (uint)virtualCode);
}
public bool Unregister(HotKey hotKey)
{
if (!this.m_RegisteredList.ContainsKey(hotKey))
{
return false;
}
var id = this.m_RegisteredList[hotKey];
this.m_RegisteredList.Remove(hotKey);
return s_UnregisterHotKey(this.m_HandleSource.Handle, id);
}
定義熱鍵被按下類別
public class HotKeyPressedEventArgs : EventArgs
{
public HotKeyPressedEventArgs(HotKey HotKey)
{
if (HotKey == null)
{
throw new ArgumentNullException("HotKey");
}
this.HotKey = HotKey;
}
public HotKey HotKey { get; private set; }
}
為了縮短篇幅,有興趣的人可以到Codeplex看原碼跟下載範例
http://component.codeplex.com/SourceControl/changeset/view/28740
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET