Implement a Command Pattern Using C#

Implement a Command Pattern Using C#

With Visual Studio.NET IDE, one can easily add user interface objects such as menus and toolbar buttons to a form and let the IDE generate event handlers skeleton code in the form's design window to handle the events triggered by pressing toolbar buttons or selecting menus. Although this automatic code generation makes the user interface design and coding very easy, for a large, complex Windows application, a great amount of event handling code is dumped into the form class and makes the form class code increasingly bulky and difficult to maintain. From the viewpoint of object-oriented design, this is also not a very good design and the code cannot be re-used.

Buttons or menu items usually carry out a request in response to user input, which then causes an action to be invoked. The command design pattern lets these user interface objects make requests of unspecified application objects by turning the request itself into an object. This object can be stored and passed around like other objects. This separation of user interface objects from the receivers of the requests makes the code easy to maintain and promotes code re-use.

This article gives an example of an implementation of the Command pattern using C# in a Windows application.

The Application

The application is just for demo purposes and therefore it is not a real-world one. It is a simple text editor with Open File and Save File menu items in the File menu; and Copy, Cut, and Paste menu items in the Edit menu. It also has Open File, Save File, Copy, and Paste tool strip buttons. Each of these user interface objects has a reference to a command object that is derived from an abstract base command class. When a click event is triggered, the user interface object invokes the Execute method of the concrete command object. The concrete command class has a reference to an editor object and delegate the task to the editor object. A concrete command object is created by a command factory that provides factory methods for each concrete command class.

The Text Editor Class

The TextEditor class contains a Windows textbox and methods to open and save a text file and copy, cut, and and paste text in the text box. The TextEditor also handles the shortcut keys Ctrl+X for cut, Ctrl+V for paste, and Ctrl+Z for undo. When it receives a Ctrl+X or Ctrl+V shortcut key, it creates a Cut or Paste command object and pushes it to a command stack owned by an undo command object for a possible undo operation later on. Therefore, the TextEditor also has a reference to the command factory and undo command object. I'll discuss the command factory and undo command later.

public class TextEditor
{
   private CmdFactory _factory;
   private UndoCmd _undoCmd;
   private TextBox _textBox;
   private OpenFileDialog _openFileDlg;
   private string _filePath;

   public TextEditor(TextBox textBox)
   {
      _textBox = textBox;
      _undoCmd = null;
      _openFileDlg = new OpenFileDialog();
      this._textBox.KeyDown +=
         new System.Windows.Forms.
         KeyEventHandler(this.TextBoxKeyDown);
   }

   // Properties, methods, and KeyDown event handler below.

}

The Command Factory Class

The CmdFactory command factory class has a reference to TextEditor and provides factory methods to produce command objects.

public class CmdFactory
{
   private TextEditor _editor;

   public CmdFactory(TextEditor editor)
   {
      _editor = editor;
      _editor.CmdFactory = this;
   }

   public OpenFileCmd OpenFileCmd()
   {
      return new OpenFileCmd(_editor, this);


   }

   public SaveFileCmd SaveFileCmd()
   {
      return new SaveFileCmd(_editor, this);
   }

   public UndoCmd UndoCmd(CmdMenuItem undoMenuItem)
   {
      return new UndoCmd(_editor, this, undoMenuItem);
   }

   public CopyCmd CopyCmd()
   {
      return new CopyCmd(_editor, this);
   }

   public CutCmd CutCmd()
   {
      return new CutCmd(_editor, this);
   }

   public PasteCmd PasteCmd()
   {
      return new PasteCmd(_editor, this);
   }
}

The Base Class of the Command Classes

The BaseCmd class is the base class of all other command classes. It has references to TextEditor and CmdFactory objects and an abstract method, Execute().

abstract public class BaseCmd
{
   protected TextEditor _editor;
   protected CmdFactory _factory;
   abstract public void Execute();

   public BaseCmd(TextEditor editor, CmdFactory factory)
   {
      _editor = editor;
      _factory = factory;
   }
}

The Concrete Command Classes That Do Not Support Undo

Generally, concrete command classes override the Execute() abstract method of the base class and delegate the operation to TextEditor. Some concrete commands do not support undo operation; for example, open/save file commands, and the copy command. These command classes are directly derived from the BaseCmd class.

public class OpenFileCmd : BaseCmd
{
   public OpenFileCmd(TextEditor editor, CmdFactory factory)
      : base(editor, factory)
   {
   }

   // Delegate Execution operation to OpenFile operation
   // of TextEditor.
   public override void Execute()
   {
      _editor.OpenFile();
   }
}

