Click here to Skip to main content
Click here to Skip to main content

Using the Command pattern for undo functionality

, 22 Sep 2004
Rate this:
Please Sign up or sign in to vote.
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.

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.

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:

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.

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).

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

Share

About the Author

Matt Berther
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 PinmemberMember 47749644-Nov-08 1:43 
GeneralRe: same Functionality in Web PinmemberCarl Warman30-Sep-10 22:19 
GeneralInheritance PinsussAnonymous7-Jun-05 16:04 
GeneralRe: Inheritance PinmemberMatt Berther7-Jun-05 20:05 
GeneralRe: Inheritance PinmemberPaulo Zemek16-Jun-09 2:19 
Questioninterruption of undo history? PinsussAnonymous6-Feb-05 3:31 
AnswerRe: interruption of undo history? PinmemberMatt Berther8-Feb-05 9:20 
GeneralRe: interruption of undo history? Pinmemberplanoie4-Apr-07 21:59 
GeneralThanks! PinmemberClako0814-Nov-04 7:23 
GeneralNice PinmemberDrew Noakes28-Sep-04 22:16 
GeneralRe: Nice PinmemberMatt Berther8-Oct-04 12:20 
GeneralRe: Nice Pinmemberplanoie4-Apr-07 21:56 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141030.1 | Last Updated 23 Sep 2004
Article Copyright 2004 by Matt Berther
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid