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.