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

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

Custom AuthorizationHandler for SignalR Hubs

How to implement IAuthorizationRequirement for SignalR in Asp.Net Core v5.0 Been battling this for a couple of days, and eventually ended up raising an issue on Asp.Net Core gitHub  to find the answer. Wanting to do some custom authorization on a SignalR Hub when the client makes a connection (Hub is created) and when an endpoint (Hub method) is called:  I was assuming I could use the same Policy for both class & method attributes, but it ain't so - not because you can't, because you need the signatures to be different. Method implementation has a resource type of HubInnovationContext: I assumed class implementation would have a resource type of HubConnectionContext - client connects etc... This isn't the case, it's infact of type DefaultHttpContext . For me I don't even need that, it can be removed completely  from the inheritence signature and override implementation. Only other thing to note, and this could be a biggy, is the ordering of the statements in th