首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 註冊全域熱鍵:RegisterHotKey P/Invoke 完整實作與 WPF/WinForms 整合

     編輯:關於C語言

前言

全域熱鍵允許應用程式在無焦點時回應組合鍵 (如 Ctrl+Alt+F12)。本文提供完整的 P/Invoke 封裝、WinForms/WPF 整合範例、多熱鍵管理、資源正確釋放模式。

核心 P/Invoke 封裝


using System;
using System.Runtime.InteropServices;
using System.Windows.Forms; // Keys 列舉

public static class HotKeyManager
{
    // Windows 訊息常數
    public const int WM_HOTKEY = 0x0312;

    [Flags]
    public enum KeyModifiers : uint
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
        Windows = 8, // Win 鍵
        NoRepeat = 0x4000 // 防止重複觸發
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    // 取得錯誤代碼
    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();
}

WinForms 整合實作


using System;
using System.Collections.Generic;
using System.Windows.Forms;

public class HotKeyForm : Form
{
    private readonly Dictionary _hotKeyActions = new();
    private int _nextId = 1;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == HotKeyManager.WM_HOTKEY)
        {
            int id = m.WParam.ToInt32();
            if (_hotKeyActions.TryGetValue(id, out var action))
            {
                try { action(); }
                catch (Exception ex) { /* 記錄日誌 */ }
            }
        }
        base.WndProc(ref m);
    }

    /// 註冊熱鍵
    /// 熱鍵 ID,用於後續解除註冊
    public int RegisterHotKey(Keys key, HotKeyManager.KeyModifiers modifiers, Action action)
    {
        int id = _nextId++;
        if (HotKeyManager.RegisterHotKey(this.Handle, id, modifiers, key))
        {
            _hotKeyActions[id] = action;
            return id;
        }
        throw new InvalidOperationException($"熱鍵註冊失敗: {modifiers} + {key}, Error: {HotKeyManager.GetLastError()}");
    }

    public void UnregisterHotKey(int id)
    {
        if (_hotKeyActions.Remove(id))
        {
            HotKeyManager.UnregisterHotKey(this.Handle, id);
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        // 解除所有熱鍵
        foreach (int id in _hotKeyActions.Keys)
            HotKeyManager.UnregisterHotKey(this.Handle, id);
        _hotKeyActions.Clear();
        base.OnFormClosing(e);
    }
}

// 使用範例
public class MainForm : HotKeyForm
{
    public MainForm()
    {
        // Ctrl + Shift + S : 儲存
        RegisterHotKey(Keys.S, HotKeyManager.KeyModifiers.Control | HotKeyManager.KeyModifiers.Shift, () => 
        {
            this.Invoke(() => SaveData());
        });

        // Alt + F4 : 關閉 (攔截系統關閉熱鍵需特殊處理)
        // Win + G : 遊戲模式切換
        RegisterHotKey(Keys.G, HotKeyManager.KeyModifiers.Windows, () => ToggleGameMode());
    }

    private void SaveData() { /* ... */ }
    private void ToggleGameMode() { /* ... */ }
}

WPF 整合實作 (HwndSource)


using System;
using System.Windows;
using System.Windows.Interop;
using System.Collections.Generic;

public class WpfHotKeyManager : IDisposable
{
    private readonly Window _window;
    private readonly HwndSource _source;
    private readonly Dictionary _actions = new();
    private int _nextId = 1;

    public WpfHotKeyManager(Window window)
    {
        _window = window;
        _source = PresentationSource.FromVisual(window) as HwndSource;
        _source?.AddHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == HotKeyManager.WM_HOTKEY)
        {
            int id = wParam.ToInt32();
            if (_actions.TryGetValue(id, out var action))
            {
                // 確保在 UI 執行緒執行
                _window.Dispatcher.Invoke(action);
                handled = true;
            }
        }
        return IntPtr.Zero;
    }

    public int Register(Keys key, HotKeyManager.KeyModifiers modifiers, Action action)
    {
        int id = _nextId++;
        if (HotKeyManager.RegisterHotKey(_source.Handle, id, modifiers, key))
        {
            _actions[id] = action;
            return id;
        }
        throw new InvalidOperationException($"WPF 熱鍵註冊失敗: {HotKeyManager.GetLastError()}");
    }

    public void Unregister(int id)
    {
        if (_actions.Remove(id))
            HotKeyManager.UnregisterHotKey(_source.Handle, id);
    }

    public void Dispose()
    {
        foreach (int id in _actions.Keys)
            HotKeyManager.UnregisterHotKey(_source.Handle, id);
        _actions.Clear();
        _source?.RemoveHook(WndProc);
    }
}

// ViewModel/View 使用
public partial class MainWindow : Window
{
    private readonly WpfHotKeyManager _hotKeys;

    public MainWindow()
    {
        InitializeComponent();
        _hotKeys = new WpfHotKeyManager(this);
        
        Loaded += (s, e) => 
        {
            _hotKeys.Register(Key.S, HotKeyManager.KeyModifiers.Control | HotKeyManager.KeyModifiers.Shift, SaveCommand.Execute);
            _hotKeys.Register(Key.F1, HotKeyManager.KeyModifiers.None, ShowHelp);
        };
        
        Closed += (s, e) => _hotKeys.Dispose();
    }
}

控制台應用程式註冊熱鍵 (無視窗)


using System;
using System.Runtime.InteropServices;

public class ConsoleHotKeyDemo
{
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetConsoleWindow();

    public static void Run()
    {
        IntPtr hWnd = GetConsoleWindow();
        int id = 1;
        
        if (HotKeyManager.RegisterHotKey(hWnd, id, HotKeyManager.KeyModifiers.Control, Keys.Q))
        {
            Console.WriteLine("已註冊 Ctrl+Q 退出,按下試試...");
            
            // 訊息迴圈
            while (true)
            {
                // 簡易訊息泵
                System.Windows.Forms.Application.DoEvents(); // 需參考 System.Windows.Forms
                System.Threading.Thread.Sleep(50);
            }
        }
    }
}

常見熱鍵衝突與解決

熱鍵衝突對象建議
Ctrl+Alt+Del系統安全序列不可攔截,內核級處理
Win+L鎖定工作站不可攔截
Alt+Tab視窗切換不可攔截
Ctrl+Shift+Esc工作管理員不可攔截
Win+數字工作列快速啟動避免使用
Ctrl+Alt+字母顯示卡驅動程式 (Intel/AMD/NVIDIA)測試後再用

最佳實踐檢查清單

  • ✅ 使用 Dictionary 管理多熱鍵,避免硬編碼 ID
  • ✅ FormClosing/Dispose 徹底解除註冊,否則系統保留熱鍵導致下次啟動失敗
  • ✅ 熱鍵回調中使用 Control.Invoke/Dispatcher.Invoke 回 UI 執行緒
  • ✅ 捕捉回調例外,避免訊息迴圈崩潰
  • ✅ 提供設定介面讓用戶自訂熱鍵,持久化至設定檔
  • ✅ 啟動時檢查 GetLastError() == 1409 (ERROR_HOTKEY_ALREADY_REGISTERED) 提示衝突
  • ✅ 考慮 KeyModifiers.NoRepeat 避免長按重複觸發

現代替代:Windows 10+ 低層鍵盤鉤子


// 更靈活但複雜:SetWindowsHookEx(WH_KEYBOARD_LL, ...)
// 可攔截所有按鍵、修改/阻止傳遞、支援更複雜組合
// 適用:全域鍵盤記錄、遊戲外掛、無障礙工具
// 注意:需簽名、管理員權限、反作弊系統可能攔截

總結

RegisterHotKey 是實作全域熱鍵的標準方式,適用於大多數工具類應用。封裝良好的 HotKeyManager 類別可跨 WinForms/WPF/Console 復用。關鍵在於:正確的 P/Invoke 簽名、ID 管理、資源釋放、UI 執行緒切換、衝突處理。新專案若需更精細控制,可研究 WH_KEYBOARD_LL 低層鉤子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved