web-dev-qa-db-ja.com

テキストコンテンツのプログラムによる変更とユーザーによる変更に関するTextBoxTextChangedイベント

プログラムによるテキストの変更(ボタンクリックハンドラーイベントなど)とユーザー入力(テキストの入力、切り取り、貼り付け)を区別したいと思います。
出来ますか?

19
Greg

組み込みのWPFTextBoxを使用したいだけなら、それが可能だとは思いません。

ここのSilverlightフォーラムにも同様の議論があります: http://forums.silverlight.net/p/119128/268453.aspx まったく同じ質問ではありませんが、それに似たアイデアだと思います元の投稿であなたのためにトリックをするかもしれません。サブクラス化されたTextBoxにSetTextメソッドを設定します。このメソッドは、テキストを変更する前にフラグを設定し、後で元に戻します。次に、TextChangedイベント内のフラグを確認できます。もちろん、これには、その方法を使用するためにすべてのプログラムによるテキストの変更が必要になりますが、プロジェクトを十分に制御して、それが機能すると思うことを義務付けている場合。

5
JHunz

TextBoxでのユーザー入力は、次のように識別できます。

  • 入力:PreviewTextInputイベント
  • バックスペース、削除、入力:PreviewKeyDownイベント
  • 貼り付け:DataObject.PastingEvent

これらの3つのイベントをboolフラグと組み合わせて、上記のいずれかがTextChangedイベントの前に発生したかどうかを示し、更新の理由がわかります。

入力と貼り付けは簡​​単ですが、Backspaceが常にTextChangedをトリガーするとは限りません(たとえば、テキストが選択されておらず、カーソルが位置0にある場合)。したがって、PreviewTextInputにはいくつかのロジックが必要です。

これは、上記のロジックを実装し、TextChangedが発生したときにboolフラグを指定してコマンドを実行するアタッチされた動作です。

<TextBox ex:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />

そしてコードであなたは次のようなアップデートのソースを見つけることができます

private void TextChanged_Executed(object parameter)
{
    object[] parameters = parameter as object[];
    object sender = parameters[0];
    TextChangedEventArgs e = (TextChangedEventArgs)parameters[1];
    bool userInput = (bool)parameters[2];

    if (userInput == true)
    {
        // User input update..
    }
    else
    {
        // Binding, Programatic update..
    }
}

効果を示す小さなサンプルプロジェクトを次に示します。 SourceOfTextChanged.Zip

TextChangedBehavior

public class TextChangedBehavior
{
    public static DependencyProperty TextChangedCommandProperty =
        DependencyProperty.RegisterAttached("TextChangedCommand",
                                            typeof(ICommand),
                                            typeof(TextChangedBehavior),
                                            new UIPropertyMetadata(TextChangedCommandChanged));

    public static void SetTextChangedCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(TextChangedCommandProperty, value);
    }

    // Subscribe to the events if we have a valid command
    private static void TextChangedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        TextBox textBox = target as TextBox;
        if (textBox != null)
        {
            if ((e.NewValue != null) && (e.OldValue == null))
            {
                textBox.PreviewKeyDown += textBox_PreviewKeyDown;
                textBox.PreviewTextInput += textBox_PreviewTextInput;
                DataObject.AddPastingHandler(textBox, textBox_TextPasted);
                textBox.TextChanged += textBox_TextChanged;
            }
            else if ((e.NewValue == null) && (e.OldValue != null))
            {
                textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
                textBox.PreviewTextInput -= textBox_PreviewTextInput;
                DataObject.RemovePastingHandler(textBox, textBox_TextPasted);
                textBox.TextChanged -= textBox_TextChanged;
            }
        }
    }

    // Catches User input
    private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        SetUserInput(textBox, true);
    }
    // Catches Backspace, Delete, Enter
    private static void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        if (e.Key == Key.Return)
        {
            if (textBox.AcceptsReturn == true)
            {
                SetUserInput(textBox, true);
            }
        }
        else if (e.Key == Key.Delete)
        {
            if (textBox.SelectionLength > 0 || textBox.SelectionStart < textBox.Text.Length)
            {
                SetUserInput(textBox, true);
            }
        }
        else if (e.Key == Key.Back)
        {
            if (textBox.SelectionLength > 0 || textBox.SelectionStart > 0)
            {
                SetUserInput(textBox, true);
            }
        }
    }
    // Catches pasting
    private static void textBox_TextPasted(object sender, DataObjectPastingEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        if (e.SourceDataObject.GetDataPresent(DataFormats.Text, true) == false)
        {
            return;
        }
        SetUserInput(textBox, true);
    }
    private static void textBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        TextBox textBox = sender as TextBox;
        TextChangedFired(textBox, e);
        SetUserInput(textBox, false);
    }

    private static void TextChangedFired(TextBox sender, TextChangedEventArgs e)
    {
        ICommand command = (ICommand)sender.GetValue(TextChangedCommandProperty);
        object[] arguments = new object[] { sender, e, GetUserInput(sender) };
        command.Execute(arguments);
    }

    #region UserInput

    private static DependencyProperty UserInputProperty =
        DependencyProperty.RegisterAttached("UserInput",
                                            typeof(bool),
                                            typeof(TextChangedBehavior));
    private static void SetUserInput(DependencyObject target, bool value)
    {
        target.SetValue(UserInputProperty, value);
    }
    private static bool GetUserInput(DependencyObject target)
    {
        return (bool)target.GetValue(UserInputProperty);
    }

    #endregion // UserInput
}
25
Fredrik Hedblad

JHunzの答えと同様に、ブール値のメンバー変数をコントロールに追加するだけです。

bool programmaticChange = false;

プログラムによる変更を行う場合は、次のようにします。

programmaticChange = true;
// insert changes to the control text here
programmaticChange = false;

イベントハンドラーでは、programmaticChangeの値を調べて、プログラムによって変更されたかどうかを判断する必要があります。

かなり明白であまりエレガントではありませんが、実行可能でシンプルです。

4
dodgy_coder

正確な要求に応じて、TextChangedイベントでTextBox.IsFocusedを使用して、手動入力を決定できます。これは明らかにプログラムの変更のすべての方法をカバーするわけではありませんが、多くの例でうまく機能し、そうするためのかなりクリーンで節約的な方法です。

基本的に、これは次の場合に機能します。
...プログラムによる変更はすべて手動の変更(ボタンの押下など)に基づいています。
次の場合は機能しません。
...プログラムの変更は完全にコードに基づいています(タイマーなど)。

コード例:

textBox.TextChanged += (sender, args) =>
    if (textBox.IsFocused)
    {
        //do something for manual input
    }
    else
    {
        //do something for programmatical input
    }
}
3
Tim Pohlmann

Dodgy_coderの部分的なクレジット(あなたが望む美しいデザインに適合しないことに同意しましたが、最高の妥協点です)。あなたがカバーしたいすべてを考慮してください:

  1. マウスでTB2からTB1にドラッグしてテキストを移動する
  2. カット(ctrl-x、プログラマティックカット、マウスメニューカット)
  3. 貼り付け(ctrl-v、プログラムによる貼り付け、mouse-menu-paste)
  4. 元に戻す(ctrl-z、プログラムによる元に戻す)
  5. やり直し(ctrl-Y、プログラムによるやり直し)
  6. 削除とバックスペース
  7. キーボードテキスト(英数字+記号+スペース)

除外するものを検討してください。

  1. テキストのプログラム設定

コード

public class MyTB : TextBox
{
    private bool _isTextProgrammaticallySet = false;

    public new string Text
    {
        set
        {
            _isTextProgrammaticallySet = true;
            base.Text = value;
            _isTextProgrammaticallySet = false;
        }
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        base.OnTextChanged(e);

        // .. on programmatic or on user


        // .. on programmatic
        if (_isTextProgrammaticallySet)
        {


            return;
        }

        // .. on user
        OnTextChangedByUser(e);
    }

    protected void OnTextChangedByUser(TextChangedEventArgs e)
    {
        // Do whatever you want.
    }
}

以下は推奨されませんが、すべてをカバーしようとした結果:
すべてのイベントをキャッチするための代替手段は次のとおりです。

  • DataObject.AddPastingHandler(MyTextBox、MyPasteCommand);
    カバー1と3
  • OnPreviewTextInput
    7をカバーしますが、スペースはカバーしません
  • OnKeyDown
    7スペースをカバー

2、4、5、6、8をカバーしようとすると、上記のより簡単で一貫性のあるソリューションを使用する必要があると考えました:)

