Unable to use Undo in TextChanged

To answer simbay's approach, which I think is being dismissed.

You can't call Undo in TextChanged because the undo operation is still being prepared by the TextBox. It seems to work sometimes and not other times, so this suggests there is a race condition between when the event is signaled and the completion of the undo preparation.

However calling Undo invoked on the Dispatcher will allow the text box to complete its undo preparation. You can validate the results of the text change and then decide if you want to keep or undo the change. This may not be the best approach, but I tried it and blasted a bunch of text changes and pastes into the text box and could not reproduce the exception.

The "accepted answer" is great ONLY if you want to prevent an invalid character from being entered or pasted, but in general I often do a lot more involved validation of TextBox input and want to verify the final text value. Its not easy to discern the final text from a Preview event because as far as the control is concerned nothing has happened yet.

To answer Terribad's question, simbay's answer is better and more succinct in more situations.

tb.TextChanged += ( sender, args ) =>
{
    if(! MeetsMyExpectations( tb.Text ) )
        Dispatcher.BeginInvoke(new Action(() => tb.Undo()));
};

I've read a lot of wild adventures in text box validation and this is about as easy as I have found.


Instead of using Undo and TextChanged, you should use the PreviewTextInput and DataObject.Pasting events. In the PreviewTextInput event handler, set e.Handled to true if the typed text is an invalid character. In the Pasting event handler, call e.CancelCommand() if the pasted text is invalid.

Here is an example for a text box that accepts only the digits 0 and 1:

XAML:

<Window x:Class="BinaryTextBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="133" Width="329">
    <StackPanel>
        <TextBox x:Name="txtBinary" Width="100" Height="24"
                 PreviewTextInput="txtBinary_PreviewTextInput"
                 DataObject.Pasting="txtBinary_Pasting"/>
    </StackPanel>
</Window>

Code behind:

using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Input;

namespace BinaryTextBox
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void txtBinary_PreviewTextInput(object sender,
                 TextCompositionEventArgs e)
        {
            e.Handled = e.Text != "0" && e.Text != "1";
        }

        private void txtBinary_Pasting(object sender, DataObjectPastingEventArgs e)
        {
            if (!Regex.IsMatch(e.DataObject.GetData(typeof(string)).ToString(), "^[01]+$"))
            {
                e.CancelCommand();
            }
        }
    }
}

Call the undo asynchronously from the From the TextChanged event handler:

Dispatcher.BeginInvoke(new Action(() => tb.Undo())) 

Tags:

C#

Wpf

Undo