public class SaveFileCmd : BaseCmd
{
   public SaveFileCmd(TextEditor editor, CmdFactory factory)
      : base(editor, factory)
   {
   }

   public override void Execute()
   {
      _editor.SaveFile();
   }
}

public class CopyCmd : BaseCmd
{
   public CopyCmd(TextEditor editor, CmdFactory factory)
      : base(editor, factory)
   {
   }

   public override void Execute()
   {
      _editor.Copy();
   }
}

Concrete Command Classes That Support Undo Operation

The cut and paste command supports the undo operation. They are derived from the UndoableCmd class that is an abstract class itself and is in turn derived from the BaseCmd class. UndoableCmd has an abstract property, Name, and an abstract method, Undo().

abstract public class UndoableCmd : BaseCmd
{
   public UndoableCmd(TextEditor editor, CmdFactory factory)
      : base(editor, factory)
   {
   }

   abstract public string Name { get; }
   abstract public void Undo();
}

A concrete undoable command class is derived from the UndoabldCmd class. It overrides the Execute() abstract method of the BaseCmd class and the Undo() abstract method of the UndoableCmd class. Here, you take the CutCmd class for the cut command as an example.

public class CutCmd : UndoableCmd
{
   private int _selStart;
   private string _selText;

   public CutCmd(TextEditor editor, CmdFactory factory)
      : base(editor, factory)
   {
      _selStart = editor.SelectionStart;
      _selText = editor.SelectedText;
   }

   public override string Name
   {
      get { return "Cut"; }
   }

   public override void Execute()
   {
      CutCmd cmd = _factory.CutCmd();
      _editor.Cut(cmd);
   }

   public override void Undo()
   {
      _editor.UndoCut(_selStart, _selText);
   }
}
Note In the Execute() method of CutCmd, a new instance of CutCmd is created by the command factory. The method calls TextEditor's Cut() method, passing this CutCmd object as the parameter. This is because with each "cut" operation, a CutCmd object needs to be pushed into the command stack of the Undo command object for a possible undo operation. The Cut() method of TextEditor shows how it is accomplished:
// Cut() method of TextEditor
public void Cut(CutCmd cmd)
{
   _textBox.Cut();
   _undoCmd.Push(cmd);
}

Implement a Command Pattern Using C#

The Undo Command

The UndoCmd class implements the "undo" operation. It has a stack of UndoableCmd. When TextEditor executes a command that supports the undo operation, it gets the reference to that command from the parameter of the method and calls the UndoCmd's Push() method to push that command into the stack. In the Execute() method of the UndoCmd class, it pops an undoable command out of the stack and calls the Undo() method of this command. The UndoCmd class also keeps a reference to the "Undo" menu item; with each push/pop operation on its stack, it changes the "Undo" menu. It disables the menu if the stack is empty after a pop, and enables the menu with each push operation. The text of the Undo menu is determined by the command at the top of the stack.

public class UndoCmd : BaseCmd
{
   private Stack<UndoableCmd> _cmdStack;
   private CmdMenuItem _undoMenuItem;

   public UndoCmd(TextEditor editor, CmdFactory factory,
                  CmdMenuItem undoMenuItem)
      : base(editor, factory)
   {
      _undoMenuItem = undoMenuItem;
      _cmdStack = new Stack<UndoableCmd>();
   }

   public override void Execute()
   {
      UndoableCmd cmd = _cmdStack.Pop();
      cmd.Undo();
      if (_cmdStack.Count == 0)
      {
         _undoMenuItem.Visible = false;
      }
      else
      {
         _undoMenuItem.Visible = true;
         _undoMenuItem.Text = "Undo " + _cmdStack.Peek().Name;
      }
   }

   public void Push(UndoableCmd cmd)
   {
      _cmdStack.Push(cmd);
      _undoMenuItem.Visible = true;
      _undoMenuItem.Text = "Undo " + cmd.Name;
   }
}

Handling Shortcut Keys

When the shortcut key Ctrl+X for the cut operation or Ctrl+V for the paste operation is pressed, a CutCmd or PasteCmd object is created and is pushed into the command stack of the "Undo" command. If Ctrl+Z is pressed, the Execution() method of the Undo command object is called to keep the text of the "Undo" menu in sync. The code to handling the shortcut key pressed event is in TextEdit because it owns the textbox.

