Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

Using the Command pattern for undo functionality

Rate me:
Please Sign up or sign in to vote.
4.38/5 (48 votes)
22 Sep 20043 min read 106.3K   790   72   12
Implementing undo functionality using design patterns.

Introduction

Command is a very powerful design pattern, whose intent is to encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

One of the biggest advantages to this pattern is that it decouples the object that invokes the operation from the one that actually knows how to perform it.

Today, I want to show you how to implement undo functionality using this command design pattern. For our example, we will be developing a very simple Notepad clone. Nothing too fancy, but enough to show the power of the pattern.

The Code

The first thing we want to do is to create an abstraction around our TextBox control. In the Command pattern, this abstraction is called the Receiver. The receiver in our example is an object called Document.

C#
class Document
{
    private TextBox textbox;
    public Document(TextBox textbox)
    {
        this.textbox = textbox;
    }

    public void BoldSelection()
    {
        Text = String.Format("{0}", Text);
    }

    public void UnderlineSelection()
    {
        Text = String.Format("<u>{0}</u>", Text);
    }
    
    public void ItalicizeSelection()
    {
        Text = String.Format("{0}", Text);
    }

    public void Cut()
    {
        textbox.Cut();
    }

    public void Copy()
    {
        textbox.Copy();
    }

    public void Paste()
    {
        textbox.Paste();
    }

    public string Text
    {
        get { return textbox.Text; }
        set { textbox.Text = value; }
    }
}

What we have defined with the Document object are the operations that could be performed against this document, completely decoupling this functionality from our main application. If we want to change what happens when we bold a selection, we go to this object, rather than to the presentation code.

Secondly, we will need to design our Command interfaces. Since it is possible that we have commands that will not require undo functionality (for example, Copy), we have created two base classes (Command and UndoableCommand). We'll see how UndoableCommand ties in, a little later in the article. For now, just keep in mind that this is the class to derive from if you need your command to be able to undo itself.

C#
public abstract class Command
{
    public abstract void Execute();
}

public abstract class UndoableCommand : Command
{
    public abstract void Undo();
}

As we work through our application and start adding menu items to it, we will start to see that we need a Command object to handle each of these actions. So, to handle bold, let's create the following:

C#
class BoldCommand : UndoableCommand
{
    private Document document;
    private string previousText;
    public BoldCommand(Document doc)
    {
        this.document = doc;
        previousText = this.document.Text;
    }

    public override void Execute()
    {
        document.BoldSelection();
    }

    public override void Undo()
    {
        document.Text = previousText;
    }
}

By creating a document object that wraps the TextBox, we were able to completely decouple the object that will invoke the operation (a menu item) from the one that knows how to perform it (the document object).

The remaining command objects are very similar to the above. Because of this, I wont present the entire code here in print, although it is available via the download.

The remaining piece that we will need to bring everything together is a CommandManager. The CommandManager is a very simple class that has an internal stack that keeps track of our commands for our undo functionality.

C#
class CommandManager
{
    private Stack commandStack = new Stack();

    public void ExecuteCommand(Command cmd)
    {
        cmd.Execute();
        if (cmd is UndoableCommand)
        {
            commandStack.Push(cmd);
        }
    }

    public void Undo()
    {
        if (commandStack.Count > 0)
        {
            UndoableCommand cmd = (UndoableCommand)commandStack.Pop();
            cmd.Undo();
        }
    }
}

We see in the above code, that we only add the command to the undo stack if it is an UndoableCommand. Remember, when I said that we would see how it ties in. Here it is. We don't want commands that don't have any undo functionality to be added to the stack. If the user tries to undo something that doesn't support undo, it would appear to be unresponsive.

