
Undo/redo functionality can be implemented using a combination of the Command and Memento design patterns (see figure above). The idea is to ask an object (the originator) for a “memento” of itself which contains all the information necessary for that object to later restore itself to its initial state. That memento is then associated with a MemorableUndoableCommand, which in turn is inserted in the CommandHistory. By calling CommandHistory’s Undo and Redo methods, the UndoableCommands in the history can be Executed and Unexecuted. Calling Execute and Unexecute on commands results in the saved mementos being set in the IMemorable objects, thus restoring their state and providing the desired undo and redo functionality.
For situations where you want a single, atomic, undoable operation that really consists of executing and unexecuting a sequence of well-defined UndoableCommands, you can use the CompositeUndoableCommand. In order to maintain the correct ordering of operations as if you had inserted the individual operations directly into the CommandHistory, the queued UndoableCommands are unexecuted in the reverse order that they are executed in. This is useful for commands like a "delete all" operation, where undoing it could require inserting multiple objects in a single undo step.
When implementing undo/redo using this pattern, there are a few things to be aware of:
| 1. | When you think you’ve removed all references to the originator (i.e. the object that implements IMemorable) in your own code in an effort to allow the garbage collector to do its work, remember they may not be. If you’ve implemented undo/redo in the way described above, you will still have a reference to the originator in the UndoableCommand, which is in the CommandHistory, which doesn’t die until the IVC is closed. If the originator is lightweight, this may not be a big deal. But if it holds onto a lot of resources, this could pose memory management problems. |
| 2. | If the operation you want to make undoable/redoable involves the creation of a new originator, the pattern above may not work properly. Here’s why: Let’s say you create an UndoableCommand that instantiates a new ROI graphic that acts as the originator. The command is then placed in the CommandHistory. If the user then resizes the ROI, you remember that action by creating another UndoableCommand and placing it in the CommandHistory as well. Now, consider what happens when the user clicks undo twice, then redo once: The resize operation is undone, the create operation is undone, then the create operation is redone. If the user clicks redo one more time—expecting the resize operation to be redone—he will discover that nothing happens. This is because the instance of the ROI Graphic in the CommandHistory is different from the one that was just re-created. The way to solve this is not to create a new object the second time, but to simply re-insert the original. |
| 3. | Under certain circumstances, when you undo n commands on an object, then redo those same n commands, the state of the object may not be the same as when you started. This phenomenon is known as hysteresis and is always a concern when implementing undo/redo. |
In addition to these fundamental classes, the viewer provides some more that make applying the same operation to multiple objects of the same type even easier. Consider the PanTool that allows the user to move the currently selected image around in the containing Tile. Once the user releases the mouse, all the other images in the same display set move to the same location as the currently selected one. Now imagine the work involved in figuring out how to make this entire operation (i.e. panning an entire display set) undoable, not to mention how to ensure that it can be done in a consistent way for all similarly functioning tools, of which there are quite a few. Enter IUndoableOperation<T> , UndoableOperationCommand, DrawableUndoableOperationCommand<T> and ImageOperationApplicator!

These all sound pretty complicated, right? Not really. Consider the interface of IUndoableOperation. It essentially outlines the predictable pattern of applying the same undoable operation to an arbitrary object. First, you check if the operation applies to the object. Next, you get the originator for the object and get a memento from it — call this the 'begin state'. Lastly, you apply the operation to the object and get another memento from the originator — the 'end state'. You can then construct a MemorableUndoableCommand as described earlier. Note that, in order for IUndoableOperation to work, the originator must either be the object itself, or some property of the object that implements IMemorable. So how does this help make Undo/Redo any easier? It doesn't on it's own, but when used with another class such as UndoableOperationCommand or ImageOperationApplicator, it does. The following is a quick summary of what these classes do:
| • | UndoableOperationCommand: takes an IUndoableOperation and a list of objects to apply it to. Make sure to call Execute on the command in order to actually apply the IUndoableOperation before adding the UndoableOperationCommand to the CommandHistory. Internally, when the IUndoableOperation is first applied, UndoableOperationCommand just adds MemorableUndoableCommands to it's own internal list. DrawableUndoableOperationCommand is simply a specialization of this class for objects that implement IDrawable, like IPresentationImage; it handles redrawing the objects not only after the IUndoableOperation is first applied, but also when the operation is undone/redone from within the CommandHistory. |
| • | ImageOperationApplicator: internally uses DrawableUndoableOperationCommand, but it will automatically figure out which images to apply the IUndoableOperation to based on the reference IPresentationImage you pass to its constructor. This class is indispensable when writing tools such as the ZoomTool, where an operation is applied to a single image, and then all the others in the same display set need to be updated. |