WPF 入队和重放关键事件

标签 wpf keyboard

我正在尝试提高 WPF 业务应用程序的响应能力,以便当用户在等待服务器响应后出现新屏幕的“之间”屏幕时,他们仍然可以输入数据。我可以对事件进行排队(在后台面板上使用 PreviewKeyDown 事件处理程序),但是我很难在加载后将我出列的事件扔回新面板。特别是新面板上的 TextBoxes 没有拾取文本。我尝试过引发相同的事件(在捕获它们时将 Handled 设置为 true,再次引发它们时将 Handled 设置为 false)创建新的 KeyDown 事件、新的 PreviewKeyDown 事件、执行 ProcessInput、在面板上执行 RaiseEvent、将焦点设置在右侧TextBox 和在 TextBox 上做 RaiseEvent,很多东西。

看起来应该很简单,但我无法弄清楚。

以下是我尝试过的一些方法。考虑一个名为 EventQ 的 KeyEventArgs 队列:

这是行不通的一件事:

        while (EventQ.Count > 0)
        {
            KeyEventArgs kea = EventQ.Dequeue();
            tbOne.Focus(); // tbOne is a text box
            kea.Handled = false;
            this.RaiseEvent(kea);
        }

这是另一个:
        while (EventQ.Count > 0)
        {
            KeyEventArgs kea = EventQ.Dequeue();
            tbOne.Focus(); // tbOne is a text box
            var key = kea.Key;                    // Key to send
            var routedEvent = Keyboard.PreviewKeyDownEvent; // Event to send
            KeyEventArgs keanew = new KeyEventArgs(
                Keyboard.PrimaryDevice,
                PresentationSource.FromVisual(this),
                0,
                key) { RoutedEvent = routedEvent, Handled = false };

            InputManager.Current.ProcessInput(keanew);
        }

还有一个:
        while (EventQ.Count > 0)
        {
            KeyEventArgs kea = EventQ.Dequeue();
            tbOne.Focus(); // tbOne is a text box
            var key = kea.Key;                    // Key to send
            var routedEvent = Keyboard.PreviewKeyDownEvent; // Event to send
            this.RaiseEvent(
              new KeyEventArgs(
                Keyboard.PrimaryDevice,
                PresentationSource.FromVisual(this),
                0,
                key) { RoutedEvent = routedEvent, Handled = false }
            );
        }

我注意到的一件奇怪的事情是,在使用 InputManager 方法 (#2) 时确实会出现空格。但是普通的文本键没有。

最佳答案

当我进行一些研究时,我发现了相同的资源,因此我认为您在答案中所做的工作非常有效。

我查看并找到了另一种方法,使用 Win32 API。我不得不引入一些线程和小延迟,因为出于某种原因,关键事件没有以正确的顺序重播。总的来说,我认为这个解决方案更容易,而且我还想出了如何包含修饰键(通过使用 Get/SetKeyboardState 函数)。大写字母有效,键盘快捷键也应如此。

启动演示应用程序,按下键 1 space 2 space 3 tab 4 space 5 space 6 ,然后单击该按钮会产生以下结果:

enter image description here

Xml:

<UserControl x:Class="WpfApplication1.KeyEventQueueDemo"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" >

    <StackPanel>
        <TextBox x:Name="tbOne" Margin="5,2" />
        <TextBox x:Name="tbTwo" Margin="5,2" />
        <Button x:Name="btn" Content="Replay key events" Margin="5,2" />
    </StackPanel>
</UserControl>

后面的代码:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;

namespace WpfApplication1
{
    /// <summary>
    /// Structure that defines key input with modifier keys
    /// </summary>
    public struct KeyAndState
    {
        public int Key;
        public byte[] KeyboardState;

        public KeyAndState(int key, byte[] state)
        {
            Key = key;
            KeyboardState = state;
        }
    }

    /// <summary>
    /// Demo to illustrate storing keyboard input and playing it back at a later stage
    /// </summary>
    public partial class KeyEventQueueDemo : UserControl
    {
        private const int WM_KEYDOWN = 0x0100;

        [DllImport("user32.dll")]
        static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

        [DllImport("user32.dll")]
        static extern bool GetKeyboardState(byte[] lpKeyState);

        [DllImport("user32.dll")]
        static extern bool SetKeyboardState(byte[] lpKeyState);

        private IntPtr _handle;
        private bool _isMonitoring = true;

        private Queue<KeyAndState> _eventQ = new Queue<KeyAndState>();

        public KeyEventQueueDemo()
        {
            InitializeComponent();

            this.Focusable = true;
            this.Loaded += KeyEventQueueDemo_Loaded;
            this.PreviewKeyDown += KeyEventQueueDemo_PreviewKeyDown;
            this.btn.Click += (s, e) => ReplayKeyEvents();
        }

        void KeyEventQueueDemo_Loaded(object sender, RoutedEventArgs e)
        {
            this.Focus(); // necessary to detect previewkeydown event
            SetFocusable(false); // for demo purpose only, so controls do not get focus at tab key

            // getting window handle
            HwndSource source = (HwndSource)HwndSource.FromVisual(this);
            _handle = source.Handle;
        }

        /// <summary>
        /// Get key and keyboard state (modifier keys), store them in a queue
        /// </summary>
        void KeyEventQueueDemo_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (_isMonitoring)
            {
                int key = KeyInterop.VirtualKeyFromKey(e.Key);
                byte[] state = new byte[256];
                GetKeyboardState(state); 
                _eventQ.Enqueue(new KeyAndState(key, state));
            }
        }

        /// <summary>
        /// Replay key events from queue
        /// </summary>
        private void ReplayKeyEvents()
        {
            _isMonitoring = false; // no longer add to queue
            SetFocusable(true); // allow controls to take focus now (demo purpose only)

            MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); // set focus to first control

            // thread the dequeueing, because the sequence of inputs is not preserved 
            // unless a small delay between them is introduced. Normally the effect this
            // produces should be very acceptable for an UI.
            Task.Run(() =>
            {
                while (_eventQ.Count > 0)
                {
                    KeyAndState keyAndState = _eventQ.Dequeue();

                    Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                    {
                        SetKeyboardState(keyAndState.KeyboardState); // set stored keyboard state
                        PostMessage(_handle, WM_KEYDOWN, keyAndState.Key, 0);
                    }));

                    System.Threading.Thread.Sleep(5); // might need adjustment
                }
            });
        }

        /// <summary>
        /// Prevent controls from getting focus and taking the input until requested
        /// </summary>
        private void SetFocusable(bool isFocusable)
        {
            tbOne.Focusable = isFocusable;
            tbTwo.Focusable = isFocusable;
            btn.Focusable = isFocusable;
        }
    }
}

关于WPF 入队和重放关键事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16076681/

相关文章:

c# - 如何使用 MVVM 将不同列表绑定(bind)到每行中的 DataGridComboBoxColumn

c# - C#/WPF 中的 NEON /发光效果

c# - 绑定(bind)到 UI 的调度程序和集合

ios - 点击手势识别器干扰清除按钮

c++ - 如何在使用keybd_event后停止按键?

android - 在平板电脑上显示 DialogFragment 时隐藏键盘?

haskell - 使用程序员 dvorak 键盘布局(移位数字)在 xmonad 中切换工作区

c# - 将哈希表绑定(bind)到 WPF 组合框

c# - 在没有任何第三方/库的情况下,是否有任何替代方法可以在 C# 中使用 WPF 处理 DICOM 图像?

python - 简单的 Linux/X11 程序来获取并保持键盘焦点?