The remaining thing that we now have to do is wire up the event handlers for the menu items (and the toolbars, if you're so inclined).

C#
public class MainForm : System.Windows.Forms.Form
{
    private System.Windows.Forms.TextBox documentTextbox;
    private CommandManager commandManager = new CommandManager();
    private Document document;

    public MainForm()
    {
        //
        // Required for Windows Form Designer support
        //
        InitializeComponent();
        
        document = new Document(this.documentTextbox);
    }

    // a bunch of snipped code

    private void cutMenuItem_Click(object sender, System.EventArgs e)
    {
        commandManager.ExecuteCommand(new CutCommand(document));
    }

    private void pasteMenuItem_Click(object sender, System.EventArgs e)
    {
        commandManager.ExecuteCommand(new PasteCommand(document));
    }
    
    // etc...
}

I've made the entire sample application available for download. Keep in mind, at this point, this is a very rudimentary text editor. Your mission, should you choose to accept it, is to add redo functionality to this application.

I hope that I've been able to illustrate the power of this pattern and how it could be easily used to add complex functionality. It's really easy to add new Commands, because you don't have to change any existing objects.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I am a 31 year old software developer from Boise, Idaho. I have been working with computers since approximately age 12, when my 6th grade teacher got me hooked. My parents got me a Commodore 64 for Christmas that year, and it's been downhill ever since. Wink | ;)

I have taught myself software development, beginning with Microsoft's Visual Basic 4.0. Approximately 5 years ago, I was given an opportunity to work in the tech field for a company called HealthCast. HealthCast's web-based technology solutions manage and control access to applications and patient information stored in legacy systems, client-server applications, or web solutions.

I left HealthCast in February 2003, to pursue a fantastic opportunity with Healthwise. Healthwise provides content to organizations and informs people to help them make better health decisions, creating Prescription-Strength Information™ tools that doctors trust and consumers use.

I have been working with the .NET framework since version 1.0, beta 2 and have not looked back since. Currently, I am most intrigued by the uses of the .NET framework and XML to create distributed, reusable applications.

Comments and Discussions

 
GeneralRe: same Functionality in Web Pin
Member 47749644-Nov-08 1:43
Member 47749644-Nov-08 1:43 
GeneralRe: same Functionality in Web Pin
Carl Warman30-Sep-10 22:19
Carl Warman30-Sep-10 22:19 
GeneralInheritance Pin
Anonymous7-Jun-05 16:04
Anonymous7-Jun-05 16:04 
GeneralRe: Inheritance Pin
Matt Berther7-Jun-05 20:05
Matt Berther7-Jun-05 20:05 
Anonymous wrote:
1. Replacing the private Document fields in the individual classes with a protected Document field in the abstract Command class.

Fair enough. You're probably right here...

2. Replacing the private previousText string fields in the individual classes with a protected field in the abstract UndoableCommand class.

Im not sure about this. Not all UndoableCommands will have a previousText. (Think of a ResizeCommand).

3. Replacing the individual overriden Undo() methods with a public method in the abstract UndoableCommand class.

If I understand you correctly, this is related to item 2 above. Since all UndoableCommand may not offer the same Undo functionality, I leave this up to the individual classes to implement. You could introduce a class between those that could handle these things though.


--
Matt Berther
http://www.mattberther.com
GeneralRe: Inheritance Pin
Paulo Zemek16-Jun-09 2:19
mvaPaulo Zemek16-Jun-09 2:19 
Questioninterruption of undo history? Pin
Anonymous6-Feb-05 3:31
Anonymous6-Feb-05 3:31 
AnswerRe: interruption of undo history? Pin
Matt Berther8-Feb-05 9:20
Matt Berther8-Feb-05 9:20 
GeneralRe: interruption of undo history? Pin
Peter Lanoie4-Apr-07 21:59
Peter Lanoie4-Apr-07 21:59 
GeneralThanks! Pin
Clako0814-Nov-04 7:23
Clako0814-Nov-04 7:23 
GeneralNice Pin
Drew Noakes28-Sep-04 22:16
Drew Noakes28-Sep-04 22:16 
GeneralRe: Nice Pin
Matt Berther8-Oct-04 12:20
Matt Berther8-Oct-04 12:20 
GeneralRe: Nice Pin
Peter Lanoie4-Apr-07 21:56
Peter Lanoie4-Apr-07 21:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.