implementing undo and redo
for a command to be undone, we need to store it somewhere.  to facilitate this, we need to create a class for every command and we also need to create a class that will process and then store these commands upon request. 
interface ICommand
{
void Execute();
void UnExecute();
}

the code above shows the ICommand interface which is inherited by every command class we implement.  below is an example of how we can implement the AlignTopCommand.
class AlignTopCommand : ICommand
{
Controller controller;
bool firstPass = true;

public AlignTopCommand(Controller controller)
{
this.controller = controller;
}

public void Execute()
{
if (firstPass)
{
// remember original position of the controls
firstPass = false;
}
// align top of the controls here
controller.ModelChange(this, null);
}

public void UnExecute()
{
// rollback the controls to their original position here
controller.ModelChange(this, null);
}
}

whenever the user clicks on the Align-to-Top button in the designer's toolbar, we execute the above command by feeding its instance to the  CommandService object property of the controller object as shown below. 
private void aligntopToolStripButton_Click(object sender, EventArgs e)
{
controller.CommandService.Do(new AlignTopCommand(controller));
}

private void undoToolStripButton_Click(object sender, EventArgs e)
{
controller.CommandService.Undo();
}

private void redoToolStripButton_Click(object sender, EventArgs e)
{
controller.CommandService.Redo();
}
the CommandService property of the Controller class is an instance of the CommandService class whose job is to store ICommand objects so we can Undo/Redo them upon requests by the user.  shown below is the implementation of the CommandService class.
class CommandService
{
const int MAXCOMMANDS = 50;

List history;
int index = 0;

public EventHandler CommandHistoryChange;

public CommandService()
{
history = new List(MAXCOMMANDS);
}

~CommandService()
{
history.Clear();
history = null;
}

public bool CanRedo()
{
return index < history.Count;
}

public bool CanUndo()
{
return index > 0;
}

public void Do(ICommand command)
{
command.Execute();
if (index < history.Count)
{
history.RemoveRange(index, history.Count - index);
}
if (history.Count == MAXCOMMANDS)
{
history.RemoveAt(0);
}
history.Add(command);
index = history.Count;
CommandHistoryChange(this, null);
}

public void Undo()
{
history[--index].UnExecute();
CommandHistoryChange(this, null);
}

public void Redo()
{
history[index++].Execute();
CommandHistoryChange(this, null);
}
}

below are some screenshots i took while testing the report designer's undo mechanism. Figure1 shows two label controls.  Figure2 shows the controls after clicking the Align-to-Top button in the toolbar.  Figure3 shows the two controls after the Undo button in the toolbar was pressed.  note that the Redo button has already been enabled. 

Figure1
Figure2
Figure3


Published 02-13-2006 6:10 AM by smash
Filed under: , ,

Comments

# re: implementing undo and redo@ Monday, February 13, 2006 4:25 PM

have you seen the n-undo capability of business objects in csla? what's your reaction with it?

by jokiz

# re: implementing undo and redo@ Monday, February 13, 2006 9:58 PM

hi jokiz, i havent read Mr. Lhotka's book so i cant make any comments regarding CSLA or parts of it. but it does sound very interesting :D

by smash

# re: implementing undo and redo@ Tuesday, February 14, 2006 2:42 AM

you can try downloading the source, i'm sure you'll be able to see how it is done

by jokiz

# re: implementing undo and redo@ Tuesday, February 14, 2006 12:17 PM

ok i'll try to do just that. btw, do you have any experience using CSLA in your past projects? how was it?

by smash

# re: implementing undo and redo@ Tuesday, February 14, 2006 2:14 PM

if i read it right, the UndoableBase class in CSLA uses a stack to store snapshots of an object's undoable fields which it wraps in a HybridDictionary. the object state which is packed in the HybridDictionary is serialized before being pushed into the stack. i think it is a pretty good implementation of undoability for business objects. :D

by smash

# re: implementing undo and redo@ Wednesday, February 15, 2006 2:15 PM

i think i may have a problem with Mr. Lhotka's approach in stacking copies of a business object within that same object. it just doesnt feel right since it made the base business class seemingly complex. i believe in the notion that OOP should promote simplicity in the base level rather than the opposite.



by smash

# re: implementing undo and redo@ Wednesday, February 22, 2006 11:03 PM

There is nothing wrong with having the undoable codes in the base class since you want every child class to have that functionality. it is OOP and i haven't heard of OOP promoting simplicity in the base level...

by jokiz

# re: implementing undo and redo@ Thursday, February 23, 2006 2:08 PM

hi jokiz. what i'm saying is that i'm not comfortable with Mr. Lhotka's approach. regarding my observation about OOP which you seem to view with utmost derision, i'm quite surprised that you heard it first from me. i guess simple ideas doesnt sell too many books.

by smash

# re: implementing undo and redo@ Saturday, February 25, 2006 10:22 PM

how about posting some links for that OOP info...

by jokiz