I would like to differentiate between changing the text programmatically (for example in a button click handler event) and user input (typing, cutting and pasting text).
Is it possible?
The event handler is called whenever the contents of the TextBox control are changed, either by a user or programmatically. This event fires when the TextBox control is created and initially populated with text.
To see all changes to the text in a TextBox, you can handle the TextChanged event. This event fires after the text has changed. In the TextChanged event, the easiest way to inspect the text being entered is to just look at the value of the Text property.
The TextChanged event occurs whenever the text is changed, by the user or programmatically. In your case your A_TextChanged eventhandler is changing B's text, so that B_TextChanged is called … Adding some Debug.
User input in a TextBox
can be identified with
Combining these three events with a bool flag to indicate if any of the above occured before the TextChanged event and you'll know the reason for the update.
Typing and Pasting are easy, but Backspace doesn't always trigger TextChanged
(if no text is selected and the cursor is at position 0 for example). So some logic is needed in PreviewTextInput.
Here is an Attached Behavior that implements the logic above and executes a command with a bool flag when TextChanged
is raised.
<TextBox ex:TextChangedBehavior.TextChangedCommand="{Binding TextChangedCommand}" />
And in code you can find out the source for the update like
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.. } }
Here is a small sample project demonstrating the effect: 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 }
Depending on your exact demands you can use TextBox.IsFocused
in the TextChanged
event to determine manual input. This will obviously not cover all ways of programmatical changes, but works for a lot of examples just fine and is a pretty clean and save way of doing so.
Basically this works if:
...the programmatical changes are all based on a manual change (e.g. a Button press).
It will not work if:
...the programmatical changes are completely based on code (e.g. a Timer).
Code example:
textBox.TextChanged += (sender, args) => if (textBox.IsFocused) { //do something for manual input } else { //do something for programmatical input } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With