7

I have a TextBox to which i bound a string, if i now edit the text manually i will be able to undo those changes via TextBox.Undo(), if however i change the string and the TextBox's text is updated, i cannot undo those changes and the TextBox.CanUndo property will always be false.
I suppose this might have to do with the complete replacement of the text rather than a modification of it.

Any ideas on how i can get this to work?

H.B.
  • 166,899
  • 29
  • 327
  • 400

5 Answers5

12

I was facing the same issue (needed to accept input upon Enter and revert to original value upon Escape) and was able to handle it this way:

  1. Set UpdateSourceTrigger of your TextBox.Text binding to Explicit.
  2. Handle KeyDown event of your TextBox and put the following code in there:

    if (e.Key == Key.Enter || e.Key == Key.Escape)
    {
      BindingExpression be = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
    
      if (e.Key == Key.Enter)
      {
        if (be != null) be.UpdateSource();
      }
      else if (e.Key == Key.Escape)
      {
        if (be != null) be.UpdateTarget(); //cancels newly supplied value and reverts to the original value
      }
    }
    

I found this solution to be very elegant because it can be used in DataTemplates too. For example in my case I used it to allow in-place editing of ListBox items.

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • This does not cover the case where immediate value propagation is desired. You probably would want to save on lost focus as well. Still, it might be a decent work around in some cases. – H.B. Nov 14 '16 at 15:57
7

OK, started to leave a comment and realized it was an answer :)

TextBox.Undo() is intended to undo a user's interaction with the text box not a value change in the property it's bound to. A change in the property the text box is bound to will just update the value of the TextBox, this is a different change than a user edit via focus/keyboard. If you need to Undo changes to your bound properties you probably need to investigate adding an Undo/Redo stack to your application.

Ken Henderson
  • 2,828
  • 1
  • 18
  • 16
  • I suspected that there would not be a simple solution, then again, it was not really a good idea to try and handle undoing by reverting a change that occurred in a control, rather than the model. Thanks. – H.B. Dec 18 '10 at 03:52
5

Assign directly to the TextBox:

textBox.SelectAll();
textBox.SelectedText = newText;
Pixar21
  • 69
  • 1
  • 2
  • I think you misunderstood the question, this is about the undo functionality, i know how to overwrite existing text. There is no `newText`, i was trying to restore an old value that should have been stored in the TextBox's history. – H.B. Jun 01 '11 at 00:49
  • @H.B. This works great! Doesn't overwrite the clipboard, and allows you to undo text assignments that weren't made by the user! Thanks Pixar! H.B. I would consider rewarding Pixar with the answer, since this does exactly what you asked for in the question. – Darkhydro Apr 11 '14 at 22:46
  • @Darkhydro: No, unfortunately the question is about text changes caused by bindings, i.e. the property that the `TextBox.Text` is bound to is changed, no interaction with the `TextBox` itself. – H.B. Apr 12 '14 at 01:47
  • This can be easily expanded to do what you want with an attached property, so you can do something like `UserText.Value="{Binding Xyz}"` which updates the text using the method in this answer when it changes instead of using the Text property directly. I know this answer is old, but might be of interest to someone coming by it now. – Mike Marynowski Oct 28 '16 at 12:15
1

The TextBox will apply the changes to the internal undo stack if they are applied in such a way that they appear to have come from the user, like so:

        Clipboard.SetText("NewTextHere");
        TextBox.Paste();

It's a terrible workaround, as it kills whatever the user has on the clipboard (the restoring of which is pessimistically discussed here: How do I backup and restore the system clipboard in C#?) but I thought it might be worth having posted nonetheless.

Community
  • 1
  • 1
MatrixManAtYrService
  • 8,023
  • 1
  • 50
  • 61
0

So, I think the ViewModel Undo/Redo article is a good one, but it's as much as about the ViewModel pattern as it is about how to write custom Undo/Redo functionality. Also, in response to confusedGeek, I think there could be examples where undoing changes in your model, not just in your individual controls is appropriate (say you had a textbox and a slider both bound to the sample property, you want to undo a change regardless of which control made it, so we're talking about app level undo instead of control level).

So given that, here is a simple, if not somewhat kludgey example of doing precisely what you ask using a CommandBinding and a simplistic undo stack:

public partial class MainWindow : Window
{
    public static readonly DependencyProperty MyStringProperty =
        DependencyProperty.Register("MyString", typeof(String), typeof(MainWindow), new UIPropertyMetadata(""));

    // The undo stack
    Stack<String> previousStrings = new Stack<String>();
    String cur = ""; // The current textbox value
    Boolean ignore = false; // flag to ignore our own "undo" changes

    public String MyString
    {
        get { return (String)GetValue(MyStringProperty); }
        set { SetValue(MyStringProperty, value); }
    }

    public MainWindow()
    {
        InitializeComponent();
        this.LayoutRoot.DataContext = this;

        // Using the TextChanged event to add things to our undo stack
        // This is a kludge, we should probably observe changes to the model, not the UI
        this.Txt.TextChanged += new TextChangedEventHandler(Txt_TextChanged);

        // Magic for listening to Ctrl+Z
        CommandBinding cb = new CommandBinding();
        cb.Command = ApplicationCommands.Undo;
        cb.CanExecute += delegate(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        };

        cb.Executed += delegate(object sender, ExecutedRoutedEventArgs e)
        {
            if (previousStrings.Count > 0)
            {
                ignore = true;
                this.Txt.Text = previousStrings.Pop();
                ignore = false;
            }

            e.Handled = true;
        };

        this.CommandBindings.Add(cb);
    }

    void Txt_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (!ignore)
        {
            previousStrings.Push(cur);
        }

        cur = this.Txt.Text;
    }

    private void SetStr_Click(object sender, RoutedEventArgs e)
    {
        this.MyString = "A Value";
    }
}

And here is the XAML:

<Window x:Class="TestUndoBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel  Name="LayoutRoot">
    <TextBox Name="Txt" Text="{Binding Path=MyString, Mode=TwoWay}" />
    <Button Name="SetStr" Click="SetStr_Click">Set to "A Value"</Button>
</StackPanel>
</Window>

In this example the behavior is slightly different than typical TextBox undo behavior because 1) I'm ignoring selection, and 2) I'm not grouping multiple keystrokes into a single undo step, both of which are things you would want to consider in a real app, but should be relatively straightforward to implement yourself.

Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41