Click here to Skip to main content
Click here to Skip to main content

Formatting AutoGenerateColumns in an ASP.NET Grid

, 23 Oct 2006
Rate this:
Please Sign up or sign in to vote.
Demonstrates how to apply conditional formatting in a GridView or DataGrid when columns are dynamically generated, and wrap such code in an IExtenderProvider control.

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:

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.

    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:

        <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:

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:

    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:

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

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

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.

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.

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.

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.

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)

Share

About the Author

Mike Ellison

United States United States
I work for the University of Nevada, Las Vegas in the Office of Institutional Analysis and Planning. Among other things, our office is charged with the mission of deriving useful information in support of administrative decision-making from institutional data. Within the context of that mission, my office mates and I apply technology in the form of custom data processing applications, data extraction and analysis tools, reporting tools, relational databases, OLAP solutions, data warehousing, and data mining.
 
Visit my blog at MishaInTheCloud.com


Comments and Discussions

 
GeneralHiding an AutoGenerateColumns="True" Column Programmatically PinmemberRyan Hathaway1-Oct-08 5:08 
GeneralRe: Hiding an AutoGenerateColumns="True" Column Programmatically PinmemberMike Ellison1-Oct-08 5:48 
GeneralRe: Hiding an AutoGenerateColumns="True" Column Programmatically PinmemberSivaPrakasamDotNet13-Aug-09 23:18 
GeneralThanks for a great tutorial PinmemberKing Coffee20-Jun-08 6:58 
GeneralRe: Thanks for a great tutorial PinmemberMike Ellison20-Jun-08 7:02 
QuestionGetCellValue not available for Visual Basic? Pinmemberjonefer24-May-07 18:57 
AnswerRe: GetCellValue not available for Visual Basic? PinmemberMike Ellison25-May-07 7:21 
AnswerRe: GetCellValue not available for Visual Basic? Pinmembernewnarendra20-Mar-08 6:36 
did u get the answer. please post VB code
GeneralFor those interested PinmemberMike Ellison10-Dec-06 3:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140814.1 | Last Updated 23 Oct 2006
Article Copyright 2006 by Mike Ellison
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid