我有一个使用 Win32 RegisterHotKey 方法和 HwndSource.AddHook 消息处理程序的 WPF 应用程序。
如果我不使用 HwndSourceHook 的已处理参数采取任何操作,我正在尝试添加不处理热键的功能。
但是,将 Handling 设置为 false 或 true 不会改变行为,它始终拦截注册的热键,并且不会将它们传递到按下它们的应用程序。
我有另一个使用 Windows 窗体 Application.AddMessageFilter 处理程序的应用程序,在这种情况下,当返回 false 表示消息未处理时,它可以使用相同的热键正常工作。
源代码可以在这里找到:https://github.com/trevorlloydelliott/eve-switcher/blob/master/HotkeyHandler.cs
我已经像这样简化了我的逻辑:
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_HOTKEY = 0x0312;
if (msg == WM_HOTKEY)
{
handled = false;
return IntPtr.Zero;
}
}
我也尝试过不使用 Window 和 HwndSource 挂钩,而是使用
ComponentDispatcher.ThreadFilterMessage
事件的方法来拦截所有 WM_HOTKEY 并将处理设置为 false,如下所示:
private void ComponentDispatcher_ThreadFilterMessage(ref MSG msg, ref bool handled)
{
const int WM_HOTKEY = 0x0312;
if (msg .message == WM_HOTKEY)
{
handled = false;
}
}
这也无法将热键传递到底层应用程序。在我的测试中,我在打开记事本的情况下尝试使用 Tab 热键。我的应用程序将使用 RegisterHotKey 注册 Tab 热键,然后将处理设置为 false,但热键永远不会到达记事本。一旦我关闭我的应用程序,它就会到达记事本。
我使用 RegisterHotKey 的方式与在其他 Windows 窗体应用程序中相同。
Win32
RegisterHotKey()
定义了系统范围的热键。热键是全局的,并且在常规键盘输入处理之前进行处理,这意味着如果您成功注册热键,按下该键将导致您收到热键消息而不是正常的键盘输入消息。如果发生热键按下事件,则其他应用程序将看不到该按键按下。这是设计使然。
RegisterHotKey
是使用全局热键的正确方法。重点是,通过为应用程序指定热键,您可以确保该键在系统上运行的所有应用程序中是唯一的,并且您的应用程序将始终接收该键的按键事件。
使用简单的键(例如
Tab
)作为全局热键会带来与具有焦点的应用程序的本地热键冲突的问题。因此,全局热键应该能够避免用户与他常用的应用程序发生冲突。
但是,WPF 中还有另一种处理热键的方法。您可以通过使用
WH_KEYBOARD_LL
方法调用安装它来使用低级 SetWindowsHookEx
键盘挂钩。它使您能够监视输入队列中的键盘输入事件。
这是使用钩子的类:
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Input;
namespace KeyboardHooks
{
public delegate void HotkeyPressedEventHandler(object sender, HotkeyPressedEventArgs e);
public class HotkeyPressedEventArgs : EventArgs
{
public Key Key { get; }
public ModifierKeys Modifiers { get; }
public bool Handled { get; set; }
public HotkeyPressedEventArgs(Key key, ModifierKeys modifiers)
{
Key = key;
Modifiers = modifiers;
}
}
public class HotkeyManager
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
[StructLayout(LayoutKind.Sequential)]
private class KeyboardHookStruct
{
/// <summary>
/// Specifies a virtual-key code. The code must be a value in the range 1 to 254.
/// </summary>
public int vkCode;
/// <summary>
/// Specifies a hardware scan code for the key.
/// </summary>
public int scanCode;
/// <summary>
/// Specifies the extended-key flag, event-injected flag, context code, and transition-state flag.
/// </summary>
public int flags;
/// <summary>
/// Specifies the time stamp for this message.
/// </summary>
public int time;
/// <summary>
/// Specifies extra information associated with the message.
/// </summary>
public int dwExtraInfo;
}
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(
int idHook,
int nCode,
int wParam,
IntPtr lParam);
private int keyboardHook = 0;
public HotkeyManager()
{
}
public event HotkeyPressedEventHandler HotkeyPressed;
public void Start()
{
// install Keyboard hook only if it is not installed and must be installed
if (keyboardHook == 0)
{
// Create an instance of HookProc.
keyboardHook = SetWindowsHookEx(
WH_KEYBOARD_LL,
KeyboardHookProc,
Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),
0);
// If SetWindowsHookEx fails.
if (keyboardHook == 0)
{
// Returns the error code returned by the last unmanaged function called using platform invoke that has the DllImportAttribute.SetLastError flag set.
int errorCode = Marshal.GetLastWin32Error();
// do cleanup
Stop(false);
// Initializes and throws a new instance of the Win32Exception class with the specified error.
throw new Win32Exception(errorCode);
}
}
}
public void Stop(bool throwExceptions)
{
// if keyboard hook set and must be uninstalled
if (keyboardHook != 0)
{
// uninstall hook
int retKeyboard = UnhookWindowsHookEx(keyboardHook);
// reset invalid handle
keyboardHook = 0;
// if failed and exception must be thrown
if (retKeyboard == 0 && throwExceptions)
{
//Returns the error code returned by the last unmanaged function called using platform invoke that has the DllImportAttribute.SetLastError flag set.
int errorCode = Marshal.GetLastWin32Error();
//Initializes and throws a new instance of the Win32Exception class with the specified error.
throw new Win32Exception(errorCode);
}
}
}
private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam)
{
bool handled = false;
if (nCode >= 0 && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
if (HotkeyPressed != null)
{
// read structure KeyboardHookStruct at lParam
KeyboardHookStruct khStruct =
(KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
if (khStruct != null)
{
Key key = KeyInterop.KeyFromVirtualKey(khStruct.vkCode);
HotkeyPressedEventArgs args = new HotkeyPressedEventArgs(key, Keyboard.Modifiers);
HotkeyPressed.Invoke(this, args);
handled = args.Handled;
}
}
}
// if event handled in application do not handoff to other listeners
if (handled)
{
return 1;
}
return CallNextHookEx(keyboardHook, nCode, wParam, lParam);
}
}
}
使用示例:
HotkeyManager hotkeyManager = new HotkeyManager();
hotkeyManager.HotkeyPressed += HotkeyManagerOnHotkeyPressed;
hotkeyManager.Start();
private void HotkeyManagerOnHotkeyPressed(object sender, HotkeyPressedEventArgs e)
{
if (e.Key == Key.Tab && e.Modifiers == ModifierKeys.None)
{
Console.WriteLine("Tab pressed!");
//e.Handled = true;
}
}
关于WinForm的
PreFilterMessage
和WPF的HwndSourceHook
之间的区别,我猜第一个是在消息传递给任何事件处理程序之前调用的,而第二个本身就是一个事件处理程序。所以他们的行为有所不同。
奖励。还有另一种方法可以将未处理的热键按下进一步传递给其他应用程序,但它不太可靠。如果全局热键未处理,您可以取消注册它,使用此键发送系统键盘事件,然后再次注册热键。基本上,您执行以下操作:
if (!handled)
{
UnregisterHotkey(hotkey.Gesture);
KeyboardMessage.Send(hotkey.Gesture.Key);
RegisterHotkey(hotkey.Gesture);
}
KeyboardMessage
课程您可以在这里找到。
看起来不错,但在 .net 8.0 下不起作用,因为当我按 ctrl 时 UnsafeNativeMethod。