應用程式需在**無焦點、最小化、背景執行**時仍能接收特定熱鍵 (如 `Ctrl+Alt+A` 截圖)。表單層級 `KeyDown` 事件僅在擁有焦點時觸發,無法滿足需求。
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class KeyboardHook : IDisposable
{
// 事件
public event KeyEventHandler KeyDown;
public event KeyPressEventHandler KeyPress;
public event KeyEventHandler KeyUp;
// Win32 API
private delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private static HookProc _proc;
private static int _hookId = 0;
private const int WH_KEYBOARD_LL = 13; // 低層鍵盤鉤子 (全域)
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[StructLayout(LayoutKind.Sequential)]
private struct KBDLLHOOKSTRUCT
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_SYSKEYUP = 0x0105;
public void Start()
{
if (_hookId != 0) return;
_proc = HookCallback; // 必須保持委派參考,防止 GC 回收
using (ProcessModule module = Process.GetCurrentProcess().MainModule)
{
_hookId = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, GetModuleHandle(module.ModuleName), 0);
}
if (_hookId == 0)
throw new Win32Exception(Marshal.GetLastWin32Error(), "安裝鍵盤鉤子失敗");
}
public void Stop()
{
if (_hookId != 0)
{
UnhookWindowsHookEx(_hookId);
_hookId = 0;
}
}
private int HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && (KeyDown != null || KeyUp != null || KeyPress != null))
{
var info = Marshal.PtrToStructure(lParam);
Keys key = (Keys)info.vkCode;
// KeyDown / SysKeyDown
if ((KeyDown != null) && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
{
var args = new KeyEventArgs(key);
KeyDown(this, args);
if (args.Handled) return 1; // 阻止傳遞
}
// KeyPress (字元輸入)
if (KeyPress != null && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
{
// ToAscii 需鍵盤狀態,簡化略過,實際需呼叫 GetKeyboardState + ToAscii
}
// KeyUp / SysKeyUp
if ((KeyUp != null) && (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
{
var args = new KeyEventArgs(key);
KeyUp(this, args);
if (args.Handled) return 1;
}
}
return CallNextHookEx(_hookId, nCode, wParam, lParam);
}
public void Dispose() => Stop();
~KeyboardHook() => Dispose();
}
public partial class MainForm : Form
{
private KeyboardHook _hook;
public MainForm()
{
InitializeComponent();
_hook = new KeyboardHook();
_hook.KeyDown += OnGlobalKeyDown;
_hook.Start();
}
private void OnGlobalKeyDown(object sender, KeyEventArgs e)
{
// 判斷 Ctrl+Alt+A
if (e.KeyCode == Keys.A && e.Control && e.Alt)
{
e.Handled = true; // 可選:阻止其他程式接收
this.Invoke((Action)(() => MessageBox.Show("全域熱鍵觸發!")));
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_hook?.Dispose();
base.OnFormClosing(e);
}
}
| 組合 | 判斷條件 |
|---|---|
| 單一鍵 (A) | `e.KeyCode == Keys.A` |
| Ctrl + A | `e.KeyCode == Keys.A && e.Control` |
| Alt + A | `e.KeyCode == Keys.A && e.Alt` |
| Shift + A | `e.KeyCode == Keys.A && e.Shift` |
| Ctrl + Alt + A | `e.KeyCode == Keys.A && e.Control && e.Alt` |
| Ctrl + Shift + A | `e.KeyCode == Keys.A && e.Control && e.Shift` |
| Win + A | `e.KeyCode == Keys.A && (e.Modifiers & Keys.Windows) == Keys.Windows` |
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private const int WM_HOTKEY = 0x0312;
private const uint MOD_ALT = 0x0001;
private const uint MOD_CONTROL = 0x0002;
private const uint MOD_SHIFT = 0x0004;
private const uint MOD_WIN = 0x0008;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HOTKEY)
{
int id = m.WParam.ToInt32();
if (id == 1) MessageBox.Show("熱鍵觸發 (RegisterHotKey)");
}
base.WndProc(ref m);
}
// 註冊 Ctrl+Alt+A
RegisterHotKey(this.Handle, 1, MOD_CONTROL | MOD_ALT, (uint)Keys.A);
原文連結:https://www.bianceng.cn/Programming/csharp/201410/45484.htm