Catnap Games

Simple undo/redo system for C#

So you want to add an undo/redo system to your program? OK.

First, we need to separate the GUI from the code that actually does stuff. This is sometimes called “a command design pattern”. Each command should know how to “do” stuff and how to “undo” it. It may be as simple as this:

public interface Command
{
void Do();
void Undo();
}

Second, we need a class that will keep track of what commands have been applied and which ones haven’t. Let’s call this class History. It knows where we are and what to do (or undo) next. Again, pretty simple. So let’s add some more functionality while we’re at it. Our History class will notify us when a command is executed and will know if there are any unsaved changes. Here goes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


public class History {

private List commands = new List();
private int lastExecuted = -1;
private int lastSaved = -1;

public delegate void Changed(bool haveUnsavedChanges);
public event Changed OnChanged = (h) => { };

public void Clear() {
commands.Clear();
lastExecuted = -1;
lastSaved = -1;

OnChanged(false);
}


public void Save() {
lastSaved = lastExecuted;

OnChanged(false);
}


public bool Modified {
get { return lastSaved != lastExecuted; }
}


public int Size
{
get { return commands.Count; }
}


public int LastExecuted
{
get { return lastExecuted; }
}


public void Limit(int numCommands) {
while (commands.Count > numCommands) {
commands.RemoveAt(0);
if (lastExecuted >= 0) {
lastExecuted--;
}
if (lastSaved >= 0) {
lastSaved--;
}
}
}


public void Add(Command command, bool execute) {
if (lastExecuted + 1 < commands.Count) {
int numCommandsToRemove = commands.Count
- (lastExecuted + 1);
for (int i = 0; i < numCommandsToRemove; i++) {
commands.RemoveAt(lastExecuted + 1);
}
lastSaved = -1;
}
if (execute) {
command.Do();
}
commands.Add(command);
lastExecuted = commands.Count - 1;

OnChanged(true);
}


public void Undo() {
if (lastExecuted >= 0) {
if (commands.Count > 0) {
commands[lastExecuted].Undo();
lastExecuted--;
OnChanged(lastExecuted != lastSaved);
}
}
}


public void Redo() {
if (lastExecuted + 1 < commands.Count) {
commands[lastExecuted + 1].Do();
lastExecuted++;
OnChanged(lastExecuted != lastSaved);
}
}

}


You can drop these two classes into your project and start using them right away.

UPDATE: added a C++ version with demo - see this page for details.

You may run into a situation where you think you need to undo/redo multiple commands at once. For example, you have an editor that allows the user to select and change multiple objects simultaneously. There is a simple solution for this - implement a single additional Command - one that keeps a nested list of other commands and calls their Do() and Undo() methods all at once. You may want to reverse the call order for the Undo() to keep out of trouble.

Also, if you’re wondering what that “execute” flag is good for in the History.Add() method, imagine an editor that allows moving objects by dragging them. You probably don’t want to have a separate Move command for every tiny mouse move, do you? What you really want is a single Command that will put the object back into the place where it was when the user pressed the mouse button and started dragging it, right? Well, just remember the original coordinates and keep updating the object yourself with each mouse move. When the mouse button is released, create a Move command, give it the original and the new coordinates and add it to the history without executing it.