A Remedy for DataGrid Vertigo
By Russ Nemhauser
Published: 3/15/2003
Reader Level: Intermediate
Rated: 4.33 by 3 member(s).
Tell a Friend
Rate this Article
Printable Version
Discuss in the Forums

A Remedy for DataGrid Vertigo

The AlternatingItemStyle can be a wonderful thing. But in many cases I found that I would start to get a feeling like vertigo if I scrolled the page up or down. So I thought back to my days in Manhattan in the financial industry and remembered how I used to prepare reports to be viewed on screen. I alternated the colors of the rows, but I did it every three or five rows (depending on the width of the report, the content of the report, or my own aesthetic taste).

The ASP.NET DataGrid is an extremely popular control. People know how to use it (with varying levels of depth) and it is pretty simple to display a lot of content with only a handful of lines of code. With its functionality and popularity in mind, I decided to use the DataGrid itself as a base class and enhance what people were already used to.

In this article I will show you how to create a control that we'll call DataGridPlus. Our control will allow developers to specify the number of alternating rows before we change from our ItemStyle to our AlternatingItemStyle or back again.

We'll start by creating a new ASP.NET web application called dgPlusTester. By default we should have a single web form called webform1.aspx that we'll use to display our new control. Once our web project is created we'll right-click the solution in the Solution Explorer and choose New Project... from the Add menu. After you've selected the language folder of your choice, click Web Control Library in the "Templates" area and call this new project DataGridPlus.

By default Visual Studio will create a new control called WebCustomControl1.cs or WebCustomControl1.vb and add it to the web control library project. My habit is to just delete that control and create my own, so right-click the file name and choose "Delete". Once you have removed that control, right-click the web control project in Solution Explorer and choose Add New Item from the Add menu. Choose Web Custom Control as your template and call your control DataGridPlus.cs or DataGridPlus.vb (depending on what language you are using).

Our new web custom control will most likely have a Text property. We won't be needing it, so there are three sections of code to modify before we get to the fun stuff. Look in your code for:

C#

[DefaultProperty("Text"),
ToolboxData("<{0}:DataGridPlus runat=server>")]

VB

<DefaultProperty("Text"), ToolboxData("<{0}:DataGridPlus runat=server>")>

We'll remove the code DefaultProperty("Text"), (since there will be no property called "Text" to default to). The next bit of code to remove is the Text property itself. Look in your control for the following code and delete it.

C#

private string text;

[Bindable(true),
    Category("Appearance"),
    DefaultValue("")]
public string Text
{
    get
    { 
        return text;
    }
    set
    {
        text = value;
    }
}

VB

Dim _text As String

<Bindable(True), Category("Appearance"), DefaultValue("")> Property [Text]() As String
    Get
        Return _text
    End Get

    Set(ByVal Value As String)
        _text = Value
    End Set
End Property

The final place that uses this Text property is the Render method. For now we'll simply replace the reference to our Text property with an empty string. So our Render method should now look like this:

C#

protected override void Render(HtmlTextWriter output)
{
    output.Write("");
}

VB

Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
    output.Write("")
End Sub

Congratulations. We're now ready to begin writing our new control. To begin with we'll need to reference some of the .NET namespaces - add code at the top of your control to use System.Drawing and System.Web.UI.WebControls if you don't already have them referenced.

In order for our new DataGridPlus control to have all the functionality of the ASP.NET DataGrid we'll have to inherit the DataGrid in our new control. So we'll change the inheritance to System.Web.UI.WebControls.DataGrid from the default System.Web.UI.WebControls.WebControl.

Since we'll let the developer choose how many rows before alternating styles we'll need to add a property that will allow it. We could specify the Category under which we'd like our new property to appear in the properties window. If we had the code [Category("Appearance")] immediately preceding the code for our property we would find it grouped with the other properties in the Appearance category.

C#

private Int16 _rows;
public Int16 AlternatingRows
{
    get { return _rows; }
    set { _rows = value; }
}

VB

Private _rows As Int16
Public Property AlternatingRows As Int16
    Get
        Return _rows
    End Get
    Set
        _rows = Value
    End Set
End Property

In addition to binding data, a DataGrid applies TableItemStyles to the DataGridItems it contains. What we need to do is apply the ItemStyle and AlternatingItemStyle when WE want to instead of letting the DataGrid decide for us. We will have to keep track of how many rows we've rendered since the last time we changed styles. We'll also need to know what style we're supposed to be using at any given moment as we loop through the DataGridItems.

