Итак, я думаю, что статья ViewModel Undo / Redo хорошая, но она касается не только шаблона ViewModel, но и того, как написать пользовательскую функциональность Undo / Redo.Кроме того, в ответ на confusedGeek, я думаю, могут быть примеры, когда отмена изменений в вашей модели, а не только в ваших отдельных элементах управления, уместна (скажем, у вас есть текстовое поле и ползунок, оба привязаны к свойству примера, вы хотите отменить изменениенезависимо от того, какой элемент управления был создан, поэтому мы говорим об отмене уровня приложения вместо уровня управления).
Итак, учитывая, что это простой, если не несколько хитрый, пример выполнения именно того, что вы просите, используяCommandBinding и упрощенный стек отмены:
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";
}
}
А вот 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>
В этом примере поведение немного отличается от обычного поведения отмены TextBox, поскольку 1) яигнорируя выделение, и 2) я не группирую несколько нажатий клавиш в один шаг отмены, оба из которых вы бы хотели рассмотреть в реальном приложении, но должны быть относительно простыми для реализации самостоятельно.