WPF入队并重放关键事件

Mis*_*hax 11 keyboard wpf

我正在尝试提高WPF业务应用程序的响应能力,以便当用户在服务器响应之后等待新屏幕出现的"屏幕之间"屏幕时,他们仍然可以输入数据.我能够对事件进行排队(在后台面板上使用PreviewKeyDown事件处理程序),但是当我加载时,我很难将新事件中的事件抛出.特别是新面板上的Te​​xtBoxes没有拾取文本.我已尝试提升相同的事件(在捕获它们时将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);
        }
Run Code Online (Sandbox Code Playgroud)

这是另一个:

        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);
        }
Run Code Online (Sandbox Code Playgroud)

而另一个:

        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 }
            );
        }
Run Code Online (Sandbox Code Playgroud)

我注意到的一个奇怪的事情是,当使用InputManager方法(#2)时,会出现空格.但普通的文本键却没有.

Mik*_*chs 8

当我做一些研究时,相同的资源出现在我身上,所以我认为你在答案中所做的是非常有效的.

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

启动演示应用程序,按键1 space 2 space 3 tab 4 space 5 space 6,然后单击按钮将生成以下内容:

在此输入图像描述

XAML:

<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>
Run Code Online (Sandbox Code Playgroud)

代码背后:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)