Archive for June, 2007|Monthly archive page

Challenges in implementing the Undo Redo functionality

Here are some of the points to be noted

  • Undo/Redo is an EDIT operation

Operation1 + Undo(on Operation1) = 2 Edit operations.

This will mean that the state of the object(lastModified) is changed even though there is no change in the content.

Hence any mechanism to know whether there are any changes made to the ObjectModel state must be based on the difference in the content and cannot just be based on its lastModified info.

  • Maintaining the edits in a Stack(FirstInLastOut behaviour) will enforce the undo/redo implementation to be sequential in the reverse order in which the operations were performed. It cannot offer the flexibility to offer the undo/redo functionality for the edits which need not correspond to the latest edit operation.
  • Each edit must implement the CanUndo() and CanRedo() to validate the undo/redo operation. This is very essential to ensure that the object model on which edit operation is performed via the undo/redo implementation does not push the underlying object model to an Invalid state.

Each edit might have a cached object instance associated with it when the edit is initialized. When trying to perform the Undo/Redo operation using the cached object, validation is must to ensure that using the cached object doesnt push the underlying object model to an Invalid State, resulting in a total disaster.

  • Each edit must implement Dispose() to ensure that objects cached in the edit are disposed and they don’t eat up the memory.

In addition to all these, for a robust implementation of undo/redo here are some tips

1. Undo Redo for an edit operation on the objectModel should only be dependent on the objects in the objectModel. It should never be dependent on the objects used in the UI which are used for displaying the content of the objectModel.

2.  Always update the object model before updating the UI, so that the UI displayed will always truly represents the object model.

3. The undo/redo implementation will deal with cached instance of the object model but working with caching instances of the UI objects should be avoided and instead the UI objects which represent the object model must be retreived at run time.

Implementing Undo/Redo

Here is a code snippet for implementing the Undo/Redo.

Sample Code

</pre>
//interface defining the edit
 <strong> public interface IUndoableEdit</strong>
 {

//Perform the Undo operation
 bool Undo();

//Perform the Redo operation
 bool Redo();

//validates whether Undo operation can be performed on this edit   //in the current context
 bool CanUndo();

//validates whether Redo operation can be performed on this edit   //in the current context
 bool CanRedo();

void Dispose();

}

//Class providing the infrastructure for manging the IUndoableEdits
 <strong> public class UndoStack</strong>
 {
 //stack is used to store the edits..First In Last Out

private Stack undoStack;
 private Stack redoStack;

public UndoStack()
 {
 //initialize the stack
 redoStack = new Stack(100);
 undoStack = new Stack(100);
 }

public void AddEdit(IUndoableEdit  edit)
 {
 //validate input arguments
 undoStack.Push(edit);
 }

//Clear all the edits stored in the Stack.
 public void ClearAll()
 {
 undoStack.Clear();
 redoStack.Clear();
 }

public void Undo()
 {
 if(undoStack.Count == 0)
 {
 Console.Beep();
 return;
 }

IUndoableEdit edit = undoStack.Pop();

if(edit.CanUndo() && edit.Undo())
 {
 if(edit.CanRedo() )
 {
 redoStack.Push(edit)
 }
 }
 else
 {
 edit.Dispose();
 }

}

public void Redo()
 {
 if(redoStack.Count == 0)
 {
 Console.Beep();
 return;
 }

IUndoableEdit edit = redoStack.Pop();

if(edit.CanRedo() && edit.Redo())
 {
 if(edit.CanUndo() )
 {
 undoStack.Push(edit)
 }
 }
 else
 {
 edit.Dispose();
 }

}

}
<pre>