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); }
}
}