This part in the series will focus on the SelectionController.  Here is where most of the work is done.  We've already covered the view interface, IRowSelectionView.  Now we will see how the controller interacts with the view.


Capturing Shift and Ctrl Key Status
We want to emulate multi-selection as in Windows Explorer.  The Shift key is used to select a range of rows, while the Ctrl key is used to toggle the selected status of a single row.  But how do we capture that client-side?  Clearly we need some javascript to help us out.  I use a hidden input control to store the selection for the server round-trip.

The javascript isn't overly complicated.  We call this at the moment a row is clicked.  We capture the status then immediately issue a row command.  By the way, this is not intended to be cross-browser compatible.  I've taken some steps in that direction, but still have a long way to go.

    1 function CustomGridView_GetKeyDownInfo(event)

    2 {

    3     event = event ? event : window.event;

    4     keyCode = '';

    5     if( event.ctrlKey ) {

    6         keyCode = keyCode + 'CTRL';

    7     }

    8     if( event.shiftKey ) {

    9         keyCode = keyCode + 'SHIFT';

   10     }

   11     var theForm = document.forms['aspnetForm'];

   12     if (!theForm) {

   13         theForm = document.aspnetForm;

   14     }

   15     theForm.ctl00_ctl00_hdnKeyDownInfo.value = keyCode;

   16  }

I discovered I also needed to ensure that I only attempted to select a row if the cell or row were being clicked (not any controls inside the cell).

    1 function XcisGridView_IsSenderRowOrCell(event, targetRow)

    2 {

    3     var t = || event.srcElement

    4     return (t == targetRow || t.parentNode == targetRow);

    5 }

Finally to wire it all up, I added an "OnClick" attribute to every data row.  I do this in the IRowSelectionView.RowCreated event handler.  I do a few other things in this handler as you will notice.  First, I modify the RowState and add the Selected flag if the row is currently selected.  The standard GridView only knows how to check for one selected row.  I am maintaining my own list of SelectedIndices that are persisted in ViewState.  Next, I am checking two properties on the view which the developer should use to specify the behavior of the GridView.  SelectOnRowClick determines if clicking a row will result in selecting it.  And, SelectionMode will determine if more than one row can be selected at one time.

    1 void GridView_RowCreated( object sender, GridViewRowEventArgs e )

    2 {

    3     if( SelectedIndices.Contains( e.Row.RowIndex ) )

    4     {

    5         e.Row.RowState = e.Row.RowState | DataControlRowState.Selected;

    6     }


    8     if( _view.SelectOnRowClick && e.Row.RowType == DataControlRowType.DataRow )

    9     {

   10         string captureDownKey = string.Empty;

   11         string rowCommand = "Select$";

   12         if( _view.SelectionMode == ListSelectionMode.Multiple )

   13         {

   14             captureDownKey = "CustomGridView_GetKeyDownInfo(event); ";

   15             rowCommand = "MultiSelect$";

   16         }

   17         e.Row.Attributes["OnClick"] = "if( CustomGridView_IsSenderRowOrCell(event, this) ) { " + captureDownKey + _gridView.Page.ClientScript.GetPostBackEventReference( _gridView, rowCommand + e.Row.RowIndex ) + "}";

   19     }

   20 }

Client-side this looks something like:

<tr class="NormalRow" OnClick="if( CustomGridView_IsSenderRowOrCell(event, this) ) { CustomGridView_GetKeyDownInfo(event); __doPostBack('ctl00$ctl00$EmployeeGridView','MultiSelect$0')}">

Processing the selection
Now that we've got all of the client script in place.  What do we need to do in order to process the row selections.  We are using the GridView's RowCommand where the command is "MultiSelect" and the argument is the row index.  Also, we've got our hands on the status of the various control keys.  Let's see how we use all of this information.

    1 void GridView_RowCommand( object sender, GridViewCommandEventArgs e )

    2 {

    3     if( e.CommandName == "MultiSelect" && MultiSelectEnabled )

    4     {

    5         HandleMultiSelectEvent( int.Parse((string)e.CommandArgument) );

    6     }

    7 }

Well, that was easy.  But what is this HandleMultiSelectEvent?

    1 private void HandleMultiSelectEvent( int rowClickedIndex )

    2 {

    3     switch( _keyDownInfo.Value )

    4     {

    5         case "":

    6             ShiftStartRowIndex = rowClickedIndex;

    7             ClearSelection();

    8             SelectRow( rowClickedIndex );

    9             break;

   10         case "SHIFT":

   11         case "CTRLSHIFT":

   12             foreach( GridViewRow row in _gridView.Rows )

   13             {

   14                 if( row.RowIndex.Between( ShiftStartRowIndex, rowClickedIndex ) )

   15                 {

   16                     SelectRow( row.RowIndex );

   17                 }

   18                 else

   19                 {

   20                     DeselectRow( row.RowIndex );

   21                 }

   22             }

   23             break;

   24         default//case "CTRL":

   25             if( ( _gridView.Rows[rowClickedIndex].RowState & DataControlRowState.Selected ) == DataControlRowState.Selected )

   26             {

   27                 DeselectRow( rowClickedIndex );

   28             }

   29             else

   30             {

   31                 SelectRow( rowClickedIndex );

   32             }

   33             break;

   34     }

   35 }

As you can see, if neither the Shift or Ctrl keys were down at the time the user clicked the row, then we clear all of the selections and select the new row.  If the Shift key was down when the user selected a row, we ignore the Ctrl key status.  I use ShiftStartRowIndex to identify the starting point of a range select using the Shift key.  Finally, with only the Ctrl key pressed, I simply toggle the status of the row that was clicked.  There are a bunch of helper methods here that I will leave up to the imagination.

I've clearly left out quite a lot.  If you look at the IRowSelectionInterface that I discussed last time, you will notice a lot that hasn't been explored.  For the most part, that is because the other pieces are cake compared to handling the row clicks and capturing the key status.  Some key pieces I left out: adding the hidden field to the control hierarchy (as a child of the GridView), handling events that invalidate the selection, caching the selected indices.

Next Time
The final piece to this puzzle is the CustomGridView itself.  We will conclude this series with a brief overview of how the GridView implements IRowSelectionView, and a brief discussion on testing (not exactly TDD, I know).

posted on Tuesday, November 11, 2008 11:52 PM
Filed Under [ .Net Design ASP.Net C# Design Principles ]


# re: Custom GridView Guidelines - Part 3 - SelectionController
posted by LloydJefferies
on 9/9/2009 9:09 AM
Hi cant get selected indices to work says its not available in this context. I done some searching and it is said that it was only available .net1.1 or with windows applications is this true if not how did you get it to implement? Many thanks Great Tutorial by the way. Since reading gave me the confidence to start making my own custom components

Post A Comment