To make all this work we will override the Render (which is already done for us by default) and RenderContents methods of the DataGrid control. This will allow us a huge amount of control over what is rendered to the browser, but it is also easy to make one little tiny mistake that will prevent the control from rendering anything useful at all. Now that we're all confident that we never make any mistakes, let's override our second base class method.

C#

protected override void Render(HtmlTextWriter output)
{
    output.Write("");
}

protected override void RenderContents(HtmlTextWriter output)
{

}

VB

Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
    output.Write("")
End Sub

Protected Overrides Sub RenderContents(ByVal output As System.Web.UI.HtmlTextWriter)

End Sub

First let's take care of the Render method. This is the method that will write out the HTML necessary to start our TABLE (including the I.D., the style tag, and anything else). The HtmlTextWriter is a great tool in that it allows us to pile up some HTML attributes and then call RenderBeginTag with our tag name as a parameter. When we do that the HtmlTextWriter outputs a properly-formatted opening HTML tag with all the attributes that we piled up before we called RengerBeginTag. This means we don't have to concantenate a bunch of strings together and try to get all the quotations and syntax straight.

We know the first thing that we'll need is our opening TABLE tag. So we'll use the AddAttribute method of the HtmlTextWriter object called output to render our control's "id" property. In addition, we'll need to get our hands on all the style information the developer might have set for our DataGridPlus. Luckily this information is at our fingertips. We can use the ControlStyleCreated property of our control to find out whether or not a ControlStyle has been created for our control. The ControlStyle is simply one object that will encompass every property that affects the style of our control including any font, border, or color information. As long as our control has a ControlStyle object we can simply call ControlStyle.AddAttributesToRender method and we're done. Our ControlStyle will pass all of the HTML style information we need to whatever HtmlTextWriter object we pass to it. We can also handle those developers that make good use of style sheets by throwing in one more line of code and using the AddAttribute method of the HtmlTextWriter class to output the name of the CssClass the developer specified.