3
EricG

TextChangedBehaviorクラスを Fredrik answer からクリーンアップして変更し、cutコマンドも正しく処理するようにしました(ctr+X)。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

public class TextChangedBehavior
{
    public static readonly DependencyProperty TextChangedCommandProperty =
        DependencyProperty.RegisterAttached("TextChangedCommand",
                                            typeof (ICommand),
                                            typeof (TextChangedBehavior),
                                            new UIPropertyMetadata(TextChangedCommandChanged));

    private static readonly DependencyProperty UserInputProperty =
        DependencyProperty.RegisterAttached("UserInput",
                                            typeof (bool),
                                            typeof (TextChangedBehavior));

    public static void SetTextChangedCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(TextChangedCommandProperty, value);
    }

    private static void ExecuteTextChangedCommand(TextBox sender, TextChangedEventArgs e)
    {
        var command = (ICommand)sender.GetValue(TextChangedCommandProperty);
        var arguments = new object[] { sender, e, GetUserInput(sender) };
        command.Execute(arguments);
    }

    private static bool GetUserInput(DependencyObject target)
    {
        return (bool)target.GetValue(UserInputProperty);
    }

    private static void SetUserInput(DependencyObject target, bool value)
    {
        target.SetValue(UserInputProperty, value);
    }

    private static void TextBoxOnPreviewExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        if (e.Command != ApplicationCommands.Cut)
        {
            return;
        }

        var textBox = sender as TextBox;
        if (textBox == null)
        {
            return;
        }

        SetUserInput(textBox, true);
    }

    private static void TextBoxOnPreviewKeyDown(object sender, KeyEventArgs e)
    {
        var textBox = (TextBox)sender;
        switch (e.Key)
        {
            case Key.Return:
                if (textBox.AcceptsReturn)
                {
                    SetUserInput(textBox, true);
                }
                break;

            case Key.Delete:
                if (textBox.SelectionLength > 0 || textBox.SelectionStart < textBox.Text.Length)
                {
                    SetUserInput(textBox, true);
                }
                break;

            case Key.Back:
                if (textBox.SelectionLength > 0 || textBox.SelectionStart > 0)
                {
                    SetUserInput(textBox, true);
                }
                break;
        }
    }

    private static void TextBoxOnPreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        SetUserInput((TextBox)sender, true);
    }

    private static void TextBoxOnTextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        ExecuteTextChangedCommand(textBox, e);
        SetUserInput(textBox, false);
    }

    private static void TextBoxOnTextPasted(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = (TextBox)sender;
        if (e.SourceDataObject.GetDataPresent(DataFormats.Text, true) == false)
        {
            return;
        }

        SetUserInput(textBox, true);
    }

    private static void TextChangedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        var textBox = target as TextBox;
        if (textBox == null)
        {
            return;
        }

        if (e.OldValue != null)
        {
            textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown;
            textBox.PreviewTextInput -= TextBoxOnPreviewTextInput;
            CommandManager.RemovePreviewExecutedHandler(textBox, TextBoxOnPreviewExecuted);
            DataObject.RemovePastingHandler(textBox, TextBoxOnTextPasted);
            textBox.TextChanged -= TextBoxOnTextChanged;
        }

        if (e.NewValue != null)
        {
            textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown;
            textBox.PreviewTextInput += TextBoxOnPreviewTextInput;
            CommandManager.AddPreviewExecutedHandler(textBox, TextBoxOnPreviewExecuted);
            DataObject.AddPastingHandler(textBox, TextBoxOnTextPasted);
            textBox.TextChanged += TextBoxOnTextChanged;
        }
    }
}
3
bitbonk

正しい方向を示してくれたTimに感謝しますが、私のニーズでは、IsFocusのチェックは魅力のように機能しました。とても簡単です。

 if (_queryField.IsKeyboardFocused && _queryField.IsKeyboardFocusWithin)
 {
     //do your things
 }
 else 
 { 
     //whatever 
 }
1
Bruno

私もこの問題を抱えていましたが、私の場合は、Meleakのかなり複雑なソリューションを使用する代わりに、 (Preview)TextInput イベントをリッスンするだけで十分でした。プログラムによる変更も聞く必要がある場合、これは完全な解決策ではないことを理解していますが、私の場合は問題なく機能しました。

0
Henrik Karlsson