Я хотел бы различать программное изменение текста
(например, в событии обработчика нажатия кнопки) и ввода пользователя (ввод,
вырезание и вставка текста).
Возможно ли это?
TextBox TextChanged событие при программном и пользовательском изменении содержимого текста
Ответ 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 + символы + пробел)
Рассмотрим, что вы хотите исключить:
- программная настройка текста
код
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
}