After our opening TABLE tag has been generated it is time to output all of the rows and then close up our TABLE. Since we decided to override RenderContents all we have to do is call that method (passing it our Render method's HtmlTextWriter object) and then we'll close up our TABLE tag by calling RenderEndTag.

C#

protected override void Render(HtmlTextWriter output) {
    // Create the ID attribute of our TABLE
    output.AddAttribute("id", this.ID);

    // Create the style attribute
    if (this.ControlStyleCreated && this.ControlStyle != null)
    {
        ControlStyle.AddAttributesToRender(output);
    }

    // Support style sheets
    if (this.CssClass != string.Empty) output.AddAttribute("class", CssClass);

    // Render our <table> tag
    output.RenderBeginTag("table");

    // Render all of the rows
    this.RenderContents(output);
    output.RenderEndTag();
}

VB

Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
    'Create the ID of our DataGridPlus
    output.AddAttribute("id", Me.ID)

    'Create the style attribute
    If Me.ControlStyleCreated AndAlso Not IsNothing(Me.ControlStyle) Then
        Me.ControlStyle.AddAttributesToRender(output)
    End If

    'Support style sheets
    If Me.CssClass <> "" Then output.AddAttribute("class", CssClass)

    'Render our <table> tag
    output.RenderBeginTag("table")

    'Render all of the rows
    Me.RenderContents(output)
    output.RenderEndTag()
End Sub

Now that our Render method is complete we'll need to take care of RenderContents. This method is in charge of building the HTML necessary to represent all of the rows (DataGridItems) in our table whether the row is a Header, Footer, Separator, Item, AlternatingItem, or the like. Since our purpose for this control is to alternate the rows' styles every x rows we'll need to introduce a couple of variables to help us through.

First we'll need to know how many rows we have rendered with the current style. We'll do this by declaring an Int16. We'll also obviously have to know what style we're supposed to use on each row as we render it. The ItemStyle and AlternatingItemStyle are just properties of the DataGrid that return TableItemStyle objects, so we'll create our own private TableItemStyle object that will hold the style we should be using as we loop through our DataGridItems.

The ASP.NET DataGrid contains a control called DataGridTable which serves as the container for all of our rows that we'll render. Therefore, we don't expect to loop through the Controls collection of our DataGridPlus expecting to find DataGridItems because we won't find any. With that in mind, we'll loop through the controls that belong to the first child control of our DataGridPlus - the DataGridTable. As we loop through the DataGridItems we'll be looking for those items that are of type Item or AlternatingItem. When we find a DataGridItem of these types we'll start modifying styles. If we find an item of another type, we'll need to be good control creators and output THOSE item styles as well. We just won't be modifying them.

C#

protected override void RenderContents(HtmlTextWriter output)
{
    Int16 rw = 0; // The number of rows that have had our style applied.
    TableItemStyle _style = this.ItemStyle;  // The style we are to apply to the DataGridItem

    if (HasControls())
    {
        foreach (DataGridItem r in Controls[0].Controls)
        {
            if (r.ItemType == ListItemType.Item || r.ItemType == ListItemType.AlternatingItem)
            {
                rw += 1;
                if (rw > _rows) // Have we rendered enough rows at the current style?
                { // Switch from the style we were using to the alternate style
                    if (_style == this.AlternatingItemStyle)
                        { _style = this.ItemStyle; }
                    else
                        { _style = this.AlternatingItemStyle; }
                    rw = 1; //Reset our row counter
                }
                _style.AddAttributesToRender(output);
            }
            else
            {
                switch (r.ItemType)
                {
                    case ListItemType.Header:
                        this.HeaderStyle.AddAttributesToRender(output);
                        break;

                    case ListItemType.Footer:
                        this.FooterStyle.AddAttributesToRender(output);
                        break;

                    case ListItemType.Pager:
                        this.PagerStyle.AddAttributesToRender(output);
                        break;

                    case ListItemType.EditItem:
                        this.EditItemStyle.AddAttributesToRender(output);
                        break;

                    case ListItemType.SelectedItem:
                        this.SelectedItemStyle.AddAttributesToRender(output);
                        break;
                }
            }
            output.RenderBeginTag("tr");

            // Render the cells for this DataGridItem
            for (int w = 0; w < r.Controls.Count; w++)
            {
                r.Controls[w].RenderControl(output);
            }
            output.RenderEndTag();
        }
    }
}

VB

Protected Overrides Sub RenderContents(output As HtmlTextWriter)
    Dim rw As Int16 = 0 ' The number of rows that have had our style applied.
    Dim _style As TableItemStyle = Me.ItemStyle ' The style we are to apply to the DataGridItem
    If HasControls() Then
        Dim r As DataGridItem
        For Each r In Controls(0).Controls
            If r.ItemType = ListItemType.Item Or r.ItemType = ListItemType.AlternatingItem Then
                rw += 1
                If rw > _rows Then ' Have we rendered enough rows at the current style?
                ' Switch from the style we were using to the alternate style
                If _style Is Me.AlternatingItemStyle Then
                    _style = Me.ItemStyle
                Else
                    _style = Me.AlternatingItemStyle
                End If
                rw = 1 'Reset our row counter
                End If
                _style.AddAttributesToRender(output)
            Else
                Select Case r.ItemType
                Case ListItemType.Header
                    Me.HeaderStyle.AddAttributesToRender(output)

                Case ListItemType.Footer
                    Me.FooterStyle.AddAttributesToRender(output)

                Case ListItemType.Pager
                    Me.PagerStyle.AddAttributesToRender(output)

                Case ListItemType.EditItem
                    Me.EditItemStyle.AddAttributesToRender(output)

                Case ListItemType.SelectedItem
                    Me.SelectedItemStyle.AddAttributesToRender(output)
                End Select
            End If
            output.RenderBeginTag("tr")

            ' Render the cells for this DataGridItem
            Dim w As Integer
            For w = 0 To r.Controls.Count - 1
                r.Controls(w).RenderControl(output)
            Next
            output.RenderEndTag()
        Next
    End If
End Sub

Now for the big moment. Compile the DataGridPlus project because we'll be using it right away.

To test our control we'll add it to the toolbox in Visual Studio. Open webform1.aspx and then right-click the toolbox and choose Customize Toolbox. Select the .NET Framework Components tab and then click the Browse button. By default Visual Studio saves new projects in its own folder inside My Documents (if it's not a web application). We'll need to navigate to the folder containing our DataGridPlus control's bin directory. Inside the bin folder should be our new DataGridPlus.dll file. Double-click that and it should appear in the list of controls. If we click Ok we should see our new control in the toolbox. Drag one of them onto webform1.aspx.

We'll need a reference to System.Data.SqlClient in the code-behind of webform1.aspx. Then we'll simply bind our DataGridPlus to the Northwind database's Employee Sales By Country stored procedure. This sproc requires two parameters (indicating the beginning date and end date to serve as record selection criteria). Don't forget to pick an AlternatingItemStyle and/or an ItemStyle after you drop our new control onto the form.

C#

