Click here to Skip to main content
15,884,077 members
Articles / Desktop Programming / Win32

An implementation of Command pattern in C#

Rate me:
Please Sign up or sign in to vote.
4.73/5 (8 votes)
3 Nov 2010CPOL16 min read 50.7K   1.3K   38  
.NET delegates and Generics allow for an elegant implementation of the Command pattern.
// The class factory can improve readability of the code by providing methods that return an operation object of the right type
// This is purely a design decision.
// For the purposes of demonstration you can define this manifest constant to use the class factory method.
#define UseClassFactory

// Readbility can also be improved by defining types for the operations.
// This is purely a design decision.
// For the purposes of demonstration you can define this manifest constant to use the type definitions method.
#define UseTypeDefinitions

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using KWUtils;

namespace CommandDemo
{
#if UseTypeDefinitions
	using AddOpType = Operation<string, int>;
	using UnAddOpType = Operation<string, int>;
	using DeleteOpType = Operation<List<int>, int>;
	using UnDeleteOpType = Operation<List<int>, List<string>>;
#endif

	public partial class CommandDemoForm : Form
	{
		#region Data members
		/// <summary>
		/// We save the names in a file. As this is just a demo the filename is fixed
		/// </summary>
		private const string fileName = "CommandDemo.txt";
		#endregion

		#region constructor
		/// <summary>
		/// Standard form initialiser. Reads in the list of names from the file
		/// </summary>
		public CommandDemoForm()
		{
			InitializeComponent();

			// Add a handler for the modifiedChanged event
			OperationBase.modifiedChanged += new CommandEventHandler(OperationBase_modifiedChanged);
			// Read in the list of names if it exists.
			try
			{
				string[] list = File.ReadAllLines(fileName);
				foreach (string line in list)
				{
					namesListBox.Items.Add(line);
				}
			}
			catch (FileNotFoundException)
			{
				// If the file doesn't exist just leave the list empty
			}
		}
		#endregion

		#region Handlers
		/// <summary>
		/// Handler for the Command ModifoedChanged event. Shows a star in the title bar
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="CommandEventArgs"/> that contains the event data.</param>
		void OperationBase_modifiedChanged(object sender, CommandEventArgs e)
		{
			if (OperationBase.IsModified)
			{
				this.Text += " *";
			}
			else
			{
				this.Text = this.Text.Substring(0, this.Text.Length - 2);
			}
		}

		/// <summary>
		/// Handler for the text box text changed event. Enables the Add button if there is some text
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		private void newNameTextBox_TextChanged(object sender, EventArgs e)
		{
			addButton.Enabled = newNameTextBox.Text.Length > 0;
		}

		/// <summary>
		/// Handler for the add button. Adds the new name to the list
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		private void addButton_Click(object sender, EventArgs e)
		{
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
			AddOperation(newNameTextBox.Text).Do();
#else
			new Operation<string, int>("Add", DoAdd, newNameTextBox.Text, 0).Do();
#endif
			newNameTextBox.Text = "";
		}

		/// <summary>
		/// Handler for the Save menu item. Saves the contents of the listbox in a file. As this is just a demo the filename is fixed.
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		private void saveToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Save();
			// We must reset the command structure to show that the data is no longer modified
			OperationBase.IsModified = false;
		}

		/// <summary>
		/// Handler for the keyDown event on the names list box. Handles the delete key to delete items
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="KeyEventArgs"/> that contains the event data.</param>
		private void namesListBox_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Delete)
			{
				// We cannot just pass a reference to namesListBox.SelectedItems as the Delete operation
				// is also used to redo the operation. Instead we pass a list of indices.
				List<int> indices = new List<int>();
				foreach (int index in namesListBox.SelectedIndices)
				{
					indices.Add(index);
				}
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
				DeleteOperation(indices).Do();
#else
				new Operation<List<int>, int>("Delete", DoDelete, indices, 0).Do();
#endif
			}
		}

		/// <summary>
		/// Handles the edit menu DropDownOpening event. Enables the Undo and Redo items as appropriate
		/// </summary>
		/// <param name="sender">The source of the event</param>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		private void editToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
		{
			if (string.IsNullOrEmpty(OperationBase.UndoName))
			{
				undoToolStripMenuItem.Text = "Undo";
				undoToolStripMenuItem.Enabled = false;
			}
			else
			{
				undoToolStripMenuItem.Text = "Undo " + OperationBase.UndoName;
				undoToolStripMenuItem.Enabled = true;
			}
			if (string.IsNullOrEmpty(OperationBase.RedoName))
			{
				redoToolStripMenuItem.Text = "Redo";
				redoToolStripMenuItem.Enabled = false;
			}
			else
			{
				redoToolStripMenuItem.Text = "Redo " + OperationBase.RedoName;
				redoToolStripMenuItem.Enabled = true;
			}
		}

		/// <summary>
		/// Handles the Undo menu item.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void undoToolStripMenuItem_Click(object sender, EventArgs e)
		{
			OperationBase.Undo();
		}

		/// <summary>
		/// Handles the Redo menu item
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void redoToolStripMenuItem_Click(object sender, EventArgs e)
		{
			OperationBase.Redo();
		}

		/// <summary>
		/// Handles the form closing event. Check to see if the data has been modified and prompts for a save
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void CommandDemoForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (OperationBase.IsModified &&
				MessageBox.Show("The data has been changed, do you wish to save?", "Command Demo", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
			{
				Save();
			}
		}
		#endregion

		#region Operation methods
		/// <summary>
		/// The Do add operation. Adds the name to the listbox.
		/// </summary>
		/// <param name="name">The name to be added to the list box</param>
		/// <param name="dummy">Not used</param>
		/// <returns></returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private UnAddOpType DoAdd(string name, int dummy)
#else
		private Operation<string, int> DoAdd(string name, int dummy)
#endif
		{
			// Add the name. We want to cater for sorted lists so we need to keep the index where it is added
			int index = namesListBox.Items.Add(name);
			// Return the undo operation. This requires both the name and the index
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
			return UnAddOperation(name, index);
#else
			// Note that the name of the Undo operation is also "Add".
			return new Operation<string, int>("Add", UndoAdd, name, index);
#endif
		}

		/// <summary>
		/// The Undo Add operation. 
		/// </summary>
		/// <param name="name">The name that is being removed. This is required to pass back to the Redo operation</param>
		/// <param name="index">The index of the item that was added</param>
		/// <returns></returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private AddOpType UndoAdd(string name, int index)
#else
		private Operation<string, int> UndoAdd(string name, int index)
#endif
		{
			// Remove the item from the specified index
			namesListBox.Items.RemoveAt(index);
			// Return the Redo operation, which is the Do operation and takes the name as parameter
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
			return AddOperation(name);
#else
			return new Operation<string, int>("Add", DoAdd, name, 0);
#endif
		}

		/// <summary>
		/// The delete operation. We cater for a multi-select list box so multiple items can be selected
		/// This is also used to redo a previous delete, so it shouldn't use the listbox itself,
		/// or a reference to the selectedItems member as the items may not be selected when the redo is done.
		/// So we pass a list of names and a list of indices.
		/// </summary>
		/// <param name="indices">A list of the indices of the items to be deleted</param>
		/// <param name="dummy">Not used</param>
		/// <returns></returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private UnDeleteOpType DoDelete(List<int> indices, int dummy)
#else
		private Operation<List<int>, List<string>> DoDelete(List<int> indices, int dummy)
#endif
		{
			// We need to delete in reverse order, so sort the list
			indices.Sort();
			// Now delete the items, keeping a list of the names for the Undo
			List<string> names = new List<string>();
			for (int x = indices.Count - 1; x >= 0; x--)
			{
				names.Insert(0, namesListBox.Items[indices[x]] as string);
				namesListBox.Items.RemoveAt(indices[x]);
			}
			// Return the undo operation
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
			return UnDeleteOperation(indices, names);
#else
			return new Operation<List<int>, List<string>>("Delete", UndoDelete, indices, names);
#endif
		}

		/// <summary>
		/// The undo delete operation. this also accepts multiple items
		/// </summary>
		/// <param name="indices">The indices of the items to be put back.</param>
		/// <param name="names">The names to be put back</param>
		/// <returns></returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private DeleteOpType UndoDelete(List<int> indices, List<string> names)
#else
		private Operation<List<int>, int> UndoDelete(List<int> indices, List<string> names)
#endif
		{
			// We need to put the names back at the given indices.
			// Note that the fundamental principle of Do/Undo/Redo is that the data cannot have been changed between
			// a Do and an Undo. The list of indices is already sorted, and the items were removed in reverse order,
			// so if we replace them in forward order they will go back in their original places.
			// Note that if the list is sorted the indices won't make any difference.
			for (int x = 0; x < indices.Count; x++)
			{
				namesListBox.Items.Insert(indices[x], names[x]);
			}
			// Now we create a redo operation to delete them again
#if UseClassFactory
			// The class factory improves readability of the code by returning an operation object of the right type
			return DeleteOperation(indices);
#else
			return new Operation<List<int>, int>("Delete", DoDelete, indices, 0);
#endif
		}
		#endregion

		#region ClassFactory
		// This is a way of simplifying the code by providing a class factory for the various operation classes
		// Define UseClassFactory to compile using this
#if UseClassFactory
		/// <summary>
		/// Make an add operation.
		/// </summary>
		/// <param name="name">The name to be added</param>
		/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private AddOpType AddOperation(string name)
#else
		private Operation<string, int> AddOperation(string name)
#endif
		{
			return new Operation<string, int>("Add", DoAdd, name, 0);
		}

		/// <summary>
		/// Make an undo add operation.
		/// </summary>
		/// <param name="name">The name to be un-added</param>
		/// <param name="index">The index of the name to be un-added</param>
		/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private UnAddOpType UnAddOperation(string name, int index)
#else
		private Operation<string, int> UnAddOperation(string name, int index)
#endif
		{
			// Note that the undo operation takes the same name as the do operation
			return new Operation<string, int>("Add", UndoAdd, name, index);
		}

		/// <summary>
		/// Make a delete operation.
		/// </summary>
		/// <param name="names">The list of names to be deleted</param>
		/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private DeleteOpType DeleteOperation(List<int> names)
#else
		private Operation<List<int>, int> DeleteOperation(List<int> names)
#endif
		{
			return new Operation<List<int>, int>("Delete", DoDelete, names, 0);
		}

		/// <summary>
		/// Make an undelete operation.
		/// </summary>
		/// <param name="indices">The indices of the names to be undeleted.</param>
		/// <param name="names">The list of names to be deleted</param>
		/// <returns>The new Operation object</returns>
#if UseTypeDefinitions
		// Type definitions improve the readability of the code
		private UnDeleteOpType UnDeleteOperation(List<int> indices, List<string> names)
#else
		private Operation<List<int>, List<string>> UnDeleteOperation(List<int> indices, List<string> names)
#endif
		{
			// Note that the undo operation takes the same name as the do operation
			return new Operation<List<int>, List<string>>("Delete", UndoDelete, indices, names);
		}
#endif
		#endregion
		
		#region private methods
		private void Save()
		{
			StreamWriter streamWriter = File.CreateText(fileName);
			foreach (string item in namesListBox.Items)
			{
				streamWriter.WriteLine(item);
			}
			streamWriter.Close();
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
After acquiring a degree in Electronic Engineering and Physics from Loughborough University, I moved into software engineering. In 1991 I went freelance and have been contracting ever since. I live in the North West of England and spend most of my spare time on the stage, or in Africa. If you like the code in my articles please feel free to offer me a job. If you would like to support my work with Project African Wilderness in Malawi please go to www.ProjectAfricanWilderness.org

Comments and Discussions