BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Algorithms and Data Structures
.NET 2.0+

Generic Multi-Level Undo and Redo

Most modern software applications include undo and redo features. Undo allows one or more activities to be reverted. Redo allows previous undo actions to be reversed. This article explains how to create a generic, multi-level undo and redo class.

Saving the State

The historical states are not stored automatically when the value changes. This would involve adding events to the wrapped type to allow the Undoable object to know when a change was made, preventing the class from working with any serializable type. It would also mean that changes that did not warrant an undo state may be recorded, using memory unnecessarily. Instead, whenever a key change happens that is worthy of storing, you should call the SaveState method.

When a user makes a change to their data, it is usual that it can be undone but that the redo function becomes unavailable. The Undoable<T> class follows this pattern. When a new state is stored, it is added to the undo stack and the redo stack is emptied. The code for the method is shown below:

public void SaveState()
{
    _redoStack.Clear();
    _undoStack.Push(GenerateUndoState());
}

private UndoState<T> GenerateUndoState()
{
    return new UndoState<T>(Value);
}

Undo Method

When the user elects to undo an operation, the previous state must be retrieved by popping it from the undo stack and storing it in the Value property. However, as the user may change their mind and perform a subsequent redo operation, the current value must first be added to the redo stack. The Undo method performs these two operations. Additionally, it throws an exception is the undo stack is empty.

public void Undo()
{
    if (_undoStack.Count == 0)
        throw new InvalidOperationException("Undo history exhausted");

    _redoStack.Push(GenerateUndoState());
    _value = _undoStack.Pop().State;
}

Redo Method

The Redo method is similar to Undo, except that the two stacks are used in reverse. An exception is thrown if the redo stack is empty. If a redo state is present, the current state is pushed onto the undo stack and the value from the top of the redo stack is set as the current value.

public void Redo()
{
    if (_redoStack.Count == 0)
        throw new InvalidOperationException("Redo history exhausted");

    _undoStack.Push(GenerateUndoState());
    _value = _redoStack.Pop().State;
}

Using Undoable<T>

To demonstrate the use of the Undoable<T> class, we can create an instance, save several states and try out the Undo and Redo methods. In the code below, a string is wrapped. The state is saved twice, with the current value changing between saves. We then carry out two Undo operations to reverse the state to the initial value. This is followed by two redo calls, which return the state to its final value.

static void Main(string[] args)
{
    IUndoable<string> stuff = new Undoable<string>("State One");
    stuff.SaveState();
    stuff.Value = "State Two";
    stuff.SaveState();
    stuff.Value = "State Three";

    stuff.Undo();   // Value = "State Two"
    stuff.Undo();   // Value = "State One"
    stuff.Redo();   // Value = "State Two"
    stuff.Redo();   // Value = "State Three"
}
6 July 2011