24 July 2008

How To: Create command and undo/redo it

I hope you have read previous posts where we declare undoable data members.
Now, lets do our first command:



public void DoCalculations(MyClass c, int i)
{
UndoRedoManager.Start("Change count");
c.Count = i * c.Count;
UndoRedoManager.Commit();
}


In this sample we changed only one property but real command can contain any number of changes of any properies of any classes. All these changes will be commited with Commit() method. If command was unsuccessful for some reason, the method Cancel() should be used instead. In this case all the changed data will rollback at the exactly same state it was at Start point.



public void DoCalculations(MyClass c, int i)
{
UndoRedoManager.Start("Change count");
c.Count = i * c.Count;
if (c.Count < 1000)
UndoRedoManager.Commit();
else
UndoRedoManager.Cancel();
}


As I noted words "commit" and "rollback", you probably think that DejaVu is one more transactional engine. Not so. The real difference is that history of commands is available all the time and we can undo them at any depth and redo some of them again.

It is also vitaly important to understand that your logic does not run any code again when a command is redone. The data state is solely returned into previous state. So, your business logic need know nothing about undo/redo activities.


How does undo/redo look like in the code? "Here We Have Idaho":



...
UndoRedoManager.Undo();
...
UndoRedoManager.Redo();


I hope you find whole this stuff simple. Meanwhile, I would like to return to the very first sample in this post. The thing is that... I do not know how to say you...
It's... It's not very reliable... The problem is that if we fail inside the command by exception, the command will be never commited or canceled. But no worries. The code snippet below shows the best practice how to deal with commands in most cases:


using (UndoRedoManager.Start())
{
...
UndoRedoManager.Commit();
}

This sample undoes last command and then brings all changes back.


Now, if exception is thrown inside the command, it will be automatically rolled back at the closing bracked of using statement. On other hand, if command successfully comes to end, it will be commited.

My practice shows that its a very reliable way to build fail proof application. Data state is safeguarded against falling into incorrect state. After command completed or unpredictably failed, data is always in correct state as it designed.

2 comments:

Anonymous said...

I want to use this library for a user control which is customizable at run-time. User can change the control's location, background color etc. Now - Location and BackColor property are NOT defined in my class which is derived from UserControl, so how can I make these properties undoable using 'DejaVu'? I was thinking to create an undoable property in my class (e.g. ControlPosition for Location and Color for BackColor) which internally will change the actual base class properties (Location & BackColor). Will this work?shimsto

Anonymous said...

How would you use the UndoRedoManager across multiple project? I have an object being pushed onto undo list in one project (a control library) and undo feature is implemented in another (front end)

Thanks