Create Custom Columns for the WinForms DataGrid Control

WinForm's DataGrid control is powerful, but its default display doesn't provide much utility. You can replace it with a third-party control, of course, but Dino demonstrates how you can use the DataGrid's customization layer to add features like hyperlinked URLs yourself.


October 01, 2003
URL:http://drdobbs.com/create-custom-columns-for-the-winforms-d/184416697

Create Custom Columns for the WinForms DataGrid Control

The Windows Forms DataGrid control is a rich user-interface component that can be effectively used to enhance the quality of data-bound forms in .NET desktop applications. The control features many capabilities including .NET-style data binding, column binding, plenty of visual settings, automatic navigation, in-place editing, and hierarchical display. It is not perfect, though. In particular, it doesn't provide great support for data types more complex than numbers and strings. For example, dates can only be rendered as strings and a URL stored in a data source is nothing more than a string. Is there a way to improve the rendering for certain types of data? A helpful suggestion on how to proceed comes from the Web counterpart of the control — the ASP.NET DataGrid control.

The ASP.NET DataGrid supports several types of columns including data-bound, button hyperlink, and templated columns. The Windows Forms grid control supplies only two types of columns — textbox and boolean columns. The default type of column is the textbox column. The text associated with the cell is rendered as the text of an optionally readonly textbox control. If the cell contains a boolean value, it is rendered through a checkbox. This is what the control can do for you. If it is not enough, you can only extend it with custom columns.

Why You Need Custom Columns

Suppose you have a database of articles with columns like title, author ID, and the URL to the online article. Unless you take steps to alter it, the DataGrid would show its contents as in Figure 1. There are a few problems with that grid. First off, the author ID is displayed as a number whereas the name would have been more appropriate and clearer for the end user. Second, the URL is static text and is not clickable. Third, if the user wished to modify the name of the article's author, wouldn't it be nice if you could serve a list of the available options? In short, all these options are not applicable with built-in columns; a couple of ad hoc columns will do the job.

A custom column is a class that derives from DataGridColumnStyle. The base class doesn't implement all the functions required by the grid, so when you start creating a new column class, be prepared to implement a few abstract methods. In addition, a few methods should be conveniently overridden for a better result. Let's review the overall pattern.

Logically speaking, a DataGrid column is a collection of cells — that is, independent objects representing a block of the column data. Such a high-level description clashes with the actual implementation of the grid in which the whole content is simply drawn to the underlying window canvas using GDI+ calls. All in all, this is not much different from what happens in Win32 programming in which you need to manually paint the window's background. The only difference is that the tools available for the job are of a slightly higher level. For performance reasons, the DataGrid maintains, at most, one instance of a control per column. The lifetime of this control and, more importantly, its type depend on the characteristics and the implementation of the column class.

When the user clicks on a column cell, the class pops up the hidden control and sizes it to fit into the cell bounds. You can choose to create a brand new instance of the control whenever the user clicks (single-call pattern), or can you go for a singleton approach: create the control once and reuse for the duration of the application. Depending on your choice, the number of active controls at a time ranges from 0 (single-call approach, no cell selected) to the number of the displayed columns.

There are particular types of control behind each type of column. The DataGridTextBoxColumn class is built around an instance of a textbox control. Instead, a checkbox control is the bread and butter of the DataGridBoolColumn class. When you build your own column classes, you can choose the control that best suits your expectations. For example, if you want to architect a column optimized for date values, the DateTimePicker control sounds like the perfect choice. Incidentally, the MSDN documentation just contains a sample grid column for dates based on the date-picker control.

The life of a DataGrid column is spent switching between two states — read and edit. When one of the cells is selected (there's always one selected cell in a selected row), the column enters edit mode, and the underlying control gets displayed within the boundaries reserved to the cell on the screen. The control should supply any UI elements that can be used to edit the current value. The column is also responsible for committing changes. The column doesn't have to physically store the new value but must pass it to a built-in method for actual storage. The data is stored in the bound data source object care of the DataGrid's internal framework.

When the user clicks outside the cell, or completes the update procedure, the edit mode is reset, the control is hidden, and the content of the cell is rendered through normal painting. Let's see how all this turns to code with a concrete example — a hyperlink column.

Create a Hyperlink Column

The class I'm going to create is called DataGridLinkColumn and inherits from the standard base class — DataGridColumnStyle. The class constructor is quite simple and limited to initializing the control behind:

public DataGridLinkColumn() : base() {
    Initialize();
}
private void Initialize() {
    _llControl = new LinkLabel();
    _toolTip = new ToolTip(); 
    _llControl.Visible = false;
    _inEdit = false;
    _inAbort = false;
}

The DataGridLinkColumn is expected to render a database-stored URL string as a clickable hyperlink. In the .NET Framework, there's just one control that serves that purpose — the LinkLabel control. The LinkLabel control is a special type of label that allows you to define sensitive areas and associate them with custom information — typically, but not necessarily, a URL. The control renders any link area with blue underlined text, thus mimicking a web hyperlink. However, when one clicks on any hyperlinked areas, an event is fired to the application but no URL navigation takes place by default. This is an important point to keep in mind.

As the previous code snippet suggests, the LinkLabel control provides no specific support for tooltips — another thing that was easy to obtain with web hyperlinks. If you want to add tooltip support to the URL displayed through the grid, a second control — a TooltTip control — must be instantiated. The LinkLabel control is created and marked as hidden.

Table 1 lists the DataGridColumnStyle methods that you might want to override to implement a real-world custom column. The base class DataGridColumnStyle is marked abstract and so are some of its methods. It goes without saying that you have to override all abstract methods, otherwise a compile error will catch you. In addition, to all the necessary overrides, a couple of other methods that have a concrete implementation in the base class should be overridden too — ConcedeFocus and SetDataGridInColumn.

The Edit method is invoked when the user clicks to select (and edit) a particular cell. The LinkLabel control is configured to work and bound to the URL and the text for the cell. The column class also defines three custom properties aimed at defining the binding mechanism between the column and the grid's data source. The properties are Text, DisplayMember, and NavigateUrlField.

The former two regard the text of the hyperlink; the latter is about the URL to jump to. If you set the Text property, then all the cells of the column will display the same text; otherwise, the values in the DisplayMember field are used. Likewise, the values in the NavigateUrlField are used to link the cells on a per-row basis. Listing 1 shows the source code of the Edit method. The ChangeEditMode helper function sets internal flags that determine the modality of the column. The LinkLabel control is resized to fit into the room of the selected cell. It is also assigned text depending on the Text and DisplayMember values.

The Links collection of the LinkLabel control tracks the sensitive areas of the control. A sensitive area is given by a range of characters in the bound text. A link area is a pair of integers indicating the start position and the width of a clickable substring. The following sample code defines a single link area that spans over the length of the text:

_llControl.Links.Add(0, _llControl.Text.Length, url);

The third argument — the URL — is not used to navigate upon clicking. That value, instead, is passed on to the event handler that will take care of the click event on the LinkLabel control.

void _llControl_LinkClicked(object sender,
LinkLabelLinkClickedEventArgs e)
{
    string target = e.Link.LinkData as string;
    if (target != null)
        Process.Start(target);
}

When the LinkClicked event fires, the event handler gets a data structure that contains a Link field. The LinkData property of the field stores the URL (or any other information) that was associated with the area. To complete the demonstration and launch the browser, you call the static Start method on the Process class. The Process class is defined in the System.Diagnostics namespace.

If the Edit method is at the heart of the column class, the Commit method represents another critical point because it is where any changed value is validated and stored into the data source. Since the DataGridLinkColumn class doesn't show editable values, the implementation of Commit is minimal. (See the companion source of this article and the MSDN documentation for more information.)

Painting the Cell

What happens when a cell is not selected for editing? As mentioned, the underlying control is hidden and the default, read-only, text is painted on the window background. The DataGridColumnStyle class defines three overloads for the method Paint. One of these overloads holds a concrete implementation; two of them are abstract and must be overridden in any derived class.

The Paint method must accomplish one key thing: drawing the text for the cell using the current visual settings such as font and colors. In particular, you have to determine the background and the foreground brushes according to the grid's style for items, alternating items, and selected items. In addition, you must select the grid's font to avoid any graphical discrepancy.

The Paint method is also the only way you have to implement more ambitious forms of customization over a DataGrid control. For example, if you want to draw cells with negative values in red (or code any similar, data-driven logic) you can do that only by overriding the Paint method.

Putting It All Together

Figure 2 shows the sample application in action. At first, the cells in the URL column display as normal text. As soon as you click to select the cell, the text morphs into a LinkLabel control, and by clicking, you jump to the desired URL. How can you bind a custom column to a DataGrid control?

If you want to have total control over the columns displayed, you have to define a table style. Listing 2 shows how to do that. Each column is represented by a column object added to the GridColumnStyles collection of the DataGridTableStyle object. The grid table style object is the skin used to build the grid's user interface. The grid can have multiple skins if needed.

Summary

Creating custom columns for a Windows Forms DataGrid control is a necessity if you're going to use it in real-world applications. Although powerful, the default version of the control is rarely optimal for a realistic end-user application. So far, resorting to a third-party grid was a foregone conclusion. While that still remains an effective option, the customization layer of the built-in DataGrid control can help you roll your own customization and increase your expertise. On the down side, experimentation takes time. What's certain is that the DataGrid control is a component more powerful than any other we had in the past bundled with Visual Basic. Evaluating its cost-effectiveness in the economy of a particular project, though, is completely up to you. w::d


Dino Esposito is Wintellect's ADO.NET and XML expert and is a trainer and consultant based in Rome, Italy. He is a contributing editor to MSDN Magazine, writing the "Cutting Edge" column, and is the author of several books for Microsoft Press, including Building Web Solutions with ASP.NET and Applied XML Programming for .NET. Contact him at [email protected].

Create Custom Columns for the WinForms DataGrid Control

Figure 1 Without custom columns, the DataGrid control is not very useful

Create Custom Columns for the WinForms DataGrid Control

Figure 2 The sample application in action

Create Custom Columns for the WinForms DataGrid Control

Listing 1 The Edit method


protected override void Edit(CurrencyManager source, int rowNum, Rectangle bounds, 
    bool isReadOnly, string instantText, bool cellIsVisible)
{
    if (_inEdit)
        return; 
        
    // The Link column is NOT editable but when in edit mode switches  
    // to hyperlink mode
    if (cellIsVisible && !_inAbort)
    {
        ChangeEditMode(true);
        _llControl.SetBounds(bounds.X, bounds.Y, bounds.Width, bounds.Height);
        _llControl.Text = GetDisplayText(source, rowNum);

        // Define the link area (1 covering the whole string)
        _llControl.Links.Clear(); 
        string url = (string) GetColumnValueAtRow(source, rowNum);
        _llControl.Links.Add(0, _llControl.Text.Length, url);
        _toolTip.SetToolTip(_llControl, url); 
        _llControl.LinkClicked +=new
                               LinkLabelLinkClickedEventHandler(_llControl_LinkClicked);
    }
    else
    {
        ChangeEditMode(false);
        _inAbort = false;
    }

return;
}

Create Custom Columns for the WinForms DataGrid Control

Listing 2 Defining the columns to bind to the DataGrid


private void ConfigureGrid()
{
    // Define the table style
    DataGridTableStyle skin = new DataGridTableStyle();
    skin.MappingName = "Titles";

    // #1 -- Title column
    DataGridLinkColumn columnTitle = new DataGridLinkColumn();
    columnTitle.MappingName = "URL";
    columnTitle.HeaderText = "Title";
    columnTitle.NavigateField = "URL";
    columnTitle.DisplayMember = "title";
    columnTitle.Width = 190;
    skin.GridColumnStyles.Add(columnTitle);

    // Add the table style info (must occur HERE)
    grid.TableStyles.Add(skin);
}

Create Custom Columns for the WinForms DataGrid Control

Table 1 DataGridColumnStyle overridable methods

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.