Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Formatting AutoGenerateColumns in an ASP.NET Grid

4.78/5 (26 votes)
23 Oct 2006CPOL6 min read 1   1.9K  
Demonstrates how to apply conditional formatting in a GridView or DataGrid when columns are dynamically generated, and wrap such code in an IExtenderProvider control.

Image 1

Introduction

There are occasions when one may work with a data source that provides dynamic columns. A stored procedure that aggregates year-to-date totals, with columns representing the months-to-date, is an example. Towards the beginning of the year, there are only a few columns of data; towards the end of the year, there are many. When displaying data like this in a DataGrid or a GridView control, it is convenient to set AutoGenerateColumns=true, letting ASP.NET create the necessary display columns at runtime. Unfortunately, columns generated this way are not available to the developer through the grid’s Columns property, and thus the developer lacks the ability to establish column-based formatting.

In spite of this limitation, a developer may trap an appropriate grid event during data-binding to apply custom formatting. This technique also allows the application of conditional formatting based on individual cell values. Furthermore, the code to apply such formatting may be wrapped in a custom control that implements the IExtenderProvider interface, permitting a page designer to apply complex conditional cell formatting to grids, without any programming.

For a GridView: Trap the RowDataBound event

A GridView control exposes the RowDataBound event during the databinding process. A handler for this event is defined with the following signature:

C#
RowDataBound_Handler(Object grid, GridViewRowEventArgs e)
{
}

The GridViewRowEventArgs object provides access to the table row that has just been bound through its Row property. Row is of the type GridViewRow, which is a derivative of TableRow. As such, there is a Cells property used to access individual TableCell objects.

In this example from the file formattingGridView.aspx in the article download (see the link at the top of the article), the assumption is made that the first column of data returned by a stored procedure (or other data source) is a text category of some kind. The remaining columns (potentially one or more) are values that need to be right-aligned, with alternating background colors on a column by column basis. To demonstrate conditional formatting for individual cells, a check is also made so special highlighting may be applied for high values. Just for fun, header cells are also styled in this code.

C#
private void GV_RowDataBound(object o, GridViewRowEventArgs e)
{
    // apply custom formatting to data cells
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // set formatting for the category cell
        TableCell cell = e.Row.Cells[0];
        cell.Width = new Unit("120px");
        cell.Style["border-right"] = "2px solid #666666";
        cell.BackColor = System.Drawing.Color.LightGray;

        // set formatting for value cells
        for(int i=1; i<e.Row.Cells.Count; i++)
        {
            cell = e.Row.Cells[i];

            // right-align each of the column cells after the first
            // and set the width
            cell.HorizontalAlign = HorizontalAlign.Right;
            cell.Width = new Unit("90px");

            // alternate background colors
            if (i % 2 == 1)
                cell.BackColor
                  =  System.Drawing.ColorTranslator.FromHtml("#EFEFEF");

            // check value columns for a high enough value
            // (value >= 8000) and apply special highlighting
            if (GetCellValue(cell) >= 8000)
            {
                cell.Font.Bold = true;
                cell.BorderWidth = new Unit("1px");
                cell.BorderColor = System.Drawing.Color.Gray;
                cell.BorderStyle = BorderStyle.Dotted;
                cell.BackColor = System.Drawing.Color.Honeydew;
            }

        }
    }

    // apply custom formatting to the header cells
    if (e.Row.RowType == DataControlRowType.Header)
    {
        foreach (TableCell cell in e.Row.Cells)
        {
            cell.Style["border-bottom"] = "2px solid #666666";
            cell.BackColor=System.Drawing.Color.LightGray;
        }
    }

}

The event is then attached declaratively to the GridView by setting the OnRowDataBound attribute:

HTML
<asp:GridView id="myList" runat="server"
              AutoGenerateColumns="true"
              OnRowDataBound="GV_RowDataBound"
              . . .
              >
</asp:GridView>

The code results in a grid that when rendered looks like the following:

Image 2

For a DataGrid: Trap the ItemDataBound event

The technique for a DataGrid is similar. A DataGrid exposes the ItemDataBound event, which gives access to a DataGridItemEventArgs object. This object contains an Item property, with a Controls collection that is populated by TableCell objects during databinding. The following example from formattingDataGrid.aspx shows the same application of formatting as above but for a DataGrid:

C#
private void DG_ItemDataBound(object o, DataGridItemEventArgs e)
{
    // apply custom formatting to data cells
    if (e.Item.ItemType == ListItemType.Item
        || e.Item.ItemType == ListItemType.AlternatingItem)
    {
        // set formatting for the category cell
        TableCell cell = (e.Item.Controls[0] as TableCell);
        cell.Width = new Unit("120px");
        cell.Style["border-right"] = "2px solid #666666";
        cell.BackColor = System.Drawing.Color.LightGray;

        // set formatting for value cells
        for(int i=1; i<e.Item.Controls.Count; i++)
        {
            cell = (e.Item.Controls[i] as TableCell);

            // right-align each of the column cells after the first
            // and set the width
            cell.HorizontalAlign = HorizontalAlign.Right;
            cell.Width = new Unit("90px");

            // alternate background colors
            if (i % 2 == 1)
                cell.BackColor
                  =  System.Drawing.ColorTranslator.FromHtml("#EFEFEF");

            // check value columns for a high enough value
            // (value >= 8000) and apply special highlighting
            if (GetCellValue(cell) >= 8000)
            {
                cell.Font.Bold = true;
                cell.BorderWidth = new Unit("1px");
                cell.BorderColor = System.Drawing.Color.Gray;
                cell.BorderStyle = BorderStyle.Dotted;
                cell.BackColor = System.Drawing.Color.Honeydew;
            }

        }

    }

    // apply custom formatting to the header cells
    if (e.Item.ItemType == ListItemType.Header)
    {
        foreach (TableCell cell in e.Item.Controls)
        {
            cell.Style["border-bottom"] = "2px solid #666666";
            cell.BackColor=System.Drawing.Color.LightGray;
        }
    }

}

Similar to the GridView, the assignment of this event handler to the DataGrid is made declaratively by setting the OnItemDataBound attribute:

HTML
<asp:DataGrid id="myList" runat="server"
              AutoGenerateColumns="true"
              OnItemDataBound="DG_ItemDataBound"
              . . .
              >
</asp:DataGrid>

Wrapping Common Formatting in a Custom Extender

Perhaps you or your organization has designed some standardized report formats that require column-based or conditional cell formatting. If you find that you apply that same report formatting to a number of grids across a project, you may find it useful to wrap custom formatting code in an extender control. An extender is a custom component that implements the IExtenderProvider interface and provides additional (extended) functionality to existing controls.

There are a number of good articles that introduce the IExtenderProvider interface and describe implementation issues in detail, including the following:

For this implementation, there isn’t that much more to do. We’ll start with a new custom class called CustomReportFormatExtender that inherits from Control and implements IExtenderProvider.

C#
[ProvideProperty("UseCustomReportFormat", typeof(GridView))]
[ . . . other attributes . . .] public class CustomReportFormatExtender : Control, IExtenderProvider
{
    . . . 
}

The ProvidePropertyAttribute decorating this class specifies that controls of type GridView should be extended with a new property called UseCustomReportFormat. When this property on a GridView is set to true, the formatting will be applied. The extender indicates that it only applies to GridView controls by implementing the CanExtend() method, required by the IExtenderProvider interface:

bool IExtenderProvider.CanExtend(object o)
{
    return (o is GridView);
}

It is the responsibility of the extender control to provide storage space and methods for setting and retrieving extended properties. To store extended property values, we have a custom ExtendedProperties class. This class records the ID of a GridView as well as the value of its extended UseCustomReportFormat boolean property.

C#
public class ExtenderProperties
{
    private bool _useCustomReportFormat;
    private string _gridID;
 
    public bool UseCustomReportFormat
    {
        get { return _useCustomReportFormat; }
        set { _useCustomReportFormat = value; }
    }

    public string GridID
    {
        get { return _gridID; }
        set { _gridID = value; }
    }
}

The extender control keeps a collection of ExtenderProperties in its Props property. Props is exposed and decorated with designer attributes purely to ensure that Visual Studio will persist the extended property values. Without this attention, the extended property values are not remembered by Visual Studio.

C#
private ExtenderPropertiesCollection _props 
  = new ExtenderPropertiesCollection();

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[PersistenceMode(PersistenceMode.InnerProperty)]
[Browsable(false)]
public ExtenderPropertiesCollection Props
{
    get
    {
        return _props;                
    }
}

The extender control then provides a Get and Set method for each extended property, following the naming convention GetPropertyName and SetPropertyName. In this case, there is only the boolean UseCustomReportFormat property to expose. The collection class ExtenderPropertiesCollection is defined with utility methods used in these Get and Set methods to simplify the management of the extended property.

C#
public bool GetUseCustomReportFormat(GridView grid)
{
    return Props.GetUseCustomReportFormatForGridID(grid.ID);
}

public void SetUseCustomReportFormat(GridView grid, bool value)
{
    Props.SetUseCustomReportFormatForGridID(grid.ID, value);
    NotifyDesignerOfChange();  // ensure compatibility with Visual Studio
}

Finally, the extender needs to provide the actual formatting functionality. It does so by hooking the RowDataBound event for desired GridViewcontrols. The formatting code is wrapped in the function Handle_RowDataBound, and assigned during the OnInit event of the extender.

C#
protected void Handle_RowDataBound(object o, GridViewRowEventArgs e)
{
    . . . formatting code . . . 
}


protected override void OnInit(EventArgs e)
{
    base.OnInit(e);

    foreach (ExtenderProperties p in Props)
    {
        GridView g = FindControl(p.GridID) as GridView;
        if (g != null && p.UseCustomReportFormat)
            g.RowDataBound += new 
              GridViewRowEventHandler(Handle_RowDataBound);
    }            
}

That’s it. This custom extender object may be compiled, added to the toolbox, and dragged onto an .aspx page. Then it is simply a matter of specifying which GridView controls to format by setting their new UseCustomReportFormat property to true.

Image 3

For a page designer, this provides a simple, standardized means for assigning complex cell-by-cell formatting in a declarative way. The sample gridWithExtender.aspx from the article download demonstrates the use of this extender control.

Other Approaches to Dynamic Column Formatting

There are alternatives to this approach of injecting code during databinding for applying formatting to dynamically generated columns. One alternative is to inspect the data source before binding it to a display control and programmatically create DataGrid or GridView columns. Properties for these DataGridColumn objects (in the case of a DataGrid) or DataControlField objects (in the case of a GridView) may be set to influence formatting behavior during databinding. The sample downloads in this CodeProject article[^] demonstrate an example of this approach. While column formatting may be applied in this way without an additional pass through individual table cells, conditional formatting on a cell-by-cell basis must still be applied using an approach like that explained in this article.

Summary

This article presents one approach for applying formatting to a GridView or DataGrid when the columns in the grid are dynamically generated. When AutoGenerateColumns set to true, cell appearance can be manipulated during databinding by trapping an appropriate event – RowDataBound in the case of a GridView, and ItemDataBound in the case of a DataGrid. Formatting applied to individual TableCell objects may be conditional based on values in the cells. For additional ease-of use, common report formatting code may be wrapped in a custom extender control, allowing for a completely declarative solution for page designers.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)