Real World GridView: Two Headed & Grouping GridViews

By now you may have figured out when I say “soon” I mean relatively soon, and by “relatively” I meant relative to the rise and fall of empires.  I am finally posting part 2 of the grid view articles.  Also, I have posted the source code on Got Dot Net for both part 1 and part 2.

If you missed part 1, you can read it here:
https://blogs.msdn.com/mattdotson/articles/490868.aspx
You can find the source code here:
https://www.codeplex.com/ASPNetRealWorldContr

I got some good feedback from people about part 1, and I wanted to address some questions in part 2.  In this article, we will focus on manipulating the GridView to tweak its appearance.  I have two samples, “TwoHeadedGridView” and “GroupingGridView”.  Several people posted comments about trying to manipulate the GridView to have an extra header row or something like that, so in this example, we will create an extra header row.  In the GroupingGridView, we investigate grouping cells to make commonality in data more apparent.

TwoHeadedGridView

Often you need more than one header on a table, and fortunately this is fairly easy to implement.  We could conceptually do this from the page, but we’ll make this reusable.  Here is what our TwoHeadedGridView looks like:

We allow the page to set the text by exposing a HeaderText property, and we will add the additional row after data binding.  Adding the row after data binding ensures that it does not get wiped out by data binding.  The only real trick is getting access to the GridView’s inner table.

        protected Table InnerTable
{
get
{
if (this.HasControls())
{
return (Table)this.Controls[0];
}

                return null;
}
}

The inner table is just an ASP.NET Table control, and is the GridView’s only child control.

Now that we know how to get at the inner table, it makes it easy to manipulate it.  Here we just add a row to the table:

GridViewRow row = new GridViewRow(0, -1, DataControlRowType.Header, DataControlRowState.Normal);
TableCell th = new TableHeaderCell();

th.HorizontalAlign = HorizontalAlign.Center;
th.ColumnSpan = this.Columns.Count;
th.BackColor = Color.SteelBlue;
th.ForeColor = Color.White;
th.Font.Bold = true;
th.Text = this.HeaderText;
row.Cells.Add(th);

this.InnerTable.Rows.AddAt(0, row);

Now that you know how to modify the inner table, you can do what ever you want to it!!

GroupingGridView

Grouping data together makes it easy to see how data is related.  Taking the same data from the previous example, we have created a GridView to see how the authors are clustered by city and state.

You can easily see that there are many authors from California, and specifically Oakland.  This is starting to look like multi-dimensional data from a cube!!  Maybe someday I will (or someone else will) expand this concept to include drill-down and expandable/collapsible buttons.

All we ask from the page is that it set the GroupingDepth property, which tells us how many columns to try to group.  In the screen shot, it is set to 2.  If I had set it to only 1, then Oakland would not have spanned multiple rows.

The real trick to spanning cells is marking cells “not visible” instead of deleting them.  Both setting a cell’s Visible property to false and deleting the cell will cause the cell not to render any HTML, but only the Visible property is stored in viewstate so our changes are persisted across postbacks.  NOTE: It is important to realize that the server side Visible property is different than the CSS display and visibility attributes.

An important design feature is that the second, third, etc columns contain groups that are subsets of the column on their left.  For instance, we want to make sure that if Kansas also had a city named Rockville that it would not be grouped with Maryland’s Rockville.  We do this though recursion because it seemed like the most efficient implementation.  Here you can see our recursive function:

        private void SpanCellsRecursive(int columnIndex, int startRowIndex, int endRowIndex)
{
if (columnIndex >= this.GroupingDepth || columnIndex >= this.Columns.Count )
return;

            TableCell groupStartCell = null;
int groupStartRowIndex = startRowIndex;

            for (int i = startRowIndex; i < endRowIndex; i++)
{
TableCell currentCell = this.Rows[i].Cells[columnIndex];

                bool isNewGroup = (null == groupStartCell) || (0 != String.CompareOrdinal(currentCell.Text, groupStartCell.Text));

if (isNewGroup)
{
if (null != groupStartCell)
{
SpanCellsRecursive(columnIndex + 1, groupStartRowIndex, i);
}

                    groupStartCell = currentCell;
groupStartCell.RowSpan = 1;
groupStartRowIndex = i;
}
else
{
currentCell.Visible = false;
groupStartCell.RowSpan += 1;
}
}

            SpanCellsRecursive(columnIndex + 1, groupStartRowIndex, endRowIndex);
}

And that’s all for today.  Be sure to look at the full source code @ https://www.codeplex.com/ASPNetRealWorldContr