Wednesday, April 6, 2011

Presenting Data with the DataGridView Control in .NET 2.0

Defining Custom Column and Cell Types

With the DataGridView, you are already leaps and bounds ahead of the DataGrid for presenting rich data because of the built-in column types that it supports out of the box. But there are always custom scenarios that you will want to support to display custom columns. Luckily, another thing the DataGridView makes significantly easier is plugging in custom column and cell types.
If you want to customize just the painting process of a cell, but you don’t need to add any properties or control things at the column level, you have an event-based option rather than creating new column and cell types. You can handle the CellPainting event and draw directly into the cell itself, and you can achieve pretty much whatever you want with the built-in cell types and some (possibly complex) drawing code. But if you want to be able to just plug your column or cell type in a reusable way with the same ease as using the built-in types, then you can derive your own column and cell types instead.
The model you should follow for plugging in custom column types matches what you have already seen for the built-in types: You need to create a column type and a corresponding cell type that the column will contain. You do this by simply inheriting from the base DataGridViewColumn and DataGridViewCell classes, either directly or indirectly, through one of the built-in types.
The best way to explain this in detail is with an example. Say I wanted to implement a custom column type that lets me display the status of the items represented by the grid’s rows. I want to be able to set a status using a custom-enumerated value, and cells in the column will display a graphic indicating that status based on the enumerated value set on the cell. To do this, I define a StatusColumn class and a StatusCell class (I disposed of the built-in type naming convention here of prefixing DataGridView on all the types because the type names get sooooooooooo long). I want these types to let me simply set the value of a cell, either programmatically or through data binding, to one of the values of a custom-enumerated type that I call StatusImage. StatusImage can take the values Green, Yellow, or Red, and I want the cell to display a custom graphic for each of those based on the value of the cell. Figure 6.7 shows the running sample application with this behavior.
06fig07.jpg Figure 6.7 Custom Column and Cell Type Example

Defining a Custom Cell Type

To achieve this, the first step is to define the custom cell type. If you are going to do your own drawing, you can override the protected virtual Paint method from the DataGridViewCell base class. However, if the cell content you want to present is just a variation on one of the built-in cell types, you should consider inheriting from one of them instead. That is what I did in this case. Because my custom cells are still going to be presenting images, the DataGridViewImageCell type makes a natural base class. My StatusCell class isn’t going to expose the ability to set the image at random, though; it is designed to work with enumerated values. I also want the cell value to be able to handle integers as long as they are within the corresponding numeric values of the enumeration, so that I can support the common situation where enumerated types are stored in a database as their corresponding integer values. The code in Listing 6.4 shows the StatusCell class implementation.

Example 6.4. Custom Cell Class

namespace CustomColumnAndCell
{
   public enum StatusImage
   {
      Green,
      Yellow,
      Red
   }

   public class StatusCell : DataGridViewImageCell
   {
      public StatusCell()
      {
         this.ImageLayout = DataGridViewImageCellLayout.Zoom;
      }

      protected override object GetFormattedValue(object value,
         int rowIndex, ref DataGridViewCellStyle cellStyle,
         TypeConverter valueTypeConverter,
         TypeConverter formattedValueTypeConverter,
         DataGridViewDataErrorContexts context)
      {

         string resource = "CustomColumnAndCell.Red.bmp";
         StatusImage status = StatusImage.Red;
         // Try to get the default value from the containing column
         StatusColumn owningCol = OwningColumn as StatusColumn;
         if (owningCol != null)
         {
            status = owningCol.DefaultStatus;
         }
         if (value is StatusImage || value is int)
         {
            status = (StatusImage)value;
         }
         switch (status)
         {
            case StatusImage.Green:
               resource = "CustomColumnAndCell.Green.bmp";
               break;            case StatusImage.Yellow:
               resource = "CustomColumnAndCell.Yellow.bmp";
               break;
            case StatusImage.Red:
               resource = "CustomColumnAndCell.Red.bmp";
               break;
            default:
               break;
         }
         Assembly loadedAssembly = Assembly.GetExecutingAssembly();
         Stream stream =
            loadedAssembly.GetManifestResourceStream(resource);
         Image img = Image.FromStream(stream);
         cellStyle.Alignment =
            DataGridViewContentAlignment.TopCenter;
         return img;
      }
   }
}
The first declaration in this code is the enumeration StatusImage. That is the value type expected by this cell type as its Value property. You can then see that the StatusCell type derives from the DataGridViewImageCell, so I can reuse its ability to render images within the grid. There is a default status field and corresponding property that lets the default value surface directly. The constructor also sets the ImageLayout property of the base class to Zoom, so the images are resized to fit the cell with no distortion.
The key thing a custom cell type needs to do is either override the Paint method, as mentioned earlier, or override the GetFormattedValue method as the StatusCell class does. This method will be called whenever the cell is rendered and lets you handle transformations from other types to the expected type of the cell. The way I have chosen to code GetFormattedValue for this example is to first set the value to a default value that will be used if all else fails. The code then tries to obtain the real default value from the containing column’s DefaultValue property if that column type is StatusColumn (discussed next). The code then checks to see if the current Value property is a StatusImage enumerated type or an integer, and if it is an integer, it casts the value to the enumerated type.
Once the status value to be rendered is determined, the GetFormattedValue method uses a switch-case statement to select the appropriate resource name corresponding to the image for that status value. You embed bitmap resources in the assembly by adding them to the Visual Studio project and setting the Build Action property on the file to Embedded Resource. The code then uses the GetManifestResourceStream m

http://osteele.com/archives/2004/08/web-mvc