Skip to main content

undo-redo-undo-redo...

Code available on GitHub

I'm currently building a UI with a semi-complex input-form that requires the ability to 'undo, redo, undo, redo' user actions ad-infinitum. Not only does it require the ability to undo the text typed in text-boxes etc it needs the ability to undo (or redo) button clicks that add\remove complex properties (reference types).

For example imagine I have the following model, where there is a hierarchical structure to the data  - parent-child relationships, the Widget has one settable value type property - 'Name' & a collection of child Widgets accessed via the 'Children' property, this property can not set by the caller- the caller has to use the 'AddChild' & 'RemoveChild' methods to affect the enumerable 'Children' property. I want the ability to 'undo, redo, undo, redo' on these two properties.
public class Widget : Model
{
    private int? id;
    private Widget parent;
    private string name;
    private readonly ObservableCollection<Widget> children;

    public Widget()
    {
        children = new ObservableCollection<Widget>();
    }

    public int? Id
    {
        get { return id; } 
        private set
        {
            SetPropertyAndNotify(ref id, value, () => Id);
        }
    }

    public string Name
    {
        get { return name; }
        set
        {
            SetPropertyAndNotify(ref name, value, () => Name);
        }
    }

    public Widget Parent
    {
        get { return parent; }
        private set
        {
            SetPropertyAndNotify(ref parent, value, () => Parent);
        }
    }

    public IEnumerable<Widget> Children
    {
        get { return children; }
    }

    public Widget AddChild(Widget child)
    {
        if (children.Contains(child))
        {
            return this;
        }

        child.Parent = this;
        children.Add(child);

        return this;
    }

    public Widget AddChild(IEnumerable<Widget> childs)
    {
        foreach (var child in childs)
        {
            AddChild(child);
        }

        return this;
    }

    public Widget RemoveChild(Widget child)
    {
        if (!children.Contains(child))
        {
            return this;
        }

        child.Parent = null;
        children.Remove(child);

        return this;
    }

    public Widget RemoveAllChildren()
    {
        foreach (var child in children.ToList())
        {
            child.Parent = null;
            children.Remove(child);
        }

        return this;
    }
}
I know already the kind of functionality required, an implementation of the Command Pattern, specifically the Memento Pattern. A quick Google found some interesting implementations much of which I didn't like for a couple of reason, one being no separation from UI - here or to complex (to many interfaces) - here. A couple more examples were just to old, not using modern languages features like Lambda expressions. In fact the use of Lambdas should make this a trivial exercise. The solution I want should be capable of being used within an MVC implementation without being tied to it, in fact it should know nothing about either the Model or the View. I envisage it being used from inside the Controller. This fits well with the UI I'm currently building, an MVVM implementation.

The solution I came up with has 2 classes, the first is the Memento we wish to be able to 'undo-redo'. As you can see the use of Action delegate syntax to remove any explicit knowledge of how the 'undo-redo' steps will be performed. There are 2 constructors one for when both undo & redo is supported and the other when only undo is supported:
public class Memento
{
    public Action Undo { get; private set; }
    public Action Redo { get; private set; }

    public Memento(Action undo)
    {
        Undo = undo;
        Redo = () => { };
    }

    public Memento(Action undo, Action redo)
    {
        Undo = undo;
        Redo = redo;
    }
}
The second class actual contains the Memento instances, simply it contains a pair of Stack representing the undo & redo list. When an Undo action is executed the Memento is first popped from the undo stack, executed and then pushed onto the Redo stack, and it's the reverse for a Redo action. The only other behaviour of note is when a new Undo\Redo is added, any Redo's are cleared, this is to prevent confusion when changes are being reverted to a certain point and then editing continues.
public class Undoable
{
    private readonly Stack<Memento> undoStack;
    private readonly Stack<Memento> redoStack;

    public Undoable()
    {
        undoStack = new Stack<Memento>();
        redoStack = new Stack<Memento>();
    }

    public void Add(Action undoAction, Action redoAction)
    {
        undoStack.Push(new Memento(undoAction, redoAction));
        redoStack.Clear();
    }

    public void Undo()
    {
        if (undoStack.Count == 0)
        {
            return;
        }

        var current = undoStack.Pop();
        current.Undo();
        redoStack.Push(current);
    }

    public void Redo()
    {
        if (redoStack.Count == 0)
        {
            return;
        }

        var current = redoStack.Pop();
        current.Redo();
        undoStack.Push(current);
    }
        
    public void Clear()
    {
        redoStack.Clear();
        undoStack.Clear();
    }
}
So to round a couple of test written using MSpec. The first one shows the Undo'ing of setting a simple (value type) property on the model:
[Subject("Undoable")]
public class when_undoing_a_value_type_property_modification
{
    private Establish context = () =>
    {
        undoable = new Undoable();
        widget = new Widget {Name = OriginalName};
    };

    private Because of = () =>
    {
        undoable.Add(() => { widget.Name = OriginalName; }, () => { widget.Name = NewName; });

        widget.Name = NewName;
        undoable.Undo();
    };

    private It should_undo_setting_the_name_on_a_widget = () => widget.Name.ShouldEqual(OriginalName);

    private static string OriginalName = "Original Name - " + Guid.NewGuid();
    private static string NewName = "New name - " + Guid.NewGuid();
    private static Widget widget;
    private static Undoable undoable;
}

The second shows the Redo'ing of adding a child Widget to the parent:
[Subject("Undoable")]
public class when_redoing_a_reference_type_property_modification
{
    private Establish context = () =>
    {
        undoable = new Undoable();
        parent = new Widget { Name = "Parent Widget" };
        child = new Widget { Name = "Child Widget" };
    };

    private Because of = () =>
    {
        undoable.Add(() => parent.RemoveChild(child), () => parent.AddChild(child));
        parent.AddChild(child);
        undoable.Undo();
        undoable.Redo();
    };

    private It parent_widget_should_cotain_child_widget = () => parent.Children.Contains(child).ShouldEqual(true);

    private It parent_widget_children_collection_should_not_be_empty = () => parent.Children.Count().ShouldNotEqual(0);

    private static Widget parent;
    private static Widget child;
    private static Undoable undoable;
}

I've pushed this to GitHub here.

Comments