private void TextBoxKeyDown(object sender, KeyEventArgs e)
{
   // Ctrl-V pressed. Create a paste command for undoing paste.
   if (e.Modifiers == Keys.Control && e.KeyCode == Keys.V)
   {
      PasteCmd cmd = _factory.PasteCmd();
      _undoCmd.Push(cmd);
   }
   // Ctrl-X pressed. Create a cut command for undoing cut.
   if (e.Modifiers == Keys.Control && e.KeyCode == Keys.X)
   {
      CutCmd cmd = _factory.CutCmd();
      _undoCmd.Push(cmd);
   }
   // Ctrl-Z pressed, undo most recent undoable command.
   else if (e.Modifiers == Keys.Control && e.KeyCode == Keys.Z)
   {
      _undoCmd.Execute();
   }
}

The CmdMenuItem Class and the CmdToolButton Class

The CmdMenuItem class and the CmdToolButton class are derived from the .NET Windows forms MenuItem and ToolStripButton classes respectively. Note that the MenuItemClick() event handler is registered to CmdMenuItem so that when an menu item is clicked, the Execute() method of the command object is invoked. Due to polymorphism, the Execute() method of a concrete command class will be called at run-time. The same is true for the CmdToolButton class.

public class CmdMenuItem : MenuItem
{
   private BaseCmd _command;

   public CmdMenuItem()
      : base()
   {
      this.Click += new EventHandler(MenuItemClick);
      _command = null;
   }

   public BaseCmd Command
   {
      get { return _command; }
      set { _command = value; }
   }

   private void MenuItemClick(object sender, EventArgs args)
   {
      if (_command != null) _command.Execute();
   }
}

public class CmdToolButton : ToolStripButton
{
   private BaseCmd _command;

   public CmdToolButton()
      : base()
   {
      this.Click += new EventHandler(ButtonItemClick);
      _command = null;
   }

   public BaseCmd Command
   {
      get { return _command; }
      set { _command = value; }
   }

   private void ButtonClick(object sender, EventArgs args)
   {
      if (_command != null) _command.Execute();
   }
}

Modify the Form Designer Generated Code

The form designer created code declares each menu item as a System.Windows.Forms.MenuItem class type and each tool strip button as a System.Windows.Forms.ToolStripButton class type. You need to manually modify the form designer generated code to use the CmdMenuItem and CmdToolButton classes to implement the command pattern. For example, the form designer generated line of code

private System.Windows.Forms.MenuItem menuFileOpen;

should be changed to

private CommandPatternApp.CmdMenuItem menuFileOpen;

and this line of code

this.menuFileOpen = new System.Windows.Forms.MenuItem();

should be changed to

this.menuFileOpen = new CommandPatternApp.CmdMenuItem();

The code also needs to be modified accordingly in the InitializeComponent method generated by the form designer, where, for example,

this.menuFileOpen = new System.Windows.Forms.MenuItem();

should be changed to

this.menuFileOpen = new CommandPatternApp.CmdMenuItem();

Hook Commands to Menu Items and Tool Strip Buttons

Hooking commands to menu items and tool bar buttons is done in Form1.cs. In the FormMain_Load () method, you have following lines of code to accomplish this:

// Created editor and factory.
_editor  = new TextEditor(this._textBox);
_factory = new CmdFactory(_editor);

// Get concrete command objects from the factory and assign it to
// corresponding menu items and tool strip buttons.
this._menuFileOpen.Command  = _factory.OpenFileCmd();
this._menuFileSave.Command  = _factory.SaveFileCmd();
this._menuEditUndo.Command  = this._editor.UndoCmd =
    _factory.UndoCmd(this._menuEditUndo);
this._menuEditCut.Command   = _factory.CutCmd();
this._menuEditCopy.Command  = _factory.CopyCmd();
this._menuEditPaste.Command = _factory.PasteCmd();
this._buttonOpen.Command    = _factory.OpenFileCmd();
this._buttonSave.Command    = _factory.SaveFileCmd();
this._buttonCopy.Command    = _factory.CopyCmd();
this._buttonPaste.Command   = _factory.PasteCmd();

The source code was written in C# 2005 and is downloadable. The zip file contains a text file so that when you run the demo project, you can use this file as a test file to open, edit, and save it. To focus on the main structure and logic, I did not put any exception handling in the code. But, this demo project provides a starting point for developing real-world software that can utilize the Command pattern.



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Targeted attacks and advanced threats are customized to infiltrate your unique IT infrastructure, evade conventional defenses, and remain hidden while stealing your corporate data. To detect these criminal intrusions, analysts and security experts agree that organizations should deploy advanced threat protection as part of an expanded security monitoring strategy. For this comparative analysis of breach detection systems, product analysis reports and comparative analysis reports are used to create the security …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds