SourceGrid specification and testing: Difference between revisions

From OpenPetra Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 35: Line 35:


<pre>
<pre>
     private bool firstFocusEventHasRun = false;
     private bool FInitialFocusEventCompleted = false;
     private bool isRepeatLeaveEvent = false;
     private bool FNewFocusEvent = false;
     private int gridRowsCount = 0;
     private bool FRepeatLeaveEventDetected = false;
     private int numGridRows    = 0;
     private int FDetailGridRowsCountPrevious = 0;
     private int gridRowsCountHasChanged = 0;
     private int FDetailGridRowsCountCurrent = 0;
     private bool newFocusEventStarted = false;
     private int FDetailGridRowsChangedState = 0;


     private void FocusPreparation(bool AIsLeaveEvent)
     private void FocusPreparation(bool AIsLeaveEvent)
     {
     {
         if (isRepeatLeaveEvent)
         if (FRepeatLeaveEventDetected)
         {
         {
             return;
             return;
         }
         }


         numGridRows = grdDetails.Rows.Count;
         FDetailGridRowsCountCurrent = grdDetails.Rows.Count;


         //first run only
         //first run only
         if (!firstFocusEventHasRun)
         if (!FInitialFocusEventCompleted)
         {
         {
             firstFocusEventHasRun = true;
             FInitialFocusEventCompleted = true;
             gridRowsCount = numGridRows;
             FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
         }
         }


         //Specify if it is a row change, add or delete
         //Specify if it is a row change, add or delete
         if (gridRowsCount == numGridRows)
         if (FDetailGridRowsCountPrevious == FDetailGridRowsCountCurrent)
         {
         {
             gridRowsCountHasChanged = 0;
             FDetailGridRowsChangedState = 0;
         }
         }
         else if (gridRowsCount > numGridRows)
         else if (FDetailGridRowsCountPrevious > FDetailGridRowsCountCurrent)
         {
         {
             gridRowsCount = numGridRows;
             FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
             gridRowsCountHasChanged = -1;
             FDetailGridRowsChangedState = -1;
         }
         }
         else if (gridRowsCount < numGridRows)
         else if (FDetailGridRowsCountPrevious < FDetailGridRowsCountCurrent)
         {
         {
             gridRowsCount = numGridRows;
             FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
             gridRowsCountHasChanged = 1;
             FDetailGridRowsChangedState = 1;
         }
         }


Line 87: Line 87:
         if (grdDetails.Sorting)
         if (grdDetails.Sorting)
         {
         {
             newFocusEventStarted = false;
             FNewFocusEvent = false;
             return;
             return;
         }
         }


         if (newFocusEventStarted == false)
         if (FNewFocusEvent == false)
         {
         {
             newFocusEventStarted = true;
             FNewFocusEvent = true;
         }
         }


         FocusPreparation(true);
         FocusPreparation(true);


         if (!isRepeatLeaveEvent)
         if (!FRepeatLeaveEventDetected)
         {
         {
             isRepeatLeaveEvent = true;
             FRepeatLeaveEventDetected = true;


             if (gridRowsCountHasChanged == -1 || numGridRows == 2)  //do not run validation if cancelling current row
             if (FDetailGridRowsChangedState == -1 || FDetailGridRowsCountCurrent == 2)  //do not run validation if cancelling current row
                                                                     // OR only 1 row present so no rowleaving event possible
                                                                     // OR only 1 row present so no rowleaving event possible
             {
             {
Line 118: Line 118:
         {
         {
             // Reset flag
             // Reset flag
             isRepeatLeaveEvent = false;
             FRepeatLeaveEventDetected = false;
             e.Cancel = true;
             e.Cancel = true;
         }
         }
Line 129: Line 129:
     private void FocusedRowChanged(System.Object sender, SourceGrid.RowEventArgs e)
     private void FocusedRowChanged(System.Object sender, SourceGrid.RowEventArgs e)
     {
     {
         newRecordUnsavedInFocus = false;
         FNewRecordUnsavedInFocus = false;


         isRepeatLeaveEvent = false;
         FRepeatLeaveEventDetected = false;


         if (!grdDetails.Sorting)
         if (!grdDetails.Sorting)
Line 137: Line 137:
             //Sometimes, FocusedRowChanged get called without FocusRowLeaving
             //Sometimes, FocusedRowChanged get called without FocusRowLeaving
             //  so need to handle that
             //  so need to handle that
             if (!newFocusEventStarted)
             if (!FNewFocusEvent)
             {
             {
                 //This implies start of a new event chain without a previous FocusRowLeaving
                 //This implies start of a new event chain without a previous FocusRowLeaving
Line 144: Line 144:


             //Only allow, row change, add or delete, not repeat events from grid changing focus
             //Only allow, row change, add or delete, not repeat events from grid changing focus
             if(e.Row != FCurrentRow && gridRowsCountHasChanged == 0)
             if(e.Row != FCurrentRow && FDetailGridRowsChangedState == 0)
             {
             {
                 // Transfer data from Controls into the DataTable
                 // Transfer data from Controls into the DataTable
Line 157: Line 157:
                 pnlDetails.Enabled = true;
                 pnlDetails.Enabled = true;
             }
             }
             else if (gridRowsCountHasChanged == 1) //Addition
             else if (FDetailGridRowsChangedState == 1) //Addition
             {
             {


             }
             }
             else if (gridRowsCountHasChanged == -1) //Deletion
             else if (FDetailGridRowsChangedState == -1) //Deletion
             {
             {
                 if (numGridRows > 1) //Implies at least one record still left
                 if (FDetailGridRowsCountCurrent > 1) //Implies at least one record still left
                 {
                 {
                    int nextRowToSelect = e.Row;
                    //If last row deleted, subtract row index to select by 1
                    if (nextRowToSelect == FDetailGridRowsCountCurrent)
                    {
                        nextRowToSelect--;
                    }
                     // Select and display the details of the currently selected Row without causing an event
                     // Select and display the details of the currently selected Row without causing an event
                     grdDetails.SelectRowInGrid(e.Row, TSgrdDataGrid.TInvokeGridFocusEventEnum.NoFocusEvent);
                     grdDetails.SelectRowInGrid(nextRowToSelect, TSgrdDataGrid.TInvokeGridFocusEventEnum.NoFocusEvent);
                     FPreviouslySelectedDetailRow = GetSelectedDetailRow();
                     FPreviouslySelectedDetailRow = GetSelectedDetailRow();
                     ShowDetails(FPreviouslySelectedDetailRow);
                     ShowDetails(FPreviouslySelectedDetailRow);
Line 173: Line 179:
                 else
                 else
                 {
                 {
                    e.Row = 0;
                     FPreviouslySelectedDetailRow = null;
                     FPreviouslySelectedDetailRow = null;
                     pnlDetails.Enabled = false;
                     pnlDetails.Enabled = false;
Line 182: Line 189:


         //Event chain tidy-up
         //Event chain tidy-up
         gridRowsCountHasChanged = 0;
         FDetailGridRowsChangedState = 0;
         newFocusEventStarted = false;
         FNewFocusEvent = false;
     }
     }
</pre>
</pre>


The above event ensures the correlation between the FCurrentRow variable and the current row, especially during sorting etc.
The above event ensures the correlation between the FCurrentRow variable and the current row, especially during sorting etc., and it also ensures, on a delete, that the record at the same index position (as determined by sort order) or 1 above is always chosen.


==Adding Rows==
==Adding Rows==
Line 193: Line 200:


<pre>
<pre>
     private bool newRecordUnsavedInFocus = false;
     private bool FNewRecordUnsavedInFocus = false;
 
/// automatically generated, create a new record of {#DETAILTABLE} and display on the edit screen
    /// we create the table locally, no dataset
     public bool CreateNew{#DETAILTABLE}()
     public bool CreateNew{#DETAILTABLE}()
     {
     {
         if(ValidateAllData(true, true))
         if(ValidateAllData(true, true))
         {     
         {     
            int previousGridRow = grdDetails.Selection.ActivePosition.Row;
             {#DETAILTABLE}Row NewRow = FMainDS.{#DETAILTABLE}.NewRowTyped();
             {#DETAILTABLE}Row NewRow = FMainDS.{#DETAILTABLE}.NewRowTyped();
             {#INITNEWROWMANUAL}
             {#INITNEWROWMANUAL}
Line 213: Line 221:


             //Must be set after the FocusRowChanged event is called as it sets this flag to false
             //Must be set after the FocusRowChanged event is called as it sets this flag to false
             newRecordUnsavedInFocus = true;
             FNewRecordUnsavedInFocus = true;


             FPreviouslySelectedDetailRow = GetSelectedDetailRow();
             FPreviouslySelectedDetailRow = GetSelectedDetailRow();
Line 237: Line 245:
                }
                }
            }
            }
    GetDetailsFromControls(FPreviouslySelectedDetailRow);
             }
             }
 
             return true;
             return true;
         }
         }
Line 258: Line 268:
<pre>
<pre>
//The sorting will be affected when a new row is saved, so need to reselect row
//The sorting will be affected when a new row is saved, so need to reselect row
if (newRecordUnsavedInFocus)
if (FNewRecordUnsavedInFocus)
{
{
SelectDetailRowByDataTableIndex(FMainDS.{#DETAILTABLE}.Rows.Count - 1);
SelectDetailRowByDataTableIndex(FMainDS.{#DETAILTABLE}.Rows.Count - 1);
Line 267: Line 277:


'''Deleting Rows'''
'''Deleting Rows'''
The code for deleting rows is yet to be added to the templates, but I have the following code that is used in GL Batch in the manual code file:
The code for deleting rows is now in the templates:


<pre>
<pre>
private void CancelRow(System.Object sender, EventArgs e)
  private void Delete{#DETAILTABLE}()
{
    if (FPreviouslySelectedDetailRow == null)
     {
     {
        return;
bool allowDeletion = true;
    }
bool deletionPerformed = false;
string deletionQuestion = Catalog.GetString("Are you sure you want to delete the current row?");
string completionMessage = string.Empty;
if (FPreviouslySelectedDetailRow == null)
{
return;
}


    int newCurrentRowPos = TFinanceControls.GridCurrentRowIndex(grdDetails);
int rowIndexToDelete = grdDetails.SelectedRowIndex();
{#DETAILTABLETYPE}Row rowToDelete = GetSelectedDetailRow();
{#PREDELETEMANUAL}
if(allowDeletion)
{
        if ((MessageBox.Show(deletionQuestion,
Catalog.GetString("Confirm Delete"),
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes))
{
{#IFDEF DELETEROWMANUAL}
    {#DELETEROWMANUAL}
{#ENDIF DELETEROWMANUAL}
{#IFNDEF DELETEROWMANUAL}
    FPreviouslySelectedDetailRow.Delete();
    deletionPerformed = true;
{#ENDIFN DELETEROWMANUAL}
    FPetraUtilsObject.SetChangedFlag();
    //Select and call the event that doesn't occur automatically
    InvokeFocusedRowChanged(rowIndexToDelete);
}
}


    /*
{#IFDEF POSTDELETEMANUAL}
    Code block here that deals with cancelling the underlying records
{#POSTDELETEMANUAL}
    */
{#ENDIF POSTDELETEMANUAL}
{#IFNDEF POSTDELETEMANUAL}
if(deletionPerformed && completionMessage.Length > 0)
{
MessageBox.Show(completionMessage,
Catalog.GetString("Deletion Completed"));
}
{#ENDIFN POSTDELETEMANUAL}


    //If some row(s) still exist after deletion
    if (grdDetails.Rows.Count > 1)
    {
        //If last row just deleted, select row at old position - 1
        if (newCurrentRowPos == grdDetails.Rows.Count)
        {
            newCurrentRowPos--;
        }
    }
    else
    {
        EnableButtonControl(false);
        ClearDetailControls();
        newCurrentRowPos = 0;
     }
     }
    //Select and call the event that doesn't occur automatically
    InvokeFocusedRowChanged(newCurrentRowPos);
    /*
    additional code
    */
</pre>
</pre>
The above code ensures that the row at the same index position is selected after the deletion occurs and the missing FocusedRowChanged event is fired.
The above code ensures that the row at the same index position is selected after the deletion occurs and the missing FocusedRowChanged event is fired.

Revision as of 09:10, 18 July 2012

The SourceGrid control and its implementation in OpenPetra, has required that modifications be made to its event handling to ensure consistent and predictable event firing in relation to selecting, adding, deleting, sorting and saving rows in the grid. This includes detecting and cancelling repating and unwanted events (mainly FocusRowLeaving event) and invoking events (mainly FocusedRowChanged) when no event is fired (when it should be). Below are the changes that have been introduced to the grid, the grid wrapper and the templates that affect use of the grid throughtout OpenPetra.

Event Handling

The two events that have been the focus of the changes to the grid-related code are FocusRowLeaving (when the currently selected row loses focus) and FocusRowChanged (when a new row receives focus).

Event - Keyboard handling

The grid fires row-level events when the grid itself loses focus even though the current row had not changed. I remmed out two lines of code in the SourceGrid code that related to the user tabbing away or clicking away from the entire grid itself to stop the firing of these events. No other in-grid events are affected. Here are the changes found in GridVirtual.cs:

    protected override void OnValidated(EventArgs e)
    {
        base.OnValidated(e);

	//NOTE: I use OnValidated and not OnLostFocus because is not called when the focus is on another child control
        // (for example an editor control) or OnLeave because before Validating event and so the validation can still be stopped

        if ((Selection.FocusStyle & FocusStyle.RemoveFocusCellOnLeave) == FocusStyle.RemoveFocusCellOnLeave)
	{
		//Changed by CT - 2012-07-03
		//Selection.Focus(Position.Empty, false);
	}

	if ((Selection.FocusStyle & FocusStyle.RemoveSelectionOnLeave) == FocusStyle.RemoveSelectionOnLeave)
	{
		//Changed by CT - 2012-07-03
		//Selection.ResetSelection(true);
	}
    }

The grid now responds to the INS and DEL key presses on the keyboard and attempts a row insert and delete respectively. To do this, it looks for the existence on the form of buttons called btnNew and btnDelete and executes their click event accordingly. To access this functionality, you obviously need the buttons named correctly, but this doesn't stop you changing the label of the button if New or Delete do not best fit the form's context, e.g. the GL Batch form uses buttons with labels Add and Cancel.

Event - FocusRowLeaving

This is the even that fires most often and needs to be curtailed and checked for repeats. Here's the code from the template with supporting methods etc.:

    private bool FInitialFocusEventCompleted = false;
    private bool FNewFocusEvent = false;
    private bool FRepeatLeaveEventDetected = false;
    private int FDetailGridRowsCountPrevious = 0;
    private int FDetailGridRowsCountCurrent = 0;
    private int FDetailGridRowsChangedState = 0;

    private void FocusPreparation(bool AIsLeaveEvent)
    {
        if (FRepeatLeaveEventDetected)
        {
            return;
        }

        FDetailGridRowsCountCurrent = grdDetails.Rows.Count;

        //first run only
        if (!FInitialFocusEventCompleted)
        {
            FInitialFocusEventCompleted = true;
            FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
        }

        //Specify if it is a row change, add or delete
        if (FDetailGridRowsCountPrevious == FDetailGridRowsCountCurrent)
        {
            FDetailGridRowsChangedState = 0;
        }
        else if (FDetailGridRowsCountPrevious > FDetailGridRowsCountCurrent)
        {
            FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
            FDetailGridRowsChangedState = -1;
        }
        else if (FDetailGridRowsCountPrevious < FDetailGridRowsCountCurrent)
        {
            FDetailGridRowsCountPrevious = FDetailGridRowsCountCurrent;
            FDetailGridRowsChangedState = 1;
        }

    }

    private void InvokeFocusedRowChanged(int AGridRowNumber)
    {
        SourceGrid.RowEventArgs rowArgs  = new SourceGrid.RowEventArgs(AGridRowNumber);
        FocusedRowChanged(grdDetails, rowArgs);
    }

    private void FocusRowLeaving(object sender, SourceGrid.RowCancelEventArgs e)
    {
        //Ignore this event if currently sorting
        if (grdDetails.Sorting)
        {
            FNewFocusEvent = false;
            return;
        }

        if (FNewFocusEvent == false)
        {
            FNewFocusEvent = true;
        }

        FocusPreparation(true);

        if (!FRepeatLeaveEventDetected)
        {
            FRepeatLeaveEventDetected = true;

            if (FDetailGridRowsChangedState == -1 || FDetailGridRowsCountCurrent == 2)  //do not run validation if cancelling current row
                                                                    // OR only 1 row present so no rowleaving event possible
            {
                e.Cancel = true;
            }

            Console.WriteLine("FocusRowLeaving");

            if (!ValidateAllData(true, true))
            {
                e.Cancel = true;
            }
        }
        else
        {
            // Reset flag
            FRepeatLeaveEventDetected = false;
            e.Cancel = true;
        }
    }

Basically, the supporting code needs to identify the user action and behave accordingly, this includes selecting, sorting, adding and deleting rows. The InvokeFocusedRowChanged() method allows the calling of the FocusedRowChanged event code when it needs to be called manually.

    private void FocusedRowChanged(System.Object sender, SourceGrid.RowEventArgs e)
    {
        FNewRecordUnsavedInFocus = false;

        FRepeatLeaveEventDetected = false;

        if (!grdDetails.Sorting)
        {
            //Sometimes, FocusedRowChanged get called without FocusRowLeaving
            //  so need to handle that
            if (!FNewFocusEvent)
            {
                //This implies start of a new event chain without a previous FocusRowLeaving
                FocusPreparation(false);
            }

            //Only allow, row change, add or delete, not repeat events from grid changing focus
            if(e.Row != FCurrentRow && FDetailGridRowsChangedState == 0)
            {
                // Transfer data from Controls into the DataTable
                if (FPreviouslySelectedDetailRow != null)
                {
                    GetDetailsFromControls(FPreviouslySelectedDetailRow);
                }

                // Display the details of the currently selected Row
                FPreviouslySelectedDetailRow = GetSelectedDetailRow();
                ShowDetails(FPreviouslySelectedDetailRow);
                pnlDetails.Enabled = true;
            }
            else if (FDetailGridRowsChangedState == 1) //Addition
            {

            }
            else if (FDetailGridRowsChangedState == -1) //Deletion
            {
                if (FDetailGridRowsCountCurrent > 1) //Implies at least one record still left
                {
                    int nextRowToSelect = e.Row;
                    //If last row deleted, subtract row index to select by 1
                    if (nextRowToSelect == FDetailGridRowsCountCurrent)
                    {
                        nextRowToSelect--;
                    }
                    // Select and display the details of the currently selected Row without causing an event
                    grdDetails.SelectRowInGrid(nextRowToSelect, TSgrdDataGrid.TInvokeGridFocusEventEnum.NoFocusEvent);
                    FPreviouslySelectedDetailRow = GetSelectedDetailRow();
                    ShowDetails(FPreviouslySelectedDetailRow);
                    pnlDetails.Enabled = true;
                }
                else
                {
                    e.Row = 0;
                    FPreviouslySelectedDetailRow = null;
                    pnlDetails.Enabled = false;
                }
            }
        }

        FCurrentRow = e.Row;

        //Event chain tidy-up
        FDetailGridRowsChangedState = 0;
        FNewFocusEvent = false;
    }

The above event ensures the correlation between the FCurrentRow variable and the current row, especially during sorting etc., and it also ensures, on a delete, that the record at the same index position (as determined by sort order) or 1 above is always chosen.

Adding Rows

Where the form templates contain code for adding records to the grid, changes have had to be made to control event firing. Here is an example:

    private bool FNewRecordUnsavedInFocus = false;
	
	/// automatically generated, create a new record of {#DETAILTABLE} and display on the edit screen
    /// we create the table locally, no dataset
    public bool CreateNew{#DETAILTABLE}()
    {
        if(ValidateAllData(true, true))
        {    
            {#DETAILTABLE}Row NewRow = FMainDS.{#DETAILTABLE}.NewRowTyped();
            {#INITNEWROWMANUAL}
            FMainDS.{#DETAILTABLE}.Rows.Add(NewRow);
            
            FPetraUtilsObject.SetChangedFlag();

			grdDetails.DataSource = null;
            grdDetails.DataSource = new DevAge.ComponentModel.BoundDataView(FMainDS.{#DETAILTABLE}.DefaultView);
            
			SelectDetailRowByDataTableIndex(FMainDS.{#DETAILTABLE}.Rows.Count - 1);
            InvokeFocusedRowChanged(grdDetails.SelectedRowIndex());

            //Must be set after the FocusRowChanged event is called as it sets this flag to false
            FNewRecordUnsavedInFocus = true;

            FPreviouslySelectedDetailRow = GetSelectedDetailRow();
            ShowDetails(FPreviouslySelectedDetailRow);
			
            Control[] pnl = this.Controls.Find("pnlDetails", true);
            if (pnl.Length > 0)
            {
	            //Look for Key & Description fields
	            bool keyFieldFound = false;
	            foreach (Control detailsCtrl in pnl[0].Controls)
	            {
	                if (!keyFieldFound && (detailsCtrl is TextBox || detailsCtrl is ComboBox))
	                {
	                    keyFieldFound = true;
	                    detailsCtrl.Focus();
	                }
	
	                if (detailsCtrl is TextBox && detailsCtrl.Name.Contains("Descr") && detailsCtrl.Text == string.Empty)
	                {
	                    detailsCtrl.Text = "PLEASE ENTER DESCRIPTION";
	                    break;
	                }
	            }

		    GetDetailsFromControls(FPreviouslySelectedDetailRow);
            }
			
            return true;
        }
        else
        {
            return false;
        }
    }

You will notice that the grid's DataSource needed to be set to Null before being reset. This ensured removal of focus from a cell on the previous row. The code also invokes the FocusedRowChanged event as well as sending focus to the new row irrespective of sorting and then gives focus to the key value field and populates the description field if it exists.

Also in another template method: SelectDetailRowByDataTableIndex(), the last line is changed that calls a new signature method in the wrapper that selects the specified row but without firing any events. The enumerator allows you to specify the event you would like to fire.

grdDetails.SelectRowInGrid(RowNumberGrid, TSgrdDataGrid.TInvokeGridFocusEventEnum.NoFocusEvent);

Saving Rows

The main issue with saving was ensuring that the sorting was correct after changing the key field. To do this the following code was added to the save event in the template:

//The sorting will be affected when a new row is saved, so need to reselect row
if (FNewRecordUnsavedInFocus)
{
	SelectDetailRowByDataTableIndex(FMainDS.{#DETAILTABLE}.Rows.Count - 1);
	InvokeFocusedRowChanged(grdDetails.SelectedRowIndex());
}

Even though the FocusRowChanged event should not be needed, it is used to display the record (which may have moved due to sorting) and keep the FCurrent variable up to date.

Deleting Rows The code for deleting rows is now in the templates:

   private void Delete{#DETAILTABLE}()
    {
		bool allowDeletion = true;
		bool deletionPerformed = false;
		string deletionQuestion = Catalog.GetString("Are you sure you want to delete the current row?");
		string completionMessage = string.Empty;
		
		if (FPreviouslySelectedDetailRow == null)
		{
			return;
		}

		int rowIndexToDelete = grdDetails.SelectedRowIndex();
		{#DETAILTABLETYPE}Row rowToDelete = GetSelectedDetailRow();
		
		{#PREDELETEMANUAL}
		
		if(allowDeletion)
		{
        	if ((MessageBox.Show(deletionQuestion,
					 Catalog.GetString("Confirm Delete"),
                     MessageBoxButtons.YesNo,
                     MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.Yes))
			{
{#IFDEF DELETEROWMANUAL}
			    {#DELETEROWMANUAL}
{#ENDIF DELETEROWMANUAL}
{#IFNDEF DELETEROWMANUAL}				
			    FPreviouslySelectedDetailRow.Delete();
			    deletionPerformed = true;
{#ENDIFN DELETEROWMANUAL}				
			
			    FPetraUtilsObject.SetChangedFlag();
			    //Select and call the event that doesn't occur automatically
			    InvokeFocusedRowChanged(rowIndexToDelete);
			}
		}

{#IFDEF POSTDELETEMANUAL}
		{#POSTDELETEMANUAL}
{#ENDIF POSTDELETEMANUAL}
{#IFNDEF POSTDELETEMANUAL}
		if(deletionPerformed && completionMessage.Length > 0)
		{
			MessageBox.Show(completionMessage,
					 Catalog.GetString("Deletion Completed"));
		}
{#ENDIFN POSTDELETEMANUAL}

    }

The above code ensures that the row at the same index position is selected after the deletion occurs and the missing FocusedRowChanged event is fired.

When we do come to add autogenerated DeleteCurrent{#DETAILTABLE}() code to the templates, we will need the above code to ensure correct grid row behaviour, as well as a call to DeleteRowManual() in the manual code file and the ability to offer a dioalog box to confirm deletion and to supply a meaningful message.

Grid Testing

Testing needs to be setup where we can test the grid on all its basic functions in forms derived from each of the templates.

Currently, all code generated from the templates compiles and a number of basic maintain screens have been tested for correct behaviour, but a smaller stand-alone application will also be needed to test the behaviour in isolation.