Objective Grid : PART I User’s Guide : Chapter 5 Using Objective Grid : Undo and Redo
Undo and Redo
Objective Grid provides a powerful mechanism that lets the end user undo and redo a series of commands. Objective Grid maintains Undo information for all previous commands in an Undo list and Redo information for all previously undone commands in a Redo list. The number of commands or Undo stack size can be specified in the parameter object.
The end user can undo and redo commands with the Edit|Undo and Edit|Redo menu commands and their associated shortcuts. This mechanism is automatically provided by Objective Grid. No additional programming is required to enable this feature.
NOTE >> As you populate your grid, Objective Grid may be generating and maintaining Undo information which allows these actions to be undone by the user. Generating and maintaining this Undo information can be time consuming. If you do not want your user to be able to undo the work you are doing to populate the grid, then you should disable the grid Undo mechanism. To do so, call GetParam()->EnableUndo(FALSE); before the code you use to populate the grid. If you want the enable the grid's Undo support after you grid has been populated, call GetParam()-> EnableUndo(TRUE); after your grid has been populated.
CGXGridCore - Undo/Redo interface
The CGXGridCore class provides the following methods for undoing and redoing commands.
BeginTrans()
Marks the beginning of a transaction-like block of commands. This block will be undone, redone, and rolled back in one step.
CommitTrans()
Marks the end of a transaction
Redo()
Redoes the previously undone command.
Rollback()
Undoes all commands since the start of the transaction without generating Redo information.
Undo()
Undoes the previous command.
Transaction-Like Blocks of Commands
There are situations where you would like the user to be able to abort, undo or redo several changes in the grid with one Undo or Redo command. For example, if the user executed a “Replace All” command and later wants to undo this command, all resulting changes should be undone at once.
The key is calling BeginTrans() before the series of commands and CommitTrans() when the series is finished.
 
void CGridSampleView::CommandBlock()
{
BeginTrans( "Block of commands" );
 
// These commands can only be undone all at once
SetStyleRange(CGXRange(1,1), style1);
SetStyleRange(CGXRange(1,2), style2);
SetStyleRange(CGXRange(1,3), style3);
CommitTrans();
}
Long Operations
In Windows programming, it is good practice to update the end user with the progress of particularly long commands. Additionally, you should give the end user the option to abort or even rollback the entire operation.
The CGXLongOperation class implements a mechanism exactly for this purpose and goes one step further. CGXLongOperation allows you to implement this mechanism in any method. It also ensures that when an operation is executed in a short amount of time, no further overhead is added. CGXLongOperation starts only after a delay after a specified amount of time has passed. If the operation completes before the wait time is over, no indication will be given to the end user.
CGXLongOperation also maintains an operation level as a static class variable. Every time you create a CGXLongOperation object in your code, the operation level will be increased. If the destructor for the object is called, the operation level will be decreased. This makes it possible to nest operations.
The following example explains how you can use CGXLongOperation in your application:
 
void operation1()
{
// At first, create a CGXLongOperation object
CGXLongOperation theOp;
// Assign a status text. This text will be displayed only
// if the operation takes longer than a specific amount of
// ticks.
theOp.SetStatusText("Operation1...", FALSE);
// In many operations you have a loop. In this loop, you
// should call NeedMessages frequently. NeedMessages will
// return TRUE if the operation runs for a specific amount
// of time. Furthermore, you could add TRY/CATCH statements.
// They make it easy to cleanup when the user wants to abort
// the operation.
TRY
{
BOOL bAbort = FALSE;
while (bStatementsToProcess)
{
// Statements to be processed
// ...
 
// check, if user pressed ESC to cancel
if (theOp.NeedMessages())
{
// if theOp.NeedMessages is TRUE the first time,
// theOp.DoMessages will display a wait cursor
theOp.SetPercentDone(nPercentDone);
theOp.DoMessages(bAbort);
 
if (bAbort)
AfxThrowUserException();
}
}
// operation executed successfully
// cleanup
}
CATCH(CUserException, e)
{
if (theOp.GetRollbackConfirmedState())
{
// user did select "Retry" in Abort-dialog box
// So, try to undo already done changes and
// cleanup
}
if (theOp.GetAbortConfirmedState())
{
// user did select "Abort" in the Abort-dialog box
// So, abort the operation and
// cleanup
}
}
END_CATCH
}
You can call this method from another method:
 
void Execute()
{
CGXLongOperation theOp;
theOp.SetStatusText("Executing ...", FALSE);
 
// The following call will lock the current operation level.
// This means that when calling operation1(), this method
// cannot change the status text. The call to
// theOp.SetStatusText("Operation1...", FALSE);
// will have no effect.
 
theOp.SetLockedState(TRUE);
TRY
{
BOOL bAbort = FALSE;
while (bStatementsToProcess)
{
Operation1();
// if user aborted Operation1(), also
// this method will be aborted.
 
// check, if user pressed ESC to cancel
if (theOp.NeedMessages())
{
theOp.SetPercentDone(nPercentDone);
theOp.DoMessages(bAbort);
 
if (bAbort)
AfxThrowUserException();
}
}
// operation executed successfully
// cleanup
}
CATCH(CUserException, e)
{
if (theOp.GetRollbackConfirmedState())
{
// user did select "Retry" in Abort-dialog box
// So, try to undo already done changes and
// cleanup
}
if (theOp.GetAbortConfirmedState())
{
// user did select "Abort" in the Abort-dialog box
// So, abort the operation and
// cleanup
}
}
END_CATCH
}
You can change the number of ticks (amount of time) necessary before NeedMessages() will return TRUE by calling the static CGXLongOperation member functions SetTicksFirstTime() and SetTicksContinued().
If you pass LONG_MAX to SetTicksFirstTime(), the CGXLongOperation mechanism will be completely disabled.