Contents
I recently returned from doing voluntary work in East Africa [^], a trip which should have been for a year but which was sadly curtailed after six months by a rather nasty poisonous spider bite. As a result, I found myself back in the UK with a large hole in my leg, a stack of photographs, and a good deal of time on my hands, so I sat down and wrote Simple Slide Show [^], which is a no-frills slide-show script and display utility offered as "charityware" - donations are invited to Project African Wilderness [^], where I was working when the spider got me. One of the requirements for this was an implementation of the Command pattern. I hunted around on the web, but could only find rather simplistic implementations, so I designed this one myself. I'm not sure it is quite what the GOF had in mind, but it does the job, and seems to me to be an elegant implementation using both delegates and Generics to achieve the desired result. It was developed using Visual Studio 2005, and there may well be certain aspects which could be improved using the facilities of VS2008 or VS2010. Please feel free to post your comments.
The primary function of the Command pattern is to provide a framework within which data can be modified; the modification can then be undone, and then redone. A vital assumption is implied, that no operation is performed on the data that is outside this framework, which is to say that an undo operation must be able to assume that the data has not changed since the operation to be undone was done, and a redo operation must be able to assume that the data has not been changed since the operation to be redone was undone (I will be using the concepts of "doing", "undoing", and "redoing" a lot, so I hope you're keeping up!). An exception to this is when the data is originally set up when the program starts running, which cannot be undone, and need not be within the framework (persistent command stacks, while perfectly feasible, lead to all sorts of problems, are not in common use, and are outside the scope of this project).
A secondary function of the pattern is to provide an indication of whether the data has or has not been modified. Note that the Command pattern implementation has no knowledge whatsoever of the actual data. This can be anything specific to the user application. Note also that there are cases where the developer must make a design decision as to what constitutes a data-altering operation - for example, some programs perform the selection of data within the command framework, allowing the user to undo and redo a selection.
The basic principle of the Command pattern is to maintain a stack of "operation descriptors", whereby an "operation descriptor" contains all the information needed to perform an operation. This includes specifying a method, defined in the user application's code, that will perform a particular operation.
Many of the examples on the web are over-simplistic, making assumptions such as an add operation is undone with a subtract operation, or that operations do not require arguments. I have avoided these assumptions; however, in the matter of arguments, there is one that I could not avoid - while it is possible in C# .NET to define methods whose arguments can vary in type (Generics), it is, as far as I know, not possible to define a method whose arguments can vary in number. As will become clear, this means that we must specify up front how many arguments our operation methods will have. I plumped for two, which seemed adequate for most cases. Of course, the implementation provided here could easily be extended to allow more. The point being that all the methods defined in the user application's code to perform Command operations must take two parameters. If they do not need two parameters, then the unused one(s) must simply be named "dummy" (or whatever) and ignored.
Another assumption that I have avoided is that an undo operation is simply the opposite of a do operation, i.e., that Undoing an add is the same as Doing a subtract. It may be, but that is the application programmer's decision. In fact, the Command pattern should have no knowledge of what the operation methods actually do.
To achieve this, I have defined an operation method, within the framework of the Command implementation, as being a method which takes two parameters and returns another operation definition. The returned definition defines the operation that will undo the original operation, or, in the case of an undo, the method that will redo the operation that was undone.
Another assumption that I have made is that there will effectively be infinite space for the undo and redo stacks - there is no limit placed on their size. If an application were to potentially generate a very large stack of undo/redo operations, it might be necessary to modify the Command classes to limit the size of the stacks.
So, let's look at the code, which is in Command.cs. The first thing that is declared is a generic delegate that will be used to define our operation methods:
public delegate OperationBase OperationMethod<T1, T2>(T1 operand1, T2 operand2);
Notice that this is not just a delegate (for C++ programmers, this is the C# equivalent of a pointer to a function), but a generic delegate (as discussed above), so that its operands can be of any type. It returns an object of type OperationBase
.
Next, we find an event handler delegate:
public delegate void CommandEventHandler(object sender, CommandEventArgs e);
and a class derived from EventArgs
called CommandEventArgs
.
public class CommandEventArgs : EventArgs
{
private bool isModified = false;
public CommandEventArgs(bool isModified)
{
this.isModified = isModified;
}
public bool IsModified
{
get
{
return isModified;
}
}
}
These are used to define an event that is fired when the modified state of the user data is changed - i.e., it goes from being unmodified to being modified, or vice-versa. This will be clarified later. Next, we find the class OperationBase
. This is an abstract class which defines an operation, and within it is the bulk of the Command pattern implementation. Most importantly, OperationBase
defines two static objects of type List<OperationBase>
. These form two stacks, the undo stack and the redo stack (they are of type List<>
, but functionally, they are used like stacks). It also defines a static int SavePoint
. This is the index in the undo list where the last save took place, and thus defines whether the data has been modified since it was last saved. It is the user application's responsibility to inform the Command framework when the data is saved, as will be described shortly.
Another static member is an object of type object
, called parent
. This may be set by the user application if required, and will be passed back to the user application as the sender
when the modifiedChanged
event is fired. OperationBase
has a non-static member variable, a string
called name
. This is the name of the operation, and is useful to the user application for putting into the application menus - the Edit menu will often contain the Undo and Redo items which display what is to be undone/redone, so for example, after an Add, the menu would contain "Undo Add". For this reason, as will be seen in the demo program, the undo and redo operations will both be given the name of the operation; i.e., the undo operation for an insert will have the name "insert", not "remove" or "delete".
The final static member is the modifiedChanged
event, which is fired whenever the modified state changes. The constructor simply sets the name, and a property accessor is provided to set the parent, while the ToString()
override returns the name. The IsModified
property accessor sets or gets the modified state.
public static bool IsModified
{
get
{
return savePoint != undoList.Count;
}
set
{
bool wasModified = (savePoint != undoList.Count);
if (value)
{
savePoint = -1;
}
else
{
savePoint = undoList.Count;
}
if (wasModified != IsModified && modifiedChanged != null)
{
modifiedChanged(parent, new CommandEventArgs(IsModified));
}
}
}
The get
accessor simply returns true
if the savePoint
is not pointing to the top of the undo stack (note that the savePoint
can be -1, as described later). The set
accessor should be called with a value of "false
" when the user application saves its data. This resets the savePoint
to point to the top of the undo stack, and will also fire the modifiedChanged
event if the modified state has actually changed, i.e., it was modified prior to the accessor being called. Note that undo and redo are still available after a save - in either case, the data will then once again be considered to have been modified.
Under certain circumstances, the accessor may also be called with a value of true
. This sets savePoint
to -1 (and fires the modifiedChanged
event if necessary) which means that the modified state will always be true
no matter what undo-s and redo-s are performed until once again set to false
. (For example, in Simple Slide Show, the entire picture file is read in when the program starts. This is not done using the Command framework which would be complicated and unnecessary, but if a picture file is found not to exist, the user is given the option to remove it from the script. If it is removed, this is also not done within the Command framework - it cannot be undone - nevertheless, it is necessary to show that the data has been modified). The class now defines an abstract
method:
protected abstract OperationBase Execute();
This has no body, it will be overridden by the generic derivatives of OperationBase
as we will see later. The guts of the class is provided by the three methods Do()
, Undo()
, and Redo()
. Do()
essentially calls Execute()
, which returns an object of type OperationBase
which is the undo operation. This is "pushed" onto the undo stack (if it is not null
), and the redo list is cleared (a redo can only be done after an undo).
public void Do()
{
bool wasModified = IsModified;
OperationBase undoItem = Execute();
if (undoItem != null)
{
undoList.Add(undoItem);
if (savePoint >= undoList.Count)
{
savePoint = -1;
}
redoList.Clear();
}
if (wasModified != IsModified && modifiedChanged != null)
{
modifiedChanged(parent, new CommandEventArgs(IsModified));
}
}
Notice also this code:
if (savePoint >= undoList.Count)
{
savePoint = -1;
}
Consider this scenario: the user saves the data, undoes the previous operation, and then does another operation. It then becomes impossible to ever get back to the unmodified state - IsModified
must return true
under any circumstance until the data is again saved and IsModified = false
is called. Undo()
and Redo()
simply "pop" an operation off their respective stacks, execute the operation by calling Execute()
, and "push" the returned OperationBase
onto the other stack, so an undo puts the returned operation onto the redo stack and a redo puts the returned operation onto the undo stack.
public static bool Undo()
{
bool result = false;
bool wasModified = IsModified;
if (undoList.Count > 0)
{
OperationBase redoItem = undoList[undoList.Count - 1].Execute();
if (redoItem == null)
{
throw (new ArgumentException
("An undo method cannot return a null redo method"));
}
redoList.Add(redoItem);
undoList.RemoveAt(undoList.Count - 1);
result = true;
}
if (wasModified != IsModified && modifiedChanged != null)
{
modifiedChanged(parent, new CommandEventArgs(IsModified));
}
return result;
}
public static bool Redo()
{
bool result = false;
bool wasModified = IsModified;
if (redoList.Count > 0)
{
undoList.Add(redoList[redoList.Count - 1].Execute());
redoList.RemoveAt(redoList.Count - 1);
result= true;
}
if (wasModified != IsModified && modifiedChanged != null)
{
modifiedChanged(parent, new CommandEventArgs(IsModified));
}
return result;
}
Note that the operation method for Do()
and Redo()
may return null
(some operations cannot be meaningfully undone, for example, copy to clipboard), but the Undo()
method must return a non-null redo operation. UndoName
and RedoName
are get
accessors that return the name of the operation on the top of the undo or redo stack, respectively. This may be used by the user application to set the text in the Redo and Undo menu items. Finally, the static method Clear()
resets the entire framework by emptying both stacks, resetting savePoint
, and, of course, firing the modifiedChanged
event, if appropriate.
OperationBase
was an abstract base class, never to be instantiated. What we need now is a real class that we can use, and this is provided by Operation
, which gives us a generic derivative of OperationBase
:
public class Operation<T1, T2> : OperationBase
The constructor takes four parameters - the name as required by the base class, the OperationMethod<T1, T2>
which is the generic delegate that points to (in C++ parlance) the method to be executed, and two arguments of type T1
and T2
, respectively, that will be passed to the method. Two get
accessors are provided to return the two operands, should this be needed. And finally... the Execute()
method is overridden to do nothing more than call the operation delegate, passing the two parameters.
Why do we need the base class and the derived class? Because of the statics. The C# language definition states that "C# allows you to define static methods that use generic type parameters. However, when invoking such a static method, you need to provide the concrete type for the containing class at the call site" (msdn.microsoft.com/en-us/library/ms379564%28VS.80%29.aspx) [^], so that, if we got rid of OperationBase
and implemented everything in the generic class Operation
, we would no longer be able to call (for example):
Operation.IsModified = false;
because we would have to specify which Operation
we are talking about - i.e., we would need to specify the type parameters. (This seems to me to be a weakness in the language, there doesn't seem to be any reason why the language couldn't support the calling of non-generic static methods that belong to a generic class. Maybe this has been extended in later versions of VS, or maybe there is a fundamental reason why this cannot be allowed which I have missed. I would welcome comments on this issue.)
The Windows application CommandDemo illustrates the use of the framework.
The use of this Command pattern implementation can end up looking a little messy, and at the end of this description, I will discuss some ways that the code can be made more readable. The important requirement is that the various operations are identified and methods provided to Do them and to Undo them. CommandDemo implements a list of names, and operations to add a name and to remove several names. It supports a sorted or unsorted list, and a single- or multiple-selection list. The code is available in CommandDemoForm.cs. The add operation is defined by the DoAdd()
method:
private Operation<string, int> DoAdd(string name, int dummy)
{
int index = namesListBox.Items.Add(name);
return new Operation<string, int>("Add", UndoAdd, name, index);
}
and its corresponding undo method:
private Operation<string, int> UndoAdd(string name, int index)
{
namesListBox.Items.RemoveAt(index);
return new Operation<string, int>("Add", DoAdd, name, 0);
}
DoAdd()
requires only a string to be added to the list, and the second parameter is ignored. UndoAdd()
requires both the string and the position at which it was added - the index tells the method which item is to be removed (we do not assume that the strings in the list are unique), and the name is required for the redo operation. Note the way that each method, after performing its allotted task of adding or removing the item from the list, then instantiates a new Operation
object to be returned. In the case of DoAdd()
, an undo operation is returned, passing the name, the undo method, and the two parameters required by the undo method. Likewise, UndoAdd()
returns a redo operation, which is simply the DoAdd()
method again. Note that the name is always "Add", as discussed previously.
The delete operation caters for multiple selection, and takes a list of indices:
private Operation<List<int>, List<string>>
DoDelete(List<int> indices, int dummy)
{
indices.Sort();
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 new Operation<List<int>,
List<string>>("Delete", UndoDelete, indices, names);
}
As with all arrays, care must be taken when deleting items by index, as each deletion alters the indexing. To avoid this, the index list is sorted, and items are deleted from the end, working backwards. Once again, an undo operation is instantiated and returned. UndoDelete()
requires all the names and all the indices:
private Operation<List<int>, int>
UndoDelete(List<int> indices, List<string> names)
{
for (int x = 0; x < indices.Count; x++)
{
namesListBox.Items.Insert(indices[x], names[x]);
}
return new Operation<List<int>,
int>("Delete", DoDelete, indices, 0);
}
As we know that this operation was generated by DoDelete()
, we also know that the list of indices is already sorted, and because we know that the items were deleted in reverse order, we also know that we can reinsert them on forwards order and they will end up in the right place. (As a diversion, let us imagine for a moment that for some reason the items had been removed in the forwards order - for example, that DoDelete()
had been passed indices 1, 3, and 5. DoDelete()
would have had to remove the item at index 1, whereby the item at index 3 would have moved to index 2, so next item 2 would be removed. Item 5 would now be at index 3, which would be removed last. The designer would now choose to pass either the original indices (1, 3, and 5) or the resultant indices (1, 2, and 3) as the indices to be undeleted, and UnDelete()
would have to make the relevant adjustments. I'll leave that as an exercise for the reader.) As always, UnDelete()
instantiates a redo operation which takes the DoDelete
method as its delegate parameter.
To execute an operation, for example, the add operation, which is executed in the click handler for the Add button, it is only necessary to instantiate an Operation
object of the correct type and call its Do()
method:
private void addButton_Click(object sender, EventArgs e)
{
new Operation<string, int>("Add", DoAdd, newNameTextBox.Text, 0).Do();
newNameTextBox.Text = "";
}
Likewise, to delete (executed when the KeyDown
handler recognises the delete key):
private void namesListBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete)
{
List<int> indices = new List<int>();
foreach (int index in namesListBox.SelectedIndices)
{
indices.Add(index);
}
new Operation<List<int>, int>("Delete", DoDelete, indices, 0).Do();
}
}
To undo the previous operation performed by the click handler of the Undo menu item, we need only call the static Undo()
method:
private void undoToolStripMenuItem_Click(object sender, EventArgs e)
{
OperationBase.Undo();
}
And likewise, to redo the operation performed by the click handler of the Redo menu item:
private void redoToolStripMenuItem_Click(object sender, EventArgs e)
{
OperationBase.Redo();
}
Finally, we can add a data modified marker to the title bar of our application - the standard marker is an asterisk - by adding a handler to the modifiedChanged
event in the form constructor:
OperationBase.modifiedChanged += new CommandEventHandler(OperationBase_modifiedChanged);
The CommandEventArgs
object passed to the handler tells us whether the data is or is not modified:
void OperationBase_modifiedChanged(object sender, CommandEventArgs e)
{
if (OperationBase.IsModified)
{
this.Text += " *";
}
else
{
this.Text = this.Text.Substring(0, this.Text.Length - 2);
}
}
Not forgetting to reset the modified state when the data is saved by the Save menu item handler (which actually saves the list to a text file called CommandDemo.txt):
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
Save();
OperationBase.IsModified = false;
}
Now, these generic types can make code a bit unreadable, so there are a couple of tricks that the designer might want to employ to tidy things up a bit. First of all, she can implement a class factory to instantiate the various Operation
classes. In CommandDemo, the class factory is in the region entitled (unsurprisingly) ClassFactory
, and I have made this selectable by defining, or not, the symbol UseTypeDefinitions
. For example, the class factory method AddOperation()
takes the string to be added, and returns an Add
operation thus:
private Operation<string, int> AddOperation(string name)
{
return new Operation<string, int>("Add", DoAdd, name, 0);
}
so that instead of:
new Operation<string, int>("Add", DoAdd, newNameTextBox.Text, 0).Do();
the add operation can be executed (by the add button click event) as:
AddOperation(newNameTextBox.Text).Do();
The second way that readability can be improved is by using "using
" (for C++ programmers, this is the equivalent of #define
-ing the types). In CommandDemo, this can be turned on by uncommenting the symbol definition UseTypeDefinitions
, and works like this: at the top of the code, within the namespace but outside any class definitions, we define aliases for the generic classes. So for the add operation, we put:
using AddOpType = Operation<string, int>;
The UndoAdd()
method definition then changes from:
private Operation<string, int> UndoAdd(string name, int index)
to:
private AddOpType UndoAdd(string name, int index)
and the class factory method for the add operation described above becomes:
private AddOpType AddOperation(string name)
{
return new Operation<string, int>("Add", DoAdd, name, 0);
}
As always, the source for both the Command implementation and the CommandDemo program are available for download. If you like boring your friends with your holiday snaps, please have a look at Simple Slide Show [^]. If you are interested in African wildlife, have a look at Project African Wilderness [^], and if you like my code, please email me and offer me a job - my leg has healed now, and I am back on the contractor market as a freelance C++/C# software engineer.