Enhancing the DataGrid with Grouped Rows and Subheadings
 
Published: 21 Sep 2005
Unedited - Community Contributed
Abstract
The ASP.NET DataGrid control provides no methods by which to group related items. Michelle Beall presents three simple techniques to enhance a DataGrid by grouping related rows and adding subheadings.
by Michelle Beall
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 39130/ 57

Introduction

Often the need arises to be able to group data in a DataGrid under subheadings. I will discuss and demonstrate three methods wth which to achieve grouping and subheadings in a DataGrid.

  1. After the DataGrid has been populated, the column with the group names is updated so that each group name spans all corresponding rows.
  2. Before the DataGrid has been populated, the data source is manipulated to add extra rows to act as subheadings.
  3. While the DataGrid is being populated, an extra subheading row is added each time the value of the group name column changes.

With the first option, the resulting table would resemble the following:

With the second and third options, the resulting table would resemble the following:

Option 1: Grouping the Rows

This method must be credited to an article by BriOnH, "Grouping Rows in a DataGrid." I have made very minor modifications to the code (mostly renaming). The grouping effect is achieved after the DataGrid has been databound, by iterating through the DataGridItems and comparing the grouping column's item value with the value in the item above it. If they are the same then the rowspan of the cell above is increased and the current item cell is hidden.

Listing1: Grouping the Rows

public int GroupColumn(DataGrid dg, int ColumnIndex)
{
    int ItemIndex = 0;
    int Groupings = 0;
 
    foreach (DataGridItem dgi in dg.Items)
    {
        if (dgi.ItemIndex > 0)
        { 
            //if current cells text is the same as the cell above it
            //make it invisible and increase the row span by 1 of the 
            //last visible cell in that column.
            if (dgi.Cells[ColumnIndex].Text == 
                dg.Items[dgItem.ItemIndex-1].Cells[ColumnIndex].Text)
            {
                dgi.Cells[ColumnIndex].Visible = false;
                dg.Items[ItemIndex].Cells[ColumnIndex].RowSpan = 
                    dg.Items[ItemIndex].Cells[ColumnIndex].RowSpan + 1;
                Groupings++;
            }
            else if (dg.Items[dgItem.ItemIndex-1].Cells[ColumnIndex].Visible)
            {
                ItemIndex = dgi.ItemIndex;
            }
            else
            {
                dg.Items[ItemIndex].Cells[ColumnIndex].RowSpan = 
                    dg.Items[ItemIndex].Cells[ColumnIndex].RowSpan + 1;
                ItemIndex = dgi.ItemIndex;
            }
        }
    }
 
    dg.Items[dg.Items.Count-1].Visible = false;
 
    return Groupings;
}

Option 2: Adding Subheadings to the DataSource

This method is credited to an article by Dave Long, "Including Subheadings in a DataGrid." I have converted the code to C#.  In this option we're creating the illusion of adding rows to the DataGrid. What is really happening is that we're looping through the datasource item collection (whether it is DataRows in a DataTable or items in an array) and, when the grouping column's value changes, inserting a new datasource item at the current position to act as the subheading.

The data should first be retrieved and sorted by the field which you want to use for your subheading. For example, in a DataGrid displaying products we may want to separate the product data with category subheadings.

Once the data is retrieved and then sorted by category, we need to iterate through the DataTable rows to identify changes in the category column. Whenever a category change is found, a new subheading row is added to the DataTable.

Listing 2: Manipulating the DataSource

private void SeparateRows(ref DataSet ds, string columnName)
{
 int i = 0;
 string prevsub = "";
 while (i <= ds.Tables[0].Rows.Count - 1)
 {
  DataRow dr = ds.Tables[0].Rows[i];
  
  // if category field value changes add a new row
  if (dr["Category"].ToString() != prevsub)
  {
   prevsub = sub;
   DataRow newrow = ds.Tables[0].NewRow();
   newrow["Title"] = "SubHeading";      // sub heading flag
   newrow[columnName] = dr[columnName];  // sub heading text
   // add row and increment counter to accommodate new row
   ds.Tables[0].Rows.InsertAt(newrow, i++);
  }
  i++;
 }
}

