Подтвердить что ты не робот

TextBox TextChanged событие при программном и пользовательском изменении содержимого текста

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

4b9b3361

Ответ 1

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

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

Ответ 2

Пользовательский ввод в 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
}

Ответ 3

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

bool programmaticChange = false;

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

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

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

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

Ответ 4

Я очистил и модифицировал класс 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;
        }
    }
}

Ответ 5

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

  • перемещение текста путем перетаскивания мышью с TB2 на TB1
  • cut (ctrl-x, программный разрез, вырез меню мыши)
  • вставить (ctrl-v, программная паста, мышь-меню)
  • undo (ctrl-z, программная отмена)
  • redo (ctrl-Y, программный повтор)
  • delete и backspace
  • текст клавиатуры (alfanumeric + символы + пробел)

Рассмотрим, что вы хотите исключить:

  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 Я решил, что я должен пойти с более простым и последовательным решением выше:)

Ответ 6

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

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

Пример кода:

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

Ответ 7

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

Ответ 8

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

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