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.