After manipulating the DataSource, we bind it to the DataGrid using the standard DataBind() method. During this method we need to intercept each added subheading row and edit its style and formatting, which we do during the ItemDataBound event. This assumes that the DataGrid columns are bound in the same order as the sample code above, which is typically the case.

Listing 3: Styling the Subheadings

private void DataGrid1_ItemDataBound(object sender, DataGridItemEventArgs e)
{
    switch (e.Item.ItemType)
    {
        case ListItemType.AlternatingItem:
        case ListItemType.Item:
            if (e.Item.Cells[1].Text.Equals("SubHeading"))
            {
                // set sub heading text to visible cell
                e.Item.Cells[0].Text = e.Item.Cells[3].Text;
                //span cell across all columns
                e.Item.Cells[0].ColumnSpan = e.Item.Cells.Count;
                // remove cells to the right
                for (int i = e.Item.Cells.Count-1; i > 0; i--)
                    e.Item.Cells.RemoveAt(i);
                // format sub heading
                e.Item.Cells[0].Attributes.Add("align", "left");
                e.Item.Cells[0].Font.Bold = true;
                e.Item.BackColor = Color.FromArgb(220, 220, 220);
            }
            break;
        default:
            break;
    }
}

Option 3: Adding Subheadings to the DataGrid

This option expands on a code snippet I found at 123aspx.com.  This approach uses the DataGrid's ItemCreated event, and works by adding subheading rows to the DataGrid when it is detected that the next value of the grouping column is different from the current value. This option achieves the same look as the second option, but without having to manipulate the DataGrid’s data source before databinding. You need to use both the ItemCreated and ItemDataBound events. Since the ItemCreated event occurs before the ItemDataBound event, we need to set things up for the next DataGridItem.

Listing 4: Manipulating the DataGrid

private string subheading = "";
private bool createsub = false;
 
// This method will peek ahead in the datasource to determine whether the next
// item will need a new subheading
private void DataGrid1_ItemDataBound(object sender, DataGridItemEventArgs e)
{
    switch (e.Item.ItemType)
    {
        case ListItemType.Header:
        case ListItemType.AlternatingItem:
        case ListItemType.Item:
            DataTable dt = (DataTable)((DataGrid)sender).DataSource; 
            // header or Subheading column value will change in next row
            if ((e.Item.ItemIndex == -1) ||
               (e.Item.ItemIndex+1 < dt.Rows.Count &&
                dt.Rows[e.Item.ItemIndex+1]["SubHeading"].ToString() != 
                dt.Rows[e.Item.ItemIndex]["SubHeading"].ToString()))
            {
                // get next subheading value
                subheading = dt.Rows[e.Item.ItemIndex+1]["SubHeading"].ToString();
                createsub = true;
            }
            else
                createsub = false;
            break;
    }
}
 
// This method will create a subheading row if needed
private void DataGrid1_ItemCreated(object sender, DataGridItemEventArgs e)
{
    switch (e.Item.ItemType)
    {
        case ListItemType.AlternatingItem:
        case ListItemType.Item:
            if (createsub)
            {
                DataGrid dg = (DataGrid)sender;
                TableCell tc = new TableCell();
                tc.Controls.Add(new LiteralControl(subheading));
                tc.ColumnSpan = e.Item.Cells.Count;
                tc.Attributes.Add("align", "left");
                tc.Font.Bold = true;
                tc.BackColor = Color.FromArgb(220, 220, 220);
                DataGridItem di = 
                    new DataGridItem(e.Item.ItemIndex+1, 0, ListItemType.Item);
                di.Cells.Add(tc);
                Table t = (Table)dg.Controls[0];
                t.Rows.Add(di);
            }
            break;
        default:
            break;
    }           
}

I hope that these methods will be useful tools in your future projects. A working sample of each option is available in the accompanying code download. For simplicity, the code samples use an Access database rather than a SQL Server database.



User Comments

No comments posted yet.






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-29 4:57:04 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search