You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 lines
5.2 KiB

  1. using System;
  2. using System.Runtime.InteropServices;
  3. using System.Windows.Forms;
  4. namespace Apewer.WinForm
  5. {
  6. /// <summary>热键。</summary>
  7. public sealed class HotKey : IMessageFilter
  8. {
  9. #region instance
  10. IntPtr _pointer = IntPtr.Zero;
  11. Keys _key = Keys.None;
  12. ModifierKey _modifier = ModifierKey.None;
  13. int _id = 0;
  14. Action<HotKey> _callback = null;
  15. /// <summary>热键的标识符。 </summary>
  16. public int Id { get => _id; }
  17. /// <summary>修饰键。</summary>
  18. public ModifierKey ModifierKey { get => _modifier; }
  19. /// <summary>按键。</summary>
  20. public Keys Key { get => _key; }
  21. private HotKey(IntPtr pointer, Keys key, ModifierKey modifier, Action<HotKey> callback)
  22. {
  23. if (callback == null) throw new ArgumentNullException(nameof(callback), "回调函数不能为空。");
  24. switch (key)
  25. {
  26. case Keys.KeyCode:
  27. case Keys.Modifiers:
  28. case Keys.None:
  29. case Keys.Shift:
  30. case Keys.Control:
  31. case Keys.Alt:
  32. throw new ArgumentException($"按键【{key}】无法注册为热键。");
  33. }
  34. // 应用程序必须在0x0000到0xBFFF的范围内指定 ID 值。
  35. // 共享 DLL 必须通过 0xFFFF(GlobalAddAtom 函数返回的范围)指定0xC000区域中的值。
  36. _id = ((int)modifier << 16) & (int)key;
  37. _pointer = pointer;
  38. _callback = callback;
  39. _key = key;
  40. _modifier = modifier;
  41. }
  42. /// <summary></summary>
  43. public override string ToString()
  44. {
  45. var alt = (ModifierKey & ModifierKey.ALT) == ModifierKey.ALT ? "ALT + " : "";
  46. var ctrl = (ModifierKey & ModifierKey.CTRL) == ModifierKey.CTRL ? "CTRL + " : "";
  47. var shift = (ModifierKey & ModifierKey.SHIFT) == ModifierKey.SHIFT ? "SHIFT + " : "";
  48. var win = (ModifierKey & ModifierKey.WIN) == ModifierKey.WIN ? "WIN + " : "";
  49. var result = $"{alt}{ctrl}{shift}{win}{Key}";
  50. return result;
  51. }
  52. #endregion
  53. #region bind
  54. const int WM_HOTKEY = 0x0312;
  55. [DllImport("user32.dll")]
  56. static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, Keys vk);
  57. [DllImport("user32.dll")]
  58. static extern bool UnregisterHotKey(IntPtr hWnd, int id);
  59. bool IMessageFilter.PreFilterMessage(ref Message m)
  60. {
  61. if (m.Msg == WM_HOTKEY && (int)m.WParam == _id)
  62. {
  63. _callback?.Invoke(this);
  64. return true;
  65. }
  66. return false;
  67. }
  68. void Bind()
  69. {
  70. var success = RegisterHotKey(_pointer, _id, (int)_modifier, _key);
  71. if (!success)
  72. {
  73. var error = Marshal.GetLastWin32Error();
  74. if (error == 1409) throw new Exception($"热键【{ToString()}】已被占用。");
  75. throw new Exception($"错误 {error}:注册热键【{ToString()}】失败。");
  76. }
  77. Application.AddMessageFilter(this);
  78. }
  79. /// <summary>释放热键。</summary>
  80. public void Release()
  81. {
  82. UnregisterHotKey(Application.OpenForms[0].Handle, _id);
  83. }
  84. #endregion
  85. // Virtual-Key 代码
  86. // https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
  87. /// <summary>绑定热键。</summary>
  88. /// <param name="form">将接收热键生成的 WM_HOTKEY(0x0312)消息的窗口句柄。</param>
  89. /// <param name="key">热键的虚拟键代码。</param>
  90. /// <param name="modifier">修饰键。</param>
  91. /// <param name="callback">回调。</param>
  92. /// <exception cref="ArgumentNullException" />
  93. /// <exception cref="ArgumentException" />
  94. /// <exception cref="Exception" />
  95. public static HotKey Bind(Form form, Keys key, ModifierKey modifier, Action<HotKey> callback)
  96. {
  97. if (form == null) throw new ArgumentNullException(nameof(form));
  98. if (callback == null) throw new ArgumentNullException(nameof(callback));
  99. var hotkey = new HotKey(form.Handle, key, modifier, callback);
  100. hotkey.Bind();
  101. return hotkey;
  102. }
  103. /// <summary>绑定热键。</summary>
  104. /// <remarks>WM_HOTKEY 消息将发布到调用线程的消息队列,并且必须在消息循环中进行处理。</remarks>
  105. /// <param name="key">热键的虚拟键代码。</param>
  106. /// <param name="modifier">修饰键。</param>
  107. /// <param name="callback">回调。</param>
  108. /// <exception cref="ArgumentNullException" />
  109. /// <exception cref="ArgumentException" />
  110. /// <exception cref="Exception" />
  111. public static HotKey Bind(Keys key, ModifierKey modifier, Action<HotKey> callback)
  112. {
  113. if (callback == null) throw new ArgumentNullException(nameof(callback));
  114. var hotkey = new HotKey(IntPtr.Zero, key, modifier, callback);
  115. hotkey.Bind();
  116. return hotkey;
  117. }
  118. }
  119. }