TextBox TextChanged event on programmatic versus user change of text contents

后端 未结 8 1712
日久生厌
日久生厌 2020-12-15 18:16

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). <

相关标签:
8条回答
  • 2020-12-15 18:56

    User input in a TextBox can be identified with

    • Typing : PreviewTextInput event
    • Backspace, Delete, Enter : PreviewKeyDown event
    • Pasting : DataObject.PastingEvent

    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
    }
    
    0 讨论(0)
  • 2020-12-15 18:56

    Partial credits for dodgy_coder (agreed not conform the beautiful design you hope for, but imo the best compromis). Consider everything you want to cover:

    1. move text by dragging with mouse from TB2 to TB1
    2. cut (ctrl-x, programmatic cut, mouse-menu-cut)
    3. paste (ctrl-v, programmatic paste, mouse-menu-paste)
    4. undo (ctrl-z, programmatic undo)
    5. redo (ctrl-Y, programmatic redo)
    6. delete & backspace
    7. keyboard text (alfanumeric + symbols + space)

    Consider what you want to exclude:

    1. programmatic setting of Text

    Code

    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.
        }
    }
    

    The following is discouraged, but the results of an attempt to cover all:
    The alternatives for catching all the events were:

    • DataObject.AddPastingHandler(MyTextBox, MyPasteCommand);
      Covers 1 & 3
    • OnPreviewTextInput
      Covers 7 but not space
    • OnKeyDown
      Covers 7-space

    Trying to cover 2, 4, 5, 6 & 8 I figured I should go with the easier and consistent solution above :)

    0 讨论(0)
提交回复
热议问题