private void Page_Load(object sender, System.EventArgs e)
{
    // Create our connection and command
    SqlConnection _cn = new SqlConnection("Data Source=nemserver; Initial Catalog=Northwind; User ID=sa;Password=mulgrew");
    SqlCommand _cmd = new SqlCommand("Employee Sales By Country", _cn);
    _cmd.CommandType = CommandType.StoredProcedure;

    // Set up our parameters
    SqlParameter _prm1 = _cmd.Parameters.Add(new SqlParameter("@Beginning_date", SqlDbType.DateTime));
    _prm1.Direction = ParameterDirection.Input;
    _prm1.Value = Convert.ToDateTime("1/1/1970");
    SqlParameter _prm2 = _cmd.Parameters.Add(new SqlParameter("@Ending_date", SqlDbType.DateTime));
    _prm2.Direction = ParameterDirection.Input;
    _prm2.Value = Convert.ToDateTime("12/31/2010");
    _cn.Open();

    // Bind our grid
    SqlDataReader _sdr = _cmd.ExecuteReader(CommandBehavior.CloseConnection);
    DataGridPlus1.DataSource = _sdr;
    DataGridPlus1.DataBind();
    _sdr.Close();
}

VB

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    'Create our connection and command
    Dim _cn As SqlConnection = New SqlConnection(ConfigurationSettings.AppSettings("ConnectionString"))
    Dim _cmd As SqlCommand = New SqlCommand("Employee Sales By Country", _cn)
    _cmd.CommandType = CommandType.StoredProcedure

    'Set up our parameters
    Dim _prm1 As SqlParameter = _cmd.Parameters.Add(New SqlParameter("@Beginning_date", SqlDbType.DateTime))
    _prm1.Direction = ParameterDirection.Input
    _prm1.Value = CDate("1/1/1970")
    Dim _prm2 As SqlParameter = _cmd.Parameters.Add(New SqlParameter("@Ending_date", SqlDbType.DateTime))
    _prm2.Direction = ParameterDirection.Input
    _prm2.Value = CDate("12/31/2010")
    _cn.Open()

    'Bind our grid
    Dim _sdr As SqlDataReader = _cmd.ExecuteReader(CommandBehavior.CloseConnection)
    DataGridPlus1.DataSource = _sdr
    DataGridPlus1.DataBind()
    _sdr.Close()
End Sub

Provided everything went as planned we should now be able to build and browse to webform1 in our web application and see the results of our efforts. I chose a light green as my alternating BackColor and you can see my results below.

CountryLastNameFirstNameShippedDateOrderIDSaleAmount
UKBuchananSteven7/16/1996 12:00:00 AM10248440
UKSuyamaMichael7/10/1996 12:00:00 AM102491863.4
UKSuyamaMichael8/16/1996 12:00:00 AM10274538.6
USADavolioNancy8/9/1996 12:00:00 AM10275291.84
USACallahanLaura8/14/1996 12:00:00 AM10276420
USAFullerAndrew8/13/1996 12:00:00 AM102771200.8
USACallahanLaura8/16/1996 12:00:00 AM102781488.8
USACallahanLaura8/16/1996 12:00:00 AM10279351
USAFullerAndrew9/12/1996 12:00:00 AM10280613.2
USAPeacockMargaret8/21/1996 12:00:00 AM1028186.5
USAPeacockMargaret8/21/1996 12:00:00 AM10282155.4
USALeverlingJanet8/23/1996 12:00:00 AM102831414.8
USAPeacockMargaret8/27/1996 12:00:00 AM102841170.38
USADavolioNancy8/26/1996 12:00:00 AM102851743.36
USACallahanLaura8/30/1996 12:00:00 AM102863016
USACallahanLaura8/28/1996 12:00:00 AM10287819
USAPeacockMargaret9/3/1996 12:00:00 AM1028880.1
UKKingRobert8/28/1996 12:00:00 AM10289479.4
USACallahanLaura9/3/1996 12:00:00 AM102902169
UKSuyamaMichael9/4/1996 12:00:00 AM10291497.52
USADavolioNancy9/2/1996 12:00:00 AM102921296
USADavolioNancy9/11/1996 12:00:00 AM10293848.7
USAPeacockMargaret9/5/1996 12:00:00 AM102941887.6
USAFullerAndrew9/10/1996 12:00:00 AM10295121.6



Marketplace
(Sponsored Links)
What are the green links?
   



 
Copyright © 2007 CMP Tech LLC |
Privacy Policy (4/10/06) | Your California Privacy Rights (4/10/06) | Terms of Service | Advertising Info | About Us | Help