Welcome to MSDN Blogs Sign in | Join | Help

Samples for the Undo Framework

I just added some samples for the Undo Framework. You can find the samples in the source code or download them from the project website.

MinimalSample

First is a simple Console App sample called MinimalSample. Here’s the full source code:

using System;
using GuiLabs.Undo;

namespace MinimalSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Original color");

            SetConsoleColor(ConsoleColor.Green);
            Console.WriteLine("New color");

            actionManager.Undo();
            Console.WriteLine("Old color again");

            using (Transaction.Create(actionManager))
            {
                SetConsoleColor(ConsoleColor.Red); // you never see Red
                Console.WriteLine("Still didn't change to Red because of lazy evaluation");
                SetConsoleColor(ConsoleColor.Blue);
            }
            Console.WriteLine("Changed two colors at once");

            actionManager.Undo();
            Console.WriteLine("Back to original");

            actionManager.Redo();
            Console.WriteLine("Blue again");
            Console.ReadKey();
        }

        static void SetConsoleColor(ConsoleColor color)
        {
            SetConsoleColorAction action = new SetConsoleColorAction(color);
            actionManager.RecordAction(action);
        }

        static ActionManager actionManager = new ActionManager();
    }

    class SetConsoleColorAction : AbstractAction
    {
        public SetConsoleColorAction(ConsoleColor newColor)
        {
            color = newColor;
        }

        ConsoleColor color;
        ConsoleColor oldColor;

        protected override void ExecuteCore()
        {
            oldColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
        }

        protected override void UnExecuteCore()
        {
            Console.ForegroundColor = oldColor;
        }
    }
}

Here we define a new action called SetConsoleColorAction, and override two abstract methods: ExecuteCore() and UnExecuteCore(). In the ExecuteCore(), we change the color to the new color and backup the old color. In UnExecuteCore() we rollback to the backed up old color. We pass the action all the context information it needs (in our case, the desired new color). We rely on the action to backup the old color and store it internally.

The philosophy is to only store the smallest diff as possible. Try to avoid copying the entire world if you can only save the minimal delta between states.

Next, pay attention to the SetConsoleColor method. It wraps creating the action and calling RecordAction on it. It helps to create an API that abstracts away the action instantiation so that it is transparent for your callers. You don’t want your callers to create actions themselves every time, you just want them to call a simple intuitive API. Also, if for whatever reason you’d like to change or remove the Undo handling in the future, you’re free to do so without breaking the clients.

Finally, the source code in Main shows how you can intersperse your API calls with calls to Undo and Redo. It also shows using a transaction to group a set of actions into a single “multi-action” (Composite design pattern). You can call your API within the using statement, but the actions’ execution is delayed until you commit the transaction (at the end of the using block). That’s why you don’t see the console color changing to red in the middle of the block. If you undo a transaction, it will undo all the little actions inside it in reverse order.

WinFormsSample

The second sample is called WinFormsSample. It shows a windows form that let’s you edit properties of a business object:

image

You can change the text of both name and age, and the values will be mapped to the business object. You can also “Set Both Properties” which illustrates transactions. Then you can click Undo and it will rollback the state of your object to the previous state. The UI will update accordingly.

There is a trick in the code to avoid infinite recursion: on textbox text change, update the business object, fire an event, update the textboxes, update the business object again, etc... We use a boolean flag called “reentrancyGuard” that only enables the TextChanged events if the textbox modification was made by user, and not programmatically. If we updated the textboxes as a results of the business object change, no need to update the business object.

Note: If this was WPF, I would just use two-way data binding, but I wanted to keep the sample as simple as possible and use only basic concepts.

Action merging

Another thing worth mentioning that this sample demonstrates is action merging. As you type in the name in the textbox ‘J’, ‘o’, ‘e’, you don’t want three separate actions to be recorded, so that you don’t have to click undo three times. To enable this, an action can determine if it wants to be merged with the next incoming action. If the next incoming action is similar in type to the last action recorded in the buffer, they merge into a single action that has the original state of the first action and the final state of the new action. This feature is very useful for recording continuous user input such as mouse dragging, typing and other events incoming at a high rate that you want to record as just one change.

We update the visual state of the Undo and Redo buttons (enabled or disabled) to determine if the actionManager can Undo() or Redo() at the moment. We call the CanRedo() and CanUndo() APIs for this.

Hopefully this has been helpful and do let me know if you have any questions.

Published Thursday, July 02, 2009 12:16 PM by Kirill Osenkov
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Samples for the Undo Framework

Thursday, July 02, 2009 9:28 PM by leniel

Great code Kirill!

Easy to understand. :)

Leniel Macaferi

http://leniel.net

# re: Samples for the Undo Framework

Friday, July 03, 2009 12:21 PM by Bruce Pierson

This is nice. Is there an AbstractAction<T>, so we could use a more generic action?

# re: Samples for the Undo Framework

Monday, July 06, 2009 3:52 PM by Kirill Osenkov

Leniel:

Thanks!

Bruce:

I don't have a generic AbstractAction<T>. What's your usecase? It's simple to add one.

Thanks,

Kirill

# re: Samples for the Undo Framework

Thursday, July 09, 2009 1:11 PM by Steven

Uh... This isn't more than the command pattern in a processed form.

# re: Samples for the Undo Framework

Thursday, July 09, 2009 2:54 PM by Kirill Osenkov

Yup, that's what I wrote here: http://blogs.msdn.com/kirillosenkov/archive/2009/06/29/new-codeplex-project-a-simple-undo-redo-framework.aspx

I should have linked to the original post.

# re: Samples for the Undo Framework

Wednesday, July 15, 2009 2:46 PM by John

Funny - I wrote something very similar about 3 wks ago... UndoObject<T>

(I didn't write the merge piece... not needed for what I was doing)

One big difference is I use 2 Stack<T> Objects, and a Current Value property.  Current Value returns UndoStack.peek();  The CanUndo and CanRedo are evaluations of the Count property of each stack.  Obviously when you undo you pop the undo stack and push the result to the redo stack.

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker