I am teaching a .NET class this week, and the attendees are a technical and enthusiastic bunch which makes things fun for me (thanks SIG!!) Today we got into a discussion about the anonymous methods feature of C# 2.0. Due to their interest, and mine, I got an opportunity to write more involved demo code on the topic then I usually do. Following is a cleaned-up/commented version of the code.

Summary:

  • Uses anonymous methods to implement the command pattern, a-la GoF.
  • Implements an Undo/Redo stack class.
  • Uses anonymous methods to keep undo and redo commands together in a single method declaration.
  • Uses anonymous methods to generate the state objects necessary to maintain specific undo and redo command information.
  • Don't forget, you need a Whidbey build of the runtime or VS.Net 2005 to try this code.

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Windows.Forms;

 

class App {

   public static void Main() {

      Application.Run(new MyForm());

   }

}

 

class MyForm : Form {

 

   // Fields for my controls

   ListBox list = new ListBox();

   TextBox textbox = new TextBox();

   Button buttonUndo;

   Button buttonRedo;

 

   UndoRedoStack urs;

 

   public MyForm() {

      Text = "Anon Sample";

      urs =  new UndoRedoStack(UndoRedoEnabled);

 

      // Create my list and textbox controls

      list.Location = new Point(150, 10);

      list.Parent = this;

      list.Sorted = true;

 

      textbox.Location = new Point(10, 10);

      textbox.Parent = this;     

 

      // Create my buttons which perform the

      // initial commands operation

      Button b = new Button();

      b.Text = "Add Item";

      b.Location = new Point(10, 40);

      b.Click += delegate { AddItem(); };

      b.Parent = this;

 

      b = new Button();

      b.Text = "Rename Item";

      b.Location = new Point(10, 70);

      b.Click += delegate { RenameItem(); };

      b.Parent = this;

 

      // Create my button which performs an undo

      buttonUndo = new Button();

      buttonUndo.Location = new Point(10, 110);

      buttonUndo.Text = "Undo";

      buttonUndo.Click +=

         delegate { urs.Undo(); };

      buttonUndo.Parent = this;

 

      // Create my button which performs a redo

      buttonRedo = new Button();

      buttonRedo.Location = new Point(10, 140);

      buttonRedo.Text = "Redo";

      buttonRedo.Click +=

         delegate { urs.Redo(); };

      buttonRedo.Parent = this;

 

      UndoRedoEnabled();

   }

 

   // Add an item to a sorted list box

   // (one of two commands)

   void AddItem() {

     

      Int32 item = 0;    

      String text = textbox.Text;

      textbox.Text = String.Empty;

 

      CommandCallback cbUndo, cbRedo;

     

      // Setup the cmd object to

      // contain our operations

      cbRedo = delegate {

         item = list.Items.Add(text);

      };

 

      cbUndo = delegate {

            list.Items.RemoveAt(item);

         };

 

      urs.PerformOperation(cbRedo, cbUndo);

   }

 

   // Rename an item in a sorted list box

   // (two of two commands)

   void RenameItem() {

      Int32 item = list.SelectedIndex;

 

      if (item > -1) {

         String oldText =

            list.SelectedItem.ToString();        

         String newText = textbox.Text;

         textbox.Text = String.Empty;

 

         CommandCallback cbUndo, cbRedo;

     

         Int32 newItem = 0;

         // Setup the cmd object to

         // contain our operations

         cbRedo = delegate {

               list.Items.RemoveAt(item);

               newItem =

                  list.Items.Add(newText);              

            }; 

 

         cbUndo = delegate {

               list.Items.RemoveAt(newItem);

               list.Items.Add(oldText);

            };

 

         // perform the initial operation   

         urs.PerformOperation(cbRedo, cbUndo);

         list.SelectedIndex = newItem;

      }

   }

 

   void UndoRedoEnabled() {

      buttonUndo.Enabled = urs.CanUndo;

      buttonRedo.Enabled = urs.CanRedo;

   }

}

 

 

// Supporting delegate for the UndoRedoStack

public delegate void CommandCallback();

 

 

public class UndoRedoStack {

 

   // Command object which holds a matching

   // do and undo operation

   class Command {

      internal CommandCallback redo;

      internal CommandCallback undo;

   }

 

   // undo and redo stacks of Command objects

   Stack<Command> undoStack =

      new Stack<Command>();

   Stack<Command> redoStack =

      new Stack<Command>();

 

   CommandCallback undoStatus;

 

   public UndoRedoStack() : this(null) {}

   public UndoRedoStack( // optional callback

      CommandCallback statusUpdateCallback) {

      undoStatus = statusUpdateCallback;

   }

 

   public void PerformOperation(

      CommandCallback operation,

      CommandCallback undoOperation) {

      // Setup the cmd object to

      // contain our operations

      Command cmd = new Command();

      cmd.redo = operation;

      cmd.undo = undoOperation;

 

      // do the operation the first time

      cmd.redo();

 

      // push the undo on the stack

      undoStack.Push(cmd);

 

      // clear our redo "history"

      redoStack.Clear();

 

      if (undoStatus != null) undoStatus();

   }

 

   // Perform an undo operation

   public void Undo() {

      Command cmd = undoStack.Pop();

      cmd.undo();

      redoStack.Push(cmd);

      if (undoStatus != null) undoStatus();

   }

 

   // Perform a redo operation

   public void Redo() {

      Command cmd = redoStack.Pop();

      cmd.redo();

      undoStack.Push(cmd);

      if (undoStatus != null) undoStatus();

   }

 

   public Boolean CanUndo {

      get { return (undoStack.Count > 0); }

   }

 

   public Boolean CanRedo {

      get { return (redoStack.Count > 0); }

   }

}