November 2008 Entries

Many of those who know me (probably my only blog readers) know that I started a new job at the beginning of the year.  At that time, I started working from home full-time, with the occasional trip to the client site.

Working from home had many positives and a few negatives.  On the positive side, I saved a lot on gas, I have been able to spend a lot more time with my family, and I've been able to take care of things at the house that I would normally have to take time off for.  At times, my focus was really improved because the distractions of an office environment were practically nonexistent.   At others (especially during the summer when the kids are out of school), it was difficult to focus on my work.  Other drawbacks include working long and strange ours (it has been hard for me to "leave" the office") and the lack of collaboration.  I've stated before that I feel pretty strongly about how a team can deliver a higher quality product than a single individual.

Well, yesterday we moved our small company (four people) into our new office.  Our office is actually a one bedroom apartment.  We are using a closet for our server room and the bedroom as our conference room.  The living area is where we have all of our desks set up.  We signed a 6 month lease to give us the opportunity to see how well the arrangement works out.

In the coming weeks I'll be able to form some new opinions about working from an office versus working from home.  I've already negotiated with my boss to work from home one day out of every two weeks.  He and I will alternate.  I think that will allow me to keep some of the benefits of working from home.  We'll see.

Actually I am excited about this change.  I think it will help the four of us build some camaraderie.  I'll still be able to see a lot of my family because my commute is only 15-20 minutes (I live in Houston where the average commute is probably an hour and a half).  And, I'll be able to put work aside more easily (I hope).

So I was hoping this post would be a glorious review of my first attempt at working with WebParts and Oracle.  Alas, the experience has not been so glorious.

I am working on a "dashboard" of sorts as a demo for my boss.  I spent a few hours on it this afternoon as a spike to see if the approach is feasible.  I have to say that I believe it is, but there is one major kink I have to work out.

ASP.Net WebParts uses the SqlPersonalizationProvider by default.  Unfortunately, we are developing on Oracle.  However, Oracle has provided a kit of providers including Membership, Role, and Personalization (among others).  It was a simple matter of running a few database scripts, including the library in web application, and configuring to use the Oracle providers.

All seemed well.  The WebPartManager recognized the authenticated user's rights appropriately.  At run-time, I could put my test page in "Design" mode and move the various parts around. 

The glitch is that the changes are not persisting.  From one postback to the next, any of the previous changes are getting lost.  For some reason, the personalization isn't getting persisted.  I spent a couple of hours with the MSDN docs and Google, but couldn't find a solution.

As it is very late, I am hoping that I'm just doing something stupid (or stupidly not doing something) and when I wake up in the morning, I will fall upon the answer.

Hopefully, I will post a follow-up very soon with the resolution.

I encountered a repeatable crash in VS2008 for the first time today.

I was working on a C# file in the editor.  SP1 added some design time hints on syntax or potential compile time problems.  The code I was working on had a few red squiggly lines letting me know that I should correct something.

Well, when I tried to edit one particular line, VS would freeze on me then throw a nice "Sorry, I'm crashing" error dialog and then close.  VS has crashed on me before, but that has typically been because something else was unstable on my system.  So, after restarting VS, it crashed in exactly the same place.  I even resorted to rebooting my box, and still there was no help.

Time and time again, VS would crash as soon as I placed the cursor on that particular line of code.  I have no idea what was so special about that line.  It was fairly short and was just a simple property assignment.

Eventually, I resorted to editing the file as much as I could in TextPad, saving, then trying the updated file in VS.  Finally VS stopped crashing on me.

Whenever the error dialog would popup, I would click the "send error" button, but no information would come back.  I just hope that someone is reading the error info and might have a clue as to the cause.

Since the event, I started investigating what the cause might have been.  I searched the web for VS2008 SP1 bugs but didn't have any luck.  I did find a couple of other blogs that had VS problems and learned a few things.  First, I will probably use Microsoft Connect the next time I encounter a problem.  Second, they have Debugging Tools you can download that will prepare a dump file they can use to investigate the problem. 

You can get the dump file with the following steps:

1. Download the Debugging Tools and install. Assume that you copy it to
“C:\Debuggers”.

2. Open a command line prompt. Close all unnecessary applications.

3. Try to reproduce the problem.

4. Before VS IDE crashes , execute the following command at the command line prompt:

cscript C:\Debuggers\adplus.vbs -crash -FullOnFirst -pn devenv.exe –o C:\Dump

Dump file will be generated at “C:\Dump” and it usually takes several hundred MB. Make sure that you have sufficient disk space.

Also, I learned that you can find some "fault" information in the Application event log.  Though, as you can see, it's not very helpful to me, but it could help the next time I need to report a problem.

e.g.:
Faulting application devenv.exe, version 9.0.30729.1, stamp 488f2b50, faulting module kernel32.dll, version 5.1.2600.5512, stamp 4802a12c, debug? 0, fault address 0x00012aeb.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.



If only I could turn the clock back a few hours and reproduce the problem.  I am glad that I am not experiencing the problem now, but I've lost an opportunity to help improve Visual Studio.

Introduction
Last time we spent a lot of time on the SelectionController.  To conclude the series, we will hook the selection behavior into our CustomGridView.

Recap:

Implementing the IRowSelectionView
There are quite a few events on the view that the CustomGridView needs to implement.  In addition to the event are a couple of behavior properties.

The properties are pretty simple.  I want them to be published, so I have identified them to be placed in the "Behavior" category:

    1 [Category( "Behavior" )]

    2 [DefaultValue( typeof( ListSelectionMode ), "Single" )]

    3 public ListSelectionMode SelectionMode

    4 {

    5     get { return (ListSelectionMode)( ViewState["SelectionMode"] ?? ListSelectionMode.Single ); }

    6     set { ViewState["SelectionMode"] = value; }

    7 }

    8 

    9 [Category( "Behavior" )]

   10 [DefaultValue( false )]

   11 public bool SelectOnRowClick

   12 {

   13     get { return (bool)( ViewState["SelectOnRowClick"] ?? false ); }

   14     set { ViewState["SelectOnRowClick"] = value; }

   15 }


Because the CustomGridView is a subclass of GridView, we can take advantage of the events that are already there.  Here is a list of events that we are using and we get for free:

    1 event GridViewPageEventHandler PageIndexChanging;

    2 event GridViewSortEventHandler Sorting;

    3 event GridViewRowEventHandler RowCreated;

    4 event GridViewCommandEventHandler RowCommand;


Then again, there are some events that we had to add in order to tap into some other GridView behavior.  If we had kept all of the selection behavior in the CustomGridView, we could have simply overridden a few methods.  But to keep separation, we chose to put the selection logic in a separate controller class.  One side effect is that we are forced to add a few extra events to meet our needs.  For example:

    1 event EventHandler _rowSelection_ChildControlsCreated;

    2 event EventHandler IRowSelectionView.ChildControlsCreated

    3 {

    4     add { _rowSelection_ChildControlsCreated += value; }

    5     remove { _rowSelection_ChildControlsCreated -= value; }

    6 }

    7 

    8 event EventHandler _rowSelection_PageSizeChanging;

    9 event EventHandler IRowSelectionView.PageSizeChanging

   10 {

   11     add { _rowSelection_PageSizeChanging += value; }

   12     remove { _rowSelection_PageSizeChanging -= value; }

   13 }

   14 

   15 event EventHandler _rowSelection_DataSourceViewChanged;

   16 event EventHandler IRowSelectionView.DataSourceViewChanged

   17 {

   18     add { _rowSelection_DataSourceViewChanged += value; }

   19     remove { _rowSelection_DataSourceViewChanged -= value; }

   20 }


You'll probably notice that I implemented these events explicitly.  I could have used implicit implementations for the most part, but there were at least a couple of event names that conflicted with other controllers that I am hooking into my custom GridView.

Of course we need to fire these events as well.  For each of the events listed above, I have overridden base GridView methods (one property) in order to provide the event hooks:

    1 protected override void CreateChildControls()

    2 {

    3     base.CreateChildControls();

    4 

    5     if(_rowSelection_ChildControlsCreated != null)

    6         _rowSelection_ChildControlsCreated( this, EventArgs.Empty );

    7 }

    8 

    9 public override int PageSize

   10 {

   11     get

   12     {

   13         return base.PageSize;

   14     }

   15     set

   16     {

   17         if(base.PageSize != value)

   18         {

   19             if(_rowSelection_PageSizeChanging != null)

   20                 _rowSelection_PageSizeChanging( this, EventArgs.Empty );

   21 

   22             base.PageSize = value;

   23         }

   24     }

   25 }

   26 

   27 protected override void OnDataSourceViewChanged( object sender, EventArgs e )

   28 {

   29     if(_rowSelection_DataSourceViewChanged != null)

   30         _rowSelection_DataSourceViewChanged( sender, e );

   31 

   32     base.OnDataSourceViewChanged( sender, e );

   33 }


Initializing the SelectionController
The final piece is for the CustomGridView to call on the services of SelectionController.  I've added that logic to the OnInit method of the GridView.

    1 protected override void OnInit( EventArgs e )

    2 {

    3     ...

    4 

    5     if(RowSelectionEnabled)

    6     {

    7         _selectionController = SelectionController.GetInstance();

    8         _selectionController.Observe( this );

    9     }

   10 

   11     ...

   12 }


Wrap-Up
That's pretty much all there is to it.  We put most of the heavy lifting in the SelectionController so the CustomGridView is pretty simple.

It is clear that there is nothing real advanced here.  I am sure a lot can be done to clean things up a bit.  For example, I could use an IoC container to hook my controller into my GridView.  Also, there are a lot of really good professional GridViews out there.  I took this approach because I needed two custom behaviors, one of which was not supported by any of the products I was looking into.  I also work for a start-up company that has been watching our budget pretty closely.  So a day or two working on the customizations was worth it.

Since writing these customizations, though, we have decided to buy a suite of UI components, including a GridView.  The new component meets part of our need and comes really close on the other.  We are hoping to tweak the new grid view to meet our needs in the near future.

Concluding Thoughts
This series was intended to help those of you teetering on the edge of control customization.  It clearly was not a step by step walkthrough.  However, I hope that it has provided some guidelines that might influence your thought process when you are ready to jump in and make your own customizations.

Introduction
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.

Recap:

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.target || 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     }

    7 

    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.


Wrap-Up
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).

Introduction
I briefly described the design approach in Part 1.  This part in the series will explore the ways in which the custom GridView communicates to the controller.  I extend my apologies for the lack of code.  I am limited in what I can provide.  I hope that these guidelines will help point you in the right direction.

Classes / Interfaces
  • CustomGridView - The GridView we are customizing
  • SelectionController - Responsible for the row selection behavior
  • IRowSelectionView - How CustomGridView and SelectionController communicate
IRowSelectionView
To start off with, we need to know what settings the developer has chosen for this instance of the CustomGridView.  We have two basic options: SelectOnRowClick and SelectionMode (single or multiple).

Also, there are a lot of events we are interested in.  Primarily, we are interested in the RowCommand on GridView.  We will be using a command name of "MultiSelect" to handle all of the row clicks.

Other GridView events we will need:
  • RowCreated - to dress the row with an OnClick client-side event with the MultiSelect command.
  • Sorting, PageIndexChanged - results in clearing the selections
Some custom events:
  • DataSourceViewChanged - to tap into the OnDataSourceViewChanged protected GridView member, results in clearing the selections.
  • PageSizeChanging, RowEditing - results in clearing the selections
As you can see, there are a number of reasons to clear the selections.  I have paging enabled on all of my grids.  If a user changes the sorting, then the selected indices don't make sense anymore.  Also, if they change the page size or move from one page to another, these actions all invalidate the selections.

The view will likely evolve as we start to build the SelectionController.  We'll see what happens next time.

Next Time
Part 3 will dive into the SelectionController and how it will manage the selection behavior.  We'll see how it will use the IRowSelectionView in order to assert itself.
 
Background
Way back in March I posted about a custom GridView I was working on.  Then in June, I said that I would be posting some guidelines "in the coming weeks".  Needless to say, I've been busy with other things. 

I am hoping to provide some basic guidelines on how to customize a ASP.Net GridView.  I am no expert.  The experts build components for retail use.  Also, I've taken a server-side approach to the problem which is perhaps a little outdated with AJAX technology available.  In my case, I use an UpdatePanel to handle partial postbacks.

Requirements
I will be using the specific example of selecting rows by a simple click, rather than adding check boxes to indicate selection.  Selected rows are identified by a background color (technically by a css class).  Also, selecting multiple rows will be supported and selection will be very much like the standard Microsoft uses within all of it's applications.  The Shift key will be used to select a range, while the Ctrl key will be used to select (or deselect) one item at a time.

Design
As I've stated, this is a server-side approach.  Initially I ended up with a single control class that was cluttered with all of my customizations.  I refactored to something similar to a model-view-controller or an observer where a class responsible for a specific customization would observe events on the GridView.  This allowed me to do a couple of things.  First, it made testing the custom behavior a lot easier as I was able to mock my GridView and isolate the new behavior.  Second, it eliminated a lot of clutter.

A good approach regardless of your design is to use TDD.  I didn't do this initially and I should have.  I talk about that a little bit here.  Think about your requirements and how you would test them.  Then get started by writing a test and implementing some behavior.

What's Next?
Next I plan to take a look at the interface between my GridView and the selection controller.

That is, don't use Cassini in your development environment if you aren't planning to use it in production.  For example, my customer is running IIS on their server.  Why shouldn't I do the same?  With what I have seen with Cassini, there is no reason.

Cassini is great, but there are noticeable differences between it and IIS.  I am using a third party menu that performs poorly through Cassini, but works fine on IIS.  Others have documented differences in behavior with Cassini.  Scott Lilly  describes a couple of scenarios where Cassini behaves differently than IIS.  Other examples can be found here and here.

So, if you are running VS 2008, your website will be executed on Cassini by default.  How do you change it?  From, the Website Property Pages dialog, under Start Options...  Select "Use custom server" and enter the URL for your virtual directory (assuming you know how to create a virtual).