February 2006 - Posts

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


Posted by smash | 9 comment(s)
Filed under: , ,
the report designer is made up of three elements, a listBox that contains the report controls, a propertyGrid where we edit control properties, and the bandContainer.  Figure1 shows the reportDesigner control at design time.  Figure2 shows the designer at runtime with some reportControls in place.

Figure1
Figure2

most of the interesting stuff occurs in the reportBand control.  it is made up of a verticalRuler, a bandHeader (which is a usercontrol made to  look like a button control) and a drawingSurface.  the drawingSurface is where we drag and drop reportControls.  the verticalRuler allows us to grow or shrink the reportBand.  the bandHeader which is docked at the bottom of the reportBand also allows as to size the band aside from telling us the type of band. 

the reportDesigner follows the MVC architecture pattern.  mouse events in the drawingSurface are routed back to the controller for processing.  the controller then calls appropriate methods in the view object depending on the current state. 

the controller's state can be Normal, Select, Drag, SizeHorizontal and SizeVertical.  the controller goes into the Select state whenever the left mouse button is clicked on a vacant area of the drawingSurface. if a widget is clicked, the controller goes into the Drag state unless the mouse pointer is located on the right edge or the bottom edge of the widget. if the mouse pointer is on the former then the controller goes into the SizeHorizontal state and if it is on the latter then the controller goes into the SizeVertical state. the code below shows how to get this done.
state = DesignState.Select;
widget = context.FindWidget(pt);
if (widget != null)
{
if (!widget.IsSelected)
context.SelectWidget(widget);
state = DesignState.Drag;
if (widget.PointAtRight(pt))
state = DesignState.SizeHorizontal;
else if (widget.PointAtBottom(pt))
state = DesignState.SizeVertical;
}
Posted by smash | with no comments
Filed under: ,