Download Source Code and Example Application (39KB)
Code is provided for free and you may change whatever you like.
However you use it at your own risk. Poor students like me can't afford law suits!
One of the most useful features seen in most Windows applications today is also one of the most taken for granted: Edit | Undo. When developing a commercial application the end-users will expect to be able to undo their mistakes. In addition, most applications allow multiple undo actions as well as redo actions (un-undo?).
In my development I first came across the need to be able to undo changes to properties of my business objects. I then abstracted so that the developed framework supports undoing any action which you care to provide the code for. For example, adding/removing an item to/from a list.
Supporting undoing is all about maintaining state. Before something changes it has to be saved somehow so that later we can restore its state. So I came to the idea of a stack of changes. Making a change involves pushing the old state onto a stack. To undo the last change we just pop the stack and set the object to that previous state.
Consider this simple example of a person object’s name property being modified then restored.
1) Initially the person’s name is “Andrew”

2) The name is then changed to “Billy”, but the old value “Andrew” is pushed onto the undo stack

3) To undo this change the stack is popped, yielding the Name = “Andrew” action. This sets the name property of the person back to “Andrew”.
4) In addition, to provide a redo function, the current state (“Billy”) is pushed onto the redo stack; Allowing us to re-apply the original change if the undo was a mistake.

Redoing an action happens by popping the redo stack and setting the object’s state. This must also push the current state back onto the undo stack.
Of course this stack based approach supports many consecutive changes to an object’s state. The only real limit is that of computer memory.
The previous example dealt with the simple notion of changing a property. The situation becomes difficult when a more involved change occurs in an object, for example, removing a person from a list. This change can only undone by first saving the person object before deletion, then adding it back to the list when the undo is requested.
A couple of technical issues exist when operating undo/redo logic:
The first key notion for developing the undo provider is that of an action. An action is essentially any change to state that can be undone and redone. It has to contain all the information it needs to enact the undo/redo operations. The undo and redo stacks will consist of these action objects.
In more detail the Action object saves a reference to the object it has state for, the state itself and two delegates that point to methods that actually handle changing an object’s state.

The two public methods Undo and Redo just invoke the respective delegates. The delegates themselves are private and must be set upon construction of the Action object.
The delegates have the following signature:
public delegate void ActionCallback(Action action);
Next, we look at the heart of the framework, the UndoProvider class. This class contains the undo and redo stacks. Additionally, the Enabled property provides a simple way to stop the class from tracking actions without lots of if branches on the business logic side.

The most import sections are the three public methods StoreAction, Undo and Redo. First we’ll look at StoreAction.
public void StoreAction(Action action)
{
// we only store when enabled and not currently inside and undo or redo operation
if(_enabled && !_undoing && !_redoing)
{
// Store the action
_undoStack.Push(action);
// The redo stack has to be cleared now since we have pushed new data on
_redoStack.Clear();
// Let the calling code know about the changes
if(CanUndoChanged!=null) CanUndoChanged(this, EventArgs.Empty);
if(CanRedoChanged!=null) CanRedoChanged(this, EventArgs.Empty);
}
}
I mentioned above about the problem of undoing an action causing the action to get pushed back on to the undo stack. For example, undoing a property change must involve calling the property’s set method. But this would ordinarily result in StoreAction being called to record the property change! (A similar collision occurs with redo.) This is why we introduce the _undoing and _redoing Boolean variables. These are set to True whilst undoing and redoing respectively.
public void Undo()
{
if(CanUndo)
{
// Set the undoing flag to stop StoreAction being called by other code
// whilst setting values, etc
_undoing = true;
// Get the recent action
Action action = (Action)_undoStack.Pop();
action.Undo();
// Put the action onto the redo stack
_redoStack.Push(action);
_undoing = false;
// Tell the calling code that stuff has changed
if(CanUndoChanged!=null) CanUndoChanged(this, EventArgs.Empty);
if(CanRedoChanged!=null) CanRedoChanged(this, EventArgs.Empty);
}
}
This code simply pops the last action from the undo stack, calls the Undo method and then pushes the action onto the redo stack.
The Redo method is virtually the same, except having the undo and redo stacks interchanged.
All three methods raise the CanUndoChanged/CanRedoChanged events. The CanUndo and CanRedo properties essentially allow the calling code to know if there are actions to be undone or redone, and therefore update UI accordingly. The changed events are there to support the .NET data binding model. The UI code can bind the CanUndo property to the Enabled property of the undo button and then automatically have the UI update to reflect the state of the UndoProvider.
The Action class does the job of implementing an incredibly generic idea. However, being generic also makes it a pain implement over and over again for something as simple as a property change. To improve this I derived a new class from Action called PropertyChangeAction. This class bypasses the callback delegates and instead overrides the Undo and Redo methods of the Action class.
The state recording the property change is stored in an internal structure.
internal struct PropertyChange
{
public string PropertyName;
public object OldValue;
public object NewValue;
public PropertyChange(string propertyName, object oldValue, object newValue)
{
PropertyName = propertyName;
OldValue = oldValue;
NewValue = newValue;
}
}
The constructor of the PropertyChangeAction class is different to that of its parent.
public PropertyChangeAction(object obj, string propertyName, object oldValue, object newValue)
: base(obj, new PropertyChange(propertyName,oldValue,newValue), null, null)
{
}
It simply builds the state object as a new PropertyChange value and sets the delegates to null since these are unused now. All the logic to undo and redo a property change can be encapsulated within the class – instead of relying on external methods. The Undo and Redo methods utilise reflection to change the property’s value.
public override void Undo()
{
// Get the PropertyChange data from the State
PropertyChange change = (PropertyChange)State;
// Use reflection to change the property of the object
Object.GetType().GetProperty(change.PropertyName).SetValue(Object,change.OldValue,null);
}
The test application download provides a simple example of the UndoProvider in action. I will go over some of the key points here. The TestComponent is a business object that utilises the UndoProvider. It demonstrates simple property change undoing and more advanced actions such as adding to a list.
Public Property Data() As String
Get
Return _data
End Get
Set(ByVal Value As String)
_undoProvider.StoreAction(New PropertyChangeAction(Me, "Data", _data, Value))
_data = Value
RaiseEvent DataChanged(Me, EventArgs.Empty)
End Set
End Property
See how the set method calls StoreAction before changing the private member. This single line of code is all that is needed to make the property change undoable.
A more involved example is found in the PersonList class. We want to be able to undo adding and removing Person objects.
Public Sub Add(ByVal p As Person)
Dim a As New Action(Me, p, AddressOf UndoAddPerson, AddressOf UndoRemovePerson)
_component.UndoProvider.StoreAction(a)
List.Add(p)
End Sub
The Add method saves the person being added in the Action object along with the pointers to the methods to call to undo and redo. Note that here I have taken the shortcut of using undo remove person being the same as redo add person.
Protected Sub UndoAddPerson(ByVal action As Action)
Dim p As Person = CType(action.State, Person)
Remove(p)
End Sub
Protected Sub UndoRemovePerson(ByVal action As Action)
Dim p As Person = CType(action.State, Person)
Add(p)
End Sub
Now in the user interface (Form1) we use the business object by data binding controls to its properties.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
_component = New TestComponent
txtData.DataBindings.Add("Text", _component, "Data")
cboOption.DataBindings.Add("SelectedIndex", _component, "Option")
lstPeople.DataSource = _component.People
btnUndo.DataBindings.Add("Enabled", _component.UndoProvider, "CanUndo")
btnRedo.DataBindings.Add("Enabled", _component.UndoProvider, "CanRedo")
End Sub
Notice that the UndoProvider is exposed by the business object. In a bigger system you would probably want to keep the UndoProvider outside the object but set a property on the many different business objects in use so they have a reference to it still.
The undo and redo buttons on the form have their Enabled properties data bound to the CanUndo/CanRedo properties so the user interface automatically updates to reflect the UndoProviders status.
Obviously now the only code we need is:
Private Sub btnUndo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUndo.Click
_component.UndoProvider.Undo()
End Sub
Private Sub btnRedo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRedo.Click
_component.UndoProvider.Redo()
End Sub
This article and attached code hopefully provides a good starting point for including undo/redo functionality in your applications. The framework is basic but very extensible, as demonstrated with the property change action. One note about the use of reflection in the derived action class is that if the property name is incorrect or the value type invalid the method will throw an exception. Use with care! You may wish to add more robust error catching in the undo/redo methods.
Feel free to modify and extend the code to meet your needs (please change the namespace however to avoid conflicts!) I always appreciate feedback so any questions, comments, etc can be sent to me at andrew@equin.co.uk.