The DataGrid Control
The DataGrid is perfect for those who want a single data control that can do everything on its own. Even with some impressive column mapping features, it's still not as customizable or flexible as the approaches you've looked at so far, and the visual appearance doesn't provide much variety (multiline text columns, for example, are not supported except when editing a row). If you need a perfect super-grid control, you are likely to find that many custom options will soon appear on the network, and these third-party controls are likely to have a far wider range of features and much more customizability. Still, the DataGrid control is useful in the true, rapid application design spirit. It even provides simple userediting functionality.To use the DataGrid, you only need to assign a table or DataSet to its DataSource property. If you use an entire DataSet, the DataGrid provides special navigation links that allow you to browse to any of the tables it contains (see Figure 9-18).

Figure 9-18: DataGrid navigation links
DataGrid Relations
You can also make use of these navigation links to create a master-detail list. All you need to do is create the appropriate table relations first.
// Create a relation between categories and products.
DataRelation dr = new DataRelation("Products in this category",
dsStore.Tables["Categories"].Columns["CategoryID"],
dsStore.Tables["Products"].Columns["CategoryID"]);
// Add the relation to the DataSet.
dsStore.Relations.Add(dr);
// Bind the data grid.
dataGrid1.DataSource = dsStore.Tables["Categories"];
It's not as flexible as our custom solution for master-detail forms, but it works well with little tweaking required. Figure 9-19 shows the master-details list.

Figure 9-19: DataGrid master-detail lists
DataGrid Column Mapping
Ordinarily, the DataGrid uses default header text and column widths and adds every field from the data source. In typical use, however, you may need to change cryptic field names, expand some columns to fit data, hide others, and choose the order they are displayed in.To do this, you need to create a DataGridTableStyle collection and add column objects that represent every column you want displayed. When you add this collection to the DataGrid.TableStyles property, the DataGrid changes its default behavior, and only displays the columns contained in the collection. It also abides by all the column settings you have configured.Here is an example that configures a DataGrid to show only one field:
// Create the column collection.
DataGridTableStyle columns = new DataGridTableStyle();
columns.MappingName = "Products";
// Create and configure the columns you want to display.
DataGridTextBoxColumn colDescription = new DataGridTextBoxColumn();
colDescription.HeaderText = "Description of Product";
colDescription.Width = 500;
colDescription.MappingName = "Description";
// Add the columns to the collection.
columns.GridColumnStyles.Add(colDescription);
// Configure the DataGrid to use these column settings.
dataGrid1.TableStyles.Add(columns);
// Bind the grid.
dataGrid1.DataSource = dsStore.Tables["Products"];
Creating Custom DataGrid Column Styles
The DataGrid only provides two types of columns: one for text data and one for true/false Boolean fields. These column types correspond to the .NET column classes DataGridBoolColumn and DataGridTextBoxColumn.It doesn't take much experimentation with the DataGrid control to realize that there are many types of data that don't suit either column type. The usual solution is to provide a read-only text field, or try to code innovative algorithms in the Format and Parse event handlers that can perform the required conversions. However, you can derive your own custom classes from the DataGridColumnStyle class, and use them to support other types of data. Table 9-3 lists the methods you need to override to create a custom DataGridColumnStyle.
Table 9-3: Overridable DataGridColumnStyle Methods
MethodDescription
Abort(), Commit(), and Edit()These methods are triggered in response to column editing. Edit occurs when the user clicks in a cell to start editing. Commit happens when the user navigates to a new cell (another field or another record) and the change is to be committed. If Commit returns false, the change is not made-instead, the Abort method gets a chance to roll it back. If you want to make a read-only column, you don't need to do anything in these methods, but you still need to override them.
GetMinimumHeight(), GetMinimumSize(), and GetPreferredHeight()Gets the dimensions of the row, both as the minimum allowed, and the preferred (default).
Paint()Displays the data in the column.
Support for unusual data types isn't the only reason to create a DataGridColumnStyle. You might just want to tweak the display for a specific field. For example, you might want to display an icon in a field that indicates something about the status of a given record (for example, a graphical "New!" starburst next to a recently added product).The next example presents a custom DataGridColumnStyle that's designed to show prices-with a twist. Prices that are lower than the indicated "special" price are displayed with a happy icon next to them.Start by defining the basic class, with a public member for the threshold price:
public class DataGridPriceIconColumn : DataGridColumnStyle
{
public decimal NicePrice;
public DataGridPriceIconColumn(decimal nicePrice)
{
this.NicePrice = nicePrice;
}
}
Next, the editing methods are overridden. No actual code is added, as this column only supports read-only use.
protected override void Abort(int rowNum)
{
// Do nothing.
}
protected override bool Commit(CurrencyManager dataSource, int rowNum)
{
return true;
}
protected override void Edit(CurrencyManager source,
int rowNum, System.Drawing.Rectangle bounds,
bool readOnly, string instantText, bool cellIsVisible)
{
// Do nothing.
}
protected override void Edit(CurrencyManager source,
int rowNum, System.Drawing.Rectangle bounds, bool readOnly)
{
// Do nothing.
}
protected override void Edit(CurrencyManager source,
int rowNum, System.Drawing.Rectangle bounds,
bool readOnly, string instantText)
{
// Do nothing.
}
Next, the code is added to return size information:
protected override int GetMinimumHeight()
{
return 20;
}
protected override int GetPreferredHeight(System.Drawing.Graphics g,
object value)
{
return 20;
}
protected override System.Drawing.Size GetPreferredSize(
System.Drawing.Graphics g, object value)
{
return new Size(100, 20);
}
Finally, the interesting code is added. This code uses some basic GDI+ techniques to draw an icon and the actual price text in the provided rectangle (which represents the cell). Notice that there are three versions of the Paint() method, and you need to implement them all. In this sample implementation, the versions with fewer parameters simply call the fullest Paint() method with some logical defaults.
protected override void Paint(System.Drawing.Graphics g,
System.Drawing.Rectangle bounds, CurrencyManager source, int rowNum,
System.Drawing.Brush backBrush, System.Drawing.Brush foreBrush,
bool alignToRight)
{
// Clear the cell.
g.FillRegion(backBrush, new Region(bounds));
decimal price = (decimal)this.GetColumnValueAtRow(source, rowNum);
Icon priceIcon;
if (price < NicePrice)
{
priceIcon = new Icon(Application.StartupPath + "\\happy2.ico");
// Draw the optional "nice price" icon.
g.DrawIcon(priceIcon, new Rectangle(bounds.X, bounds.Y, 16, 16));
}
// Draw the text.
g.DrawString(price.ToString("C"), new Font("Tahoma", (float)8.25),
Brushes.Black, bounds.X + 20, bounds.Y + 2);
}
protected override void Paint(System.Drawing.Graphics g,
System.Drawing.Rectangle bounds, CurrencyManager source,
int rowNum, bool alignToRight)
{
this.Paint(g, bounds, source, rowNum, Brushes.White, Brushes.Black,
alignToRight);
}
protected override void Paint(System.Drawing.Graphics g,
System.Drawing.Rectangle bounds, CurrencyManager source, int rowNum)
{
this.Paint(g, bounds, source, rowNum, Brushes.White, Brushes.Black, false);
}
Figure 9-20 shows the custom DataGridPriceIconColumn in action.

Figure 9-20: A custom DataGrid column
This design could also be implemented using a separate column. For example, you could bind a Price column to a normal DataGridTextBoxColumn and to a custom DataGridPriceIconColumn to show the icon. You could then place the DataGridPriceIcon column at the beginning of the row.