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

Popular posts from this blog

WPF tips & tricks: Dispatcher thread performance

Not blogged for an age, and I received an email last week which provoked me back to life. It was a job spec for a WPF contract where they want help sorting out the performance of their app especially around grids and tabular data. I thought I'd shared some tips & tricks I've picked up along the way, these aren't probably going to solve any issues you might be having directly, but they might point you in the right direction when trying to find and resolve performance issues with a WPF app. First off, performance is something you shouldn't try and improve without evidence, and this means having evidence proving you've improved the performance - before & after metrics for example. Without this you're basically pissing into the wind, which can be fun from a developer point of view but bad for a project :) So, what do I mean by ' Dispatcher thread performance '? The 'dispatcher thread' or the 'UI thread' is probably the most

Showing a message box from a ViewModel in MVVM

I was doing a code review with a client last week for a WPF app using MVVM and they asked ' How can I show a message from the ViewModel? '. What follows is how I would (and have) solved the problem in the past. When I hear the words ' show a message... ' I instantly think you mean show a transient modal message box that requires the user input before continuing ' with something else ' - once the user has interacted with the message box it will disappear. The following solution only applies to this scenario. The first solution is the easiest but is very wrong from a separation perspective. It violates the ideas behind the Model-View-Controller pattern because it places View concerns inside the ViewModel - the ViewModel now knows about the type of the View and specifically it knows how to show a message box window: The second approach addresses this concern by introducing the idea of messaging\events between the ViewModel and the View. In the example below

Implementing a busy indicator using a visual overlay in MVVM

This is a technique we use at work to lock the UI whilst some long running process is happening - preventing the user clicking on stuff whilst it's retrieving or rendering data. Now we could have done this by launching a child dialog window but that feels rather out of date and clumsy, we wanted a more modern pattern similar to the way <div> overlays are done on the web. Imagine we have the following simple WPF app and when 'Click' is pressed a busy waiting overlay is shown for the duration entered into the text box. What I'm interested in here is not the actual UI element of the busy indicator but how I go about getting this to show & hide from when using MVVM. The actual UI elements are the standard Busy Indicator coming from the WPF Toolkit : The XAML behind this window is very simple, the important part is the ViewHost. As you can see the ViewHost uses a ContentPresenter element which is bound to the view model, IMainViewModel, it contains 3 child v