全域熱鍵允許應用程式在無焦點時回應組合鍵 (如 Ctrl+Alt+F12)。本文提供完整的 P/Invoke 封裝、WinForms/WPF 整合範例、多熱鍵管理、資源正確釋放模式。
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();
}
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() { /* ... */ }
}
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 管理多熱鍵,避免硬編碼 IDFormClosing/Dispose 徹底解除註冊,否則系統保留熱鍵導致下次啟動失敗Control.Invoke/Dispatcher.Invoke 回 UI 執行緒GetLastError() == 1409 (ERROR_HOTKEY_ALREADY_REGISTERED) 提示衝突KeyModifiers.NoRepeat 避免長按重複觸發
// 更靈活但複雜:SetWindowsHookEx(WH_KEYBOARD_LL, ...)
// 可攔截所有按鍵、修改/阻止傳遞、支援更複雜組合
// 適用:全域鍵盤記錄、遊戲外掛、無障礙工具
// 注意:需簽名、管理員權限、反作弊系統可能攔截
RegisterHotKey 是實作全域熱鍵的標準方式,適用於大多數工具類應用。封裝良好的 HotKeyManager 類別可跨 WinForms/WPF/Console 復用。關鍵在於:正確的 P/Invoke 簽名、ID 管理、資源釋放、UI 執行緒切換、衝突處理。新專案若需更精細控制,可研究 WH_KEYBOARD_LL 低層鉤子。