[C#.NET] 透過 HwndSource 攔截全域快捷鍵 (Hot Key)

[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

image

 

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo