C# 封装 Windows 全局热键

发布时间 2023-12-28 09:16:21作者: 张赐荣

全局热键工具类(GlobalHotkey)

【文 / 张赐荣】

1. 功能概述

全局热键工具类(GlobalHotkey)是一个用于注册全局热键的工具类。它允许你在你的应用程序中注册特定的键盘组合,以便在用户按下这些组合时触发相应的事件。此工具类提供了以下主要功能:

  • 注册多个热键并定义每个热键的组合。
  • 当用户按下注册的热键时,触发相应的事件。
  • 跟踪热键按下的次数。

2. 如何使用

a. 注册热键

要注册热键,可以使用 RegisterHotkey 方法,该方法允许你指定修改键(如 Ctrl、Shift、Alt、Windows)和虚拟键(如 A、B、数字键等)的组合。示例:

int hotkeyId = GlobalHotkey.RegisterHotkey(ModifiedKeys.Ctrl | ModifiedKeys.Alt, VirtualKey.A);
if (hotkeyId > 0)
{
    // 热键注册成功,可以执行相应操作
}

b. 注销热键

要注销之前注册的热键,可以使用 UnregisterHotkey 方法,并传入先前注册热键时返回的热键ID。示例:

bool isUnregistered = GlobalHotkey.UnregisterHotkey(hotkeyId);
if (isUnregistered)
{
    // 热键注销成功
}

3. 多个热键绑定

可以多次调用 RegisterHotkey 方法来注册多个热键,每个热键都应具有唯一的热键ID。示例:

int hotkeyId1 = GlobalHotkey.RegisterHotkey(ModifiedKeys.Ctrl, VirtualKey.F1);
int hotkeyId2 = GlobalHotkey.RegisterHotkey(ModifiedKeys.Alt, VirtualKey.F2);
// 可以继续注册其他热键

4. 判断按键次数

当热键被按下时,会触发 HotkeyPressedEvent 事件。可以订阅这个事件来处理热键按下的情况,并获取按键的计数。示例:

GlobalHotkey.HotkeyPressedEvent += (sender, e) =>
{
    // 处理按键事件,可以通过 HotkeyPressedEventArgs 获取按键相关信息
    Console.WriteLine($"热键按下:{e.VirtualKey},按键次数:{e.PressedCount}");
};

5. 注意事项

  • 在注册热键之前,确保你的应用程序处于消息循环状态(MessageLoop)。
  • 每个热键的组合应该是独一无二的,避免冲突。
  • 在不再需要使用热键时,及时进行注销操作。

6. 示例代码

下面是一个简单的示例代码,演示了如何注册热键并处理热键按下事件:

// 注册热键
int hotkeyId = GlobalHotkey.RegisterHotkey(ModifiedKeys.Ctrl, VirtualKey.F1);

// 处理热键按下事件
GlobalHotkey.HotkeyPressedEvent += (sender, e) =>
{
    if (e.HotkeyId == hotkeyId)
    {
        // 处理 Ctrl + F1 按下事件
        Console.WriteLine($"热键按下:{e.VirtualKey},按键次数:{e.PressedCount}");
    }
};

全局热键工具类完整代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Dotnet.WinAPI.GlobalHotkey
{
	public delegate void HotkeyPressedEventHandler(object sender, HotkeyPressedEventArgs e);

	public static class GlobalHotkey
	{
		private static HotkeyMessageFilter _HotkeyMessageFilter = null;
		private static readonly Dictionary<int, KeyData> HotkeyData = new Dictionary<int, KeyData>();

		public static event HotkeyPressedEventHandler HotkeyPressedEvent;

		[DllImport("user32.dll", EntryPoint = "RegisterHotKey")]
		private static extern bool API_RegisterHotKey(IntPtr hWnd, int id, ModifiedKeys fsModifiers, VirtualKey vk);

		[DllImport("user32.dll", EntryPoint = "UnregisterHotKey")]
		private static extern bool API_UnregisterHotKey(IntPtr hWnd, int id);

		public static int RegisterHotkey(ModifiedKeys mks, VirtualKey vk)
		{
			try
			{
				if (mks == ModifiedKeys.None && vk == VirtualKey.None)
				{
					return (-1);
				}
				if (!Application.MessageLoop)
				{
					return (-2);
				}
				if (_HotkeyMessageFilter is null)
				{
					_HotkeyMessageFilter = new HotkeyMessageFilter();
					Application.AddMessageFilter(_HotkeyMessageFilter);
				}
				int hId = (((byte)mks) << 8) | (byte)vk;
				UnregisterHotkey(hId);
				int result = (API_RegisterHotKey(IntPtr.Zero, hId, mks, vk) ? hId : -1);
				if (result > 0)
				{
					System.Windows.Forms.Timer keyTimer = new System.Windows.Forms.Timer() { Interval = SystemInformation.DoubleClickTime };
					KeyData keyData = new KeyData() { KeyPressedCount = 0, KeyTimer = keyTimer };
					HotkeyData[hId] = keyData;
					keyTimer.Tick += (_s, _e) =>
					{
						keyTimer.Stop();
						keyData.KeyPressedCount = 0;
					};
				}
				return (result);
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
				return (-3);
			}
		}

		public static bool UnregisterHotkey(int hotkeyId)
		{
			try
			{
				bool res = API_UnregisterHotKey(IntPtr.Zero, hotkeyId);
				if (res && HotkeyData.ContainsKey(hotkeyId))
				{
					HotkeyData[hotkeyId].KeyTimer.Stop();
					HotkeyData.Remove(hotkeyId);
				}
				return (res);
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex);
				return (false);
			}
		}

		class HotkeyMessageFilter : IMessageFilter
		{
			private const int WM_HOTKEY = 786;

			bool IMessageFilter.PreFilterMessage(ref Message m)
			{
				switch (m.Msg)
				{
					case WM_HOTKEY:
						try
						{
							int hId = m.WParam.ToInt32();
							if (!(HotkeyPressedEvent is null) && HotkeyData.ContainsKey(hId))
							{
								HotkeyData[hId].KeyTimer.Start();
								HotkeyData[hId].KeyPressedCount++;
								int lParam = m.LParam.ToInt32();
								byte vk = (byte)(lParam >> 16);
								byte mks = (byte)(lParam & 0Xffff);
								HotkeyPressedEvent?.Invoke(this, new HotkeyPressedEventArgs(hId, (ModifiedKeys)mks, (VirtualKey)vk, HotkeyData[hId].KeyPressedCount));
							}
						}
						catch (Exception ex)
						{
							Debug.WriteLine(ex);
						}
						return (true);
						break;
				}
				return (false);
			}
		}
	}

	public class HotkeyPressedEventArgs : EventArgs
	{
		public int HotkeyId { get; }
		public ModifiedKeys ModifiedKeys { get; }
		public VirtualKey VirtualKey { get; }
		public int PressedCount { get; }

		public HotkeyPressedEventArgs(int hotkeyId, ModifiedKeys modifiedKeys, VirtualKey virtualKey, int pressedCount)
		{
			this.HotkeyId = hotkeyId;
			this.ModifiedKeys = ModifiedKeys;
			this.VirtualKey = virtualKey;
			this.PressedCount = pressedCount;
		}

		public override string ToString()
		{
			return ($"{nameof(this.HotkeyId)}:{this.HotkeyId}, {nameof(this.ModifiedKeys)}:{this.ModifiedKeys}, {nameof(this.VirtualKey)}:{this.VirtualKey}, {nameof(this.PressedCount)}:{this.PressedCount}");
		}
	}

	class KeyData
	{
		public int KeyPressedCount = 0;
		public System.Windows.Forms.Timer KeyTimer = null;
	}

	[Flags]
	public enum ModifiedKeys : byte
	{
		None = 0,
		Alt = 1,
		Ctrl = 2,
		Shift = 4,
		Windows = 8
	}

	public enum VirtualKey : uint
	{
		None = 0,
		BackSpace = 8,
		Tab = 9,
		Clear = 12,
		Enter = 13,
		Pause = 19,
		Caps = 20,
		Escape = 27,
		Space = 32,
		PageUp = 33,
		PageDown = 34,
		End = 35,
		Home = 36,
		LeftArrow = 37,
		UpArrow = 38,
		RightArrow = 39,
		DownArrow = 40,
		PrintScreen = 44,
		Insert = 45,
		Delete = 46,
		Zero = 48,
		One = 49,
		Two = 50,
		Three = 51,
		Four = 52,
		Five = 53,
		Six = 54,
		Seven = 55,
		Eight = 56,
		Nine = 57,
		A = 65,
		B = 66,
		C = 67,
		D = 68,
		E = 69,
		F = 70,
		G = 71,
		H = 72,
		I = 73,
		J = 74,
		K = 75,
		L = 76,
		M = 77,
		N = 78,
		O = 79,
		P = 80,
		Q = 81,
		R = 82,
		S = 83,
		T = 84,
		U = 85,
		V = 86,
		W = 87,
		X = 88,
		Y = 89,
		Z = 90,
		Apps = 93,
		NumPad0 = 96,
		NumPad1 = 97,
		NumPad2 = 98,
		NumPad3 = 99,
		NumPad4 = 100,
		NumPad5 = 101,
		NumPad6 = 102,
		NumPad7 = 103,
		NumPad8 = 104,
		NumPad9 = 105,
		F1 = 112,
		F2 = 113,
		F3 = 114,
		F4 = 115,
		F5 = 116,
		F6 = 117,
		F7 = 118,
		F8 = 119,
		F9 = 120,
		F10 = 121,
		F11 = 122,
		F12 = 123,
		NumLock = 144,
		ScrollLock = 145,
		Semicolon = 186,
		Equal = 187,
		Comma = 188,
		Minus = 189,
		Dot = 190,
		Slash = 191,
		BackQuote = 192,
		LeftBracket = 219,
		BackSlash = 220,
		RightBracket = 221,
		Quote = 222,
	}
}

【本文作者 张赐荣】