TextBox TextChanged событие программного или пользовательского изменения текстового содержимого - PullRequest
16 голосов
/ 01 сентября 2011

Я бы хотел провести различие между программным изменением текста (например, в событии обработчика нажатия кнопки) и пользовательский ввод (ввод, вырезание и вставка текста).
Возможно ли это?

Ответы [ 8 ]

25 голосов
/ 06 сентября 2011

Пользовательский ввод в TextBox может быть идентифицирован как

  • Ввод: Событие PreviewTextInput
  • Backspace, Delete, Enter: Событие PreviewKeyDown
  • Вставка: DataObject.PastingEvent

Объединение этих трех событий с флагом bool для указания того, произошло ли что-либо из вышеперечисленного до события TextChanged , и вы будете знать причину обновления.

Печатать и вставлять легко, но Backspace не всегда запускает TextChanged (если текст не выделен и курсор находится в позиции 0, например). Таким образом, некоторая логика необходима в PreviewTextInput .

Вот присоединенное поведение, которое реализует приведенную выше логику и выполняет команду с флагом bool, когда TextChanged поднято.

<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
}
5 голосов
/ 05 сентября 2011

Если вы просто хотите использовать встроенный текстовый ящик WPF, то я не верю, что это возможно.

Подобное обсуждение на форумах Silverlight здесь: http://forums.silverlight.net/p/119128/268453.aspx Это не совсем тот же вопрос, но я думаю, что идея, аналогичная той, что была в оригинальном посте, может помочь вам. Имейте метод SetText в подклассе TextBox, который устанавливает флаг перед изменением текста и затем устанавливает его обратно после. Затем вы можете проверить наличие флага внутри события TextChanged. Это, конечно, потребовало бы, чтобы все ваши программные текстовые изменения использовали этот метод, но если вы обладаете достаточным контролем над проектом, чтобы поручить, я думаю, он будет работать.

4 голосов
/ 06 сентября 2011

Аналогично ответу JHunz, просто добавьте переменную логического члена в ваш элемент управления:

bool programmaticChange = false;

Когда вы делаете программные изменения, сделайте следующее:

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

В ваших обработчиках событий вам просто нужно проверить значение programmaticChange, чтобы определить, является ли это программным изменением или нет.

Довольно очевидно и не очень элегантно, но также и выполнимо и просто.

3 голосов
/ 29 июня 2015

В зависимости от ваших точных требований вы можете использовать TextBox.IsFocused в событии TextChanged для определения ручного ввода. Это, очевидно, не охватывает все способы программных изменений, но отлично работает для многих примеров и является довольно чистым и безопасным способом сделать это.

В основном это работает, если:
... все программные изменения основаны на ручном изменении (например, нажатие кнопки).
Это не будет работать, если:
... программные изменения полностью основаны на коде (например, таймер).

Пример кода:

textBox.TextChanged += (sender, args) =>
    if (textBox.IsFocused)
    {
        //do something for manual input
    }
    else
    {
        //do something for programmatical input
    }
}
3 голосов
/ 07 октября 2014

Частичные кредиты для dodgy_coder (согласен не соответствовать красивому дизайну, на который вы надеетесь, но я считаю лучшим компромиссом). Рассмотрим все, что вы хотите охватить:

  1. для перемещения текста путем перетаскивания мышью из TB2 в TB1
  2. вырезать (ctrl-x, программный вырезать, вырезать из меню мыши)
  3. вставка (ctrl-v, программная вставка, вставка из меню мыши)
  4. отменить (ctrl-z, программная отмена)
  5. повтор (ctrl-Y, программный повтор)
  6. Удалить & Backspace
  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, я решил, что мне следует воспользоваться более простым и последовательным решением, приведенным выше:)

2 голосов
/ 19 июня 2013

Я очистил и изменил класс TextChangedBehavior из Ответ Фредрика , чтобы он также правильно обрабатывал команду вырезания ( 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;
        }
    }
}
1 голос
/ 30 марта 2017

Спасибо Тиму за то, что он указал в правильном направлении, но для моих нужд проверка на IsFocus работала как чудо. Это так просто ....

 if (_queryField.IsKeyboardFocused && _queryField.IsKeyboardFocusWithin)
 {
     //do your things
 }
 else 
 { 
     //whatever 
 }
0 голосов
/ 06 декабря 2012

У меня тоже была эта проблема, но для моего случая достаточно было прослушать событие (Preview) TextInput вместо использования довольно сложного решения Мелеака.Я понимаю, что это не полное решение, если вам нужно прислушиваться к программным изменениям, но в моем случае это работало нормально.

...