11

ユーザーがサーバーの応答後に新しい画面が表示されるのを待っている画面の「間」にいるときでも、データを入力できるように、WPF ビジネス アプリケーションの応答性を改善しようとしています。(バックグラウンド パネルで PreviewKeyDown イベント ハンドラーを使用して) イベントをキューに入れることはできますが、新しいパネルが読み込まれると、デキューしたイベントを新しいパネルにスローするだけで問題が発生します。特に、新しいパネルの TextBoxes はテキストを取得していません。同じイベントを発生させようとしました (それらをキャプチャするときに Handled を true に設定し、再度発生させるときに Handled を false に設定します)、新しい KeyDown イベント、新しい PreviewKeyDown イベントを作成し、ProcessInput を実行し、パネルで RaiseEvent を実行し、右側にフォーカスを設定しましたTextBox と、TextBox で RaiseEvent を実行するなど、多くのこと。

本当に単純なはずですが、わかりません。

ここに私が試したことのいくつかがあります。EventQ と呼ばれる KeyEventArgs のキューを考えてみましょう。

動作しないことが 1 つあります。

        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 }
            );
        }

私が気付いた奇妙なことの 1 つは、InputManager メソッド (#2) を使用するとスペースが表示されることです。しかし、通常のテキスト キーはそうではありません。

4

3 に答える 3

10

私がいくつかの調査を行ったときに同じリソースが見つかったので、あなたの答えであなたがしていることはかなり有効だと思います.

私は調べて、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>

コードビハインド:

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;
        }
    }
}
于 2013-04-27T17:41:31.910 に答える
3

エンキュー システムは、マルチスレッド UI が問題なく機能できるようにするプロジェクトの一部として、自分でやりたかったことです (1 つのスレッドが別のスレッドにイベントをルーティングします)。わずかな問題があります。つまり、WPF には INPUT イベントを挿入するパブリック API がありません。以下は、私が数週間前に話したマイクロソフトの従業員の 1 人からのコピー/貼り付けです。

「WPF は、適切な方法で入力イベントを注入するためのパブリック メソッドを公開していません。このシナリオは、パブリック API ではサポートされていません。おそらく、多くのリフレクションやその他のハッキングを行う必要があります。たとえば、WPF は一部の入力を次のように扱います。」これは、メッセージ ポンプから送信されたことを認識しているためです。入力イベントを発生させるだけでは、イベントは信頼されません。"

戦略を再考する必要があると思います。

于 2013-04-23T19:29:50.633 に答える
1

ご支援いただきありがとうございますが、SOコミュニティから実際に解決策を見つけていないので、これが解決策に最も近いと思われるため、自分で答えます。Erti-Chris が言う「ハック」は、私たちに残されたもののようです。私は問題を分解することができたので、まったく新しいキーボード ハンドラーを作成しているという感覚はありません。私が従っているアプローチは、イベントを InputManager 処理と TextComposition の組み合わせに分解することです。KeyEventArgs (元のものまたは自分で作成したもの) をスローしても、PreviewKeyDown ハンドラーに登録されていないようです。

難しさの一部は Erti-Chris の投稿の情報によるもので、別の部分は TextBoxes が矢印キーなどの特定のキーに、文字「A」などの通常のキーとは異なる反応を示すことに関連しているようです。

これを進めるために、この投稿の情報が役立つことがわかりました。

http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b657618e-7fc6-4e6b-9b62-1ffca25d186b

これが私が今からいくつかの肯定的な結果を得ている解決策です:

    Keyboard.Focus(tbOne); // the first element on the Panel to get the focus
    while (EventQ.Count > 0) 
    {
        KeyEventArgs kea = EventQ.Dequeue();
        kea.Handled = false;
        var routedEvent = KeyDownEvent; 

        KeyEventArgs keanew = new KeyEventArgs(
                     Keyboard.PrimaryDevice,
                     PresentationSource.FromVisual(tbOne),
                     kea.Timestamp,
                     kea.Key) { RoutedEvent = routedEvent, Handled = false };
        keanew.Source = tbOne;

        bool itWorked = InputManager.Current.ProcessInput(keanew);
        if (itWorked)
        {
            continue;
            // at this point spaces, backspaces, tabs, arrow keys, deletes are handled
        }
        else
        {
            String keyChar = kea.Key.ToString();
            if (keyChar.Length > 1)
            {
                // handle special keys; letters are one length
                if (keyChar == "OemPeriod") keyChar = ".";
                if (keyChar == "OemComma") keyChar = ",";
            }
            TextCompositionManager.StartComposition(new TextComposition(InputManager.Current, Keyboard.FocusedElement, keyChar));
        }
    }

誰かが私にもっと良い方法を示すことができれば、あなたの貢献を答えとしてマークできることを嬉しく思いますが、今のところこれが私が取り組んでいるものです.

于 2013-04-27T08:44:55.553 に答える