|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionA useful application normally has a conventional feature called "undo" or "undo-redo". It is extremely handy and I can't imagine a cyber world without undo-redo. With that said, it is not easy to implement undo-redo features. Convenience for users and headache for developers usually refer to the same thing. To my knowledge, there are no .NET libraries that have enough capability to handle all undo-redo cases because this feature is very specific to every single application. The purpose of this article is to generalize undo-redo as much as possible and to suggest implementation methodology. BackgroundThere is a well known design pattern called The interface IMemento
{
object State
{
get;
}
}
The above An interface IMemento<T>
{
/// <summary>
/// Restores target to the state memorized by this memento.
/// </summary>
/// <returns>
/// A memento of the state before restoring
/// </returns>
IMemento<T> Restore(T target);
}
Note that the This simple The /// <summary>
/// Restores the subject to the previous state on the undo stack,
/// and stores the state before undoing to redo stack.
/// </summary>
public void Undo()
{
inUndoRedo = true;
IMemento<T> top = undoStack.Pop();
redoStack.Push(top.Restore(subject));
inUndoRedo = false;
}
The /// <summary>
/// Restores the subject to the next state on the redo stack,
/// and stores the state before redoing to undo stack.
/// </summary>
public void Redo()
{
inUndoRedo = true;
IMemento<T> top = redoStack.Pop();
undoStack.Push(top.Restore(subject));
inUndoRedo = false;
}
The /// <summary>
/// Registers a memento with the history, which can be undone.
/// </summary>
public void Do(IMemento<T> m)
{
if (inUndoRedo)
throw new InvalidOperationException(
"Involking do within an undo/redo action.");
redoStack.Clear();
undoStack.Push(m);
}
Using the codeNow that the undo-redo history is in place, the only thing left is to write concrete implementations of There are two demo projects written to demonstrate the power of this pattern. They have the same functions and user interfaces. Their difference lies in how undo and redo are implemented. I named them "action based undo-redo" and "serialization based undo-redo" respectively. Action Based Undo-RedoEvery change made to the tracked object is stored in one memento. Mementos are capable of restoring states by carrying out the inverse action. history.Do(new AddShapeMemento()); // action: add shape
shapepool.Add(GetShape(e));
history.BeginCompoundDo(); //starts a compound memento
foreach (Shape s in toBeRemoved)
{
int index = shapepool.IndexOf(s);
history.Do(new RemoveShapeMemento(index, s)); //action: remove shape
shapepool.RemoveAt(index);
}
history.EndCompoundDo(); //ends a compound memento
The mechanism is very efficient in memory usage. However, the mementos within the undo-redo history are inter-dependent. If one is corrupted, the entire history gets corrupted. What's worse, it is very tedious to implement, as each single action corresponds to its own Serialization Based Undo-RedoThe tracking system doesn't care about what changes are made to the tracked object. Whenever there is a change, it stores the complete state of the object by serializing it. history.Do(new ShapePoolMemento(shapepool)); //store entire shape pool
shapepool.Add(GetShape(e));
history.Do(new ShapePoolMemento(shapepool)); //store entire shape pool
foreach (Shape s in toBeRemoved)
{
int index = shapepool.IndexOf(s);
shapepool.RemoveAt(index);
}
As opposed to action based undo-redo, this method is very easy to implement and the mementos are independent of each other. But it has a fatal pitfall - huge memory consumption, if the tracked object is large. As a result, poor time performance will be observed. Hybrid Undo-Redo (Conceptual)As you see in the previous examples, the Although I'm trying to list as many implementation methods as possible, I won't be surprised if someone comes up and tells me that there is a totally new and much better way of implementing the Points of InterestCompound MementoYou might have already noticed the two strange methods A public class CompoundMemento<T> : IMemento<T>
{
// ...
private List
Controlling CapacityThe size of the undo and redo stacks are usually constrained. Unfortunately History
|
||||||||||||||||||||||