This project has moved. For the latest updates, please go here.

Using the UndoManager

There are different ways to use this Library. Depending on how complex your application is you can pick any way, or combine them.

Before we can start, its necessary to control the UndoManager from the view.

Undo and Redo form the view

UndoManager expose two ICommand-Property’s. Bind the Command value of a Button to it and you are ready.

  1. <Window x:Class="WpfApplication1.MainWindow"
  2.        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.        xmlns:local="clr-namespace:WpfApplication1"
  5.        Title="MainWindow" Height="350" Width="525">
  6.     <Window.Resources>
  7.         <local:ViewModel x:Key="ViewModelDataSource" />
  8.     </Window.Resources>
  9.     <StackPanel DataContext="{Binding Source={StaticResource ViewModelDataSource}}">
  10.         <Button Content="Undo" Height="23" Width="75" Command="{Binding Path=UndoManager.Undo}" />
  11.         <Button Content="Redo" Height="23" Width="75" Command="{Binding Path=UndoManager.Redo}" />
  12.     </StackPanel>
  13. </Window>

Monitor property's via reflection

Create your viewmodel and add a property for the UndoManager. In the constructor you create a new UndoManager using your viewmodel as parameter and assign this to the property.

  1. public class ViewModel : INotifyPropertyChanged
  2.     {
  3.         //...
  4.  
  5.         public UndoManager UndoManager { get; private set; }
  6.         
  7.         //...
  8.         
  9.         public ViewModel()
  10.         {
  11.             //...
  12.             UndoManager = new UndoManager(this);
  13.             //...
  14.         }
  15.  
  16.         //...
  17.         
  18.     }

That’s it the UndoManager registers any change to the property's and INotifyCollectionChanged Collections, allowing you to revert these changes. You can pass as many INotifyPropertyChanged instances in the constructor as you wish.

Note:

  • Every change done after the constructor call of UndoManager will be registert in the undo manager.
  • Only property’s with a get and set method will be monitored.

Ignoring Property’s

Maybe you’ve got property’s you don’t want to monitor. In this case you can add an Attribute to this property so it wont be monitored.

[IgnorUndoManager]

Automatic Unite Changes

Ok sometimes you have Property’s the user change successive and all these changes should be regarded as one step.

Below is a sample. We have an int value and want to track the changes. In the case that our user increase the value every time by one, we suggest that this different changes should be one.

imageimage

This sample demonstrate, if the value change from 7 to 42 and then to 9, all these changes should get recorded. If we change from 7 over 8 to 9 this is one step, the 8 should not be recorded.

In order to achieve this we add an attribute to this property.

[IntFuse]

This attribute extends the abstract Attribute FusePropertyChangeAttribute implementing the method FuseFunction.

These method has three arguments and returns true if those changes can be united.

  1. class IntFuseAttribute : FusePropertyChangeAttribute
  2. {
  3.     protected override bool FuseFunction(object originalValue, object firstChange, object seccondChange)
  4.     {
  5.         int s1 = (int)originalValue;
  6.         int s2 = (int)firstChange;
  7.         int s3 = (int)seccondChange;
  8.         if (s1 == s2 + 1 && s2 == s3 + 1)
  9.             return true;
  10.         if (s3 == s2 + 1 && s2 == s1 + 1)
  11.             return true;
  12.         return false;
  13.     }
  14. }

In the sample above originalValue would be 7, firstChange 8 and seccondChange 9. The return value would be true and the 8 will not be recorded by the undomanager. Hitting undo resets the value to 7. This works only when you change the same property on the same object in a row.

Track Changes via ICommand implementation

If your viewmodel expose ICommand-interfaces you should consider the use of the UndoCommand class.

This class is an implementation of ICommand. The constructor takes two Action<object> delegets and one Predicate<object> delegate. One of the actions is the command which gets executed by the classes Execute method(implementation of ICommand) the other action is a user defined inversion of the first. The predicate is used for the CanExecute method (implementation of ICommand)

  1. public View1Model()
  2. {
  3.     UndoManager = new UndoManager(this);
  4.     FirstList = new ObservableCollection<string>(new String[] {"1","2"});
  5.     SeccondList = new ObservableCollection<string>(new String[] { "a", "b" });
  6.  
  7.  
  8.     Action<object> fromOneToTwo = obj => { FirstList.Remove(obj as String); SeccondList.Add(obj as string); };
  9.     Action<object> fromTwoToOne = obj => { SeccondList.Remove(obj as String); FirstList.Add(obj as string); };
  10.  
  11.     FromFirstToSeccond = new UndoCommand(UndoManager, fromOneToTwo, fromTwoToOne, obj => FirstList.Contains(obj as string));    
  12.     FromSeccondToFirst = new UndoCommand(UndoManager, fromTwoToOne, fromOneToTwo, obj => SeccondList.Contains(obj as string));
  13. }

In the sample above we have FirstList and SeccondList both an ObservableCollection of type string. This viewmodel also expose two commands to shift a string from one list to the other. First we define an action that moves a string from list one to list two. Second we define an action that revers the first, in this case we move the string from list two to list one. In the end we create an new UndoCommand with the UndoManager which registers the changes, the action that will be executed, the action that reverse the first and a predicate that describes if the command can be executed.

Irreversible Changes

Some changes cant be undone. If you change something that can’t be undone then the history of all previous changes will be deleted. You can specify such a change when you pass null for the reverse action.

Some useful function's

In this part some general functions of this library will be documented.

ClearUndoHistory

This method of UndoManager allows you to delete all changes till now meaning you can not undo any old changes.

  1. ClearUndoHistory();

SuspendRegisteringChanges

This functions of UndoManager let you temporary stop the registering of changes. This can be useful when you initialize the viewmodel. But be carful when you suspend the undo manager for some time without deleting the history of changes this can cause unexpected behavior.

  1. using (manager.SuspendRegisteringChanges())
  2. {
  3.     prop.SetValue(obj, value, manager.emptyArray);
  4. }

This method returns a IDisposable and the undo manager ignores changes as long at least one IDisposable exist (is not disposed).

Last edited Mar 4, 2011 at 8:01 AM by LokiMidgard, version 18

Comments

No comments yet.