c# - WPF 消息处理不尊重使用 RegisterHotKey 处理的参数

标签 c# wpf .net-core-3.1

我有一个使用 Win32 RegisterHotKey 方法和 HwndSource.AddHook 消息处理程序的 WPF 应用程序。
如果我不使用 HwndSourceHook 的处理参数采取任何操作,我正在尝试添加不处理热键的功能。
但是,设置为 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 Hook ,而是使用 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 安装键盘 Hook 方法调用。它使您能够监视输入队列中的键盘输入事件。
这是使用钩子(Hook)的类:

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您可以找到的类(class)here .

关于c# - WPF 消息处理不尊重使用 RegisterHotKey 处理的参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66395223/

相关文章:

c# - 找不到方法 - AWS .NET Core 3.1 Mock Lambda 测试工具

c# - 启动服务未返回有关失败的足够信息

c# - 点聚类算法

c# - 如何将 async/await 与返回 ObservableCollection<T> 的方法一起使用

c# - 将 ListView 滚动到顶部 ItemsSource Changed 被意外调用

c# - 跨线程操作WPF控件报错: cannot

c# - C# 3.0 中的 Action

c# - 当我 "new"时 ObservableCollection 失去绑定(bind)

c# - 本地化的 mvc3 应用程序