When it comes to subtotals in Gridviews, I could never find a great writeup to help me out so I used to complete it using Crystal Reports which made it easy.  I finally had some time  to do some more research (I’m not a programmer by trade, I just whip up some reports via asp.net) so this may not be the smartest way to do this.  I found great instructions in C HERE, so I converted it to VB and modified to fit my needs.  If you program in C it might be easier to look at that one, but i have tried to add as many comments as I could here.   There are 2 things we do here:

1.)    Figure out when to add the subtotal. In my example, when it becomes a new year. I need to insert a subtotal row before it creates a new row.  I do this in the Gridview’s RowDataBound.  This is where you would normally add totals to the footer.

2.)    Use a Function to return a row, and insert that row into the gridview before it writes the next year.


Here’s the layout of my table, as you can see, the way that I have the query(table) ordered is by year, then month. As the gridview reads the data and writes the rows, it needs to determine when the year changes so it can write the totals


Here’s how to accomplish this:
First I start off by figuring out what year it is:  I create 2 global variables (top of the page) like so:

Dim strLastYear, strCurYear As String

Then in my page Load I set them both to the current year. This will make sure that it does not add a row at the beginning of the gridview.

strCurYear = Year(Date.Now).ToString
strLastYear = Year(Date.Now).ToString


Then I needed some global variables to hold my totals and subtotals. 

Dim decTotalCost, decTotalSales, decSubCost, decSubSales As Decimal


Now in The Gridview’s rowdatabound, as it’s reading data from the database,  I need to first make sure it’s a data row, then I grab the year from the current row that it is reading from my table, and find out what year it is.

Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
'determine if we need to subtotal, so if the year hasn't changed, then we just need to add the subtotals, not create the subtotal row
strCurYear = Convert.ToString(DataBinder.Eval(e.Row.DataItem, "Year"))
If strCurYear = strLastYear Then
decsubSales += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Sales"))
decsubCost += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Cost"))


Now we need to account for if it’s a new year.  If it’s a new year we want to do a few things:

  1. 1.      Create a temporary table and set it equal to the gridview
  2. 2.      Get the index (row) that we are on.
  3. 3.      Create the new row using a function called CreateGridviewRow  which will match the color, and use the cost, num of columns (see below)
  4. 4.      Add the new row to the current Gridview at the correct location
  5. 5.      Clear the subtotals so we can start summing up next years totals, then start adding the new years totals


'if it's a new year then we create a temporary table and set it to the gridview, after that we grab the row, then we use the function to create a new row
Dim tbltemp As Table = Me.GridView1.Controls(0)
Dim intindex = tbltemp.Rows.GetRowIndex(e.Row)
Dim gvrSubTotal As GridViewRow = CreateGridviewRow(4, intindex, "#F7F6F3", 2, "Total for " + strLastYear, 20, decsubSales.ToString("c"), decsubCost.ToString("c"))

'add the row to the gridview
tbltemp.Controls.AddAt(intindex, gvrSubTotal)

'zero out the subtotals from last year since we are done
decsubSales = 0
decsubCost = 0

'since the year has changed and we haven't yet added the subtotals we need to add them after the previous years totals have been zeroed out (above)
decsubSales += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Sa-Sales"))
decsubCost += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Cost"))
End If


Now  add the running Totals that will be used in the footer

'Add TotalSales to the running Total Variables
decSales += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Sa-Sales"))
decCost += Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "Cost"))
strLastYear = Convert.ToDecimal(DataBinder.Eval(e.Row.DataItem, "sqlyear"))


We’re not out of the water yet, we not only have to write the subtotal above the footer, and then put our running totals in the footer as well. 


So when we get to the footer, the first thing we want to do is write the subtotals for the last year.  So we basically use the same code as above where we use the function to create the last subtotal row before the gridview writes the footer


ElseIf e.Row.RowType = DataControlRowType.Footer Then

'For the Footer Display the Running Totals
Dim tbltemp As Table = Me.GridView1.Controls(0)
Dim intindex = tbltemp.Rows.GetRowIndex(e.Row)

'this is for the last subtotal row
Dim gvrSubTotal As GridViewRow = CreateGridviewRow(4, intindex, "#F7F6F3", 2, "Total for " + strLastYear, 20, decsubSales.ToString("c"), decsubCost.ToString("c"))
tbltemp.Controls.AddAt(intindex, gvrSubTotal)

'zero out the subtotals
decsubSales = 0
decsubCost = 0

'write the running totals and a description to the footer
e.Row.Cells(2).Text = decSales.ToString("c")
e.Row.Cells(2).HorizontalAlign = HorizontalAlign.Right
e.Row.Cells(3).Text = decCost.ToString("c")
e.Row.Cells(3).HorizontalAlign = HorizontalAlign.Right

'zero out the running totals
decSales = 0
decCost = 0

'reset the curyear and last year
strCurYear = Year(Date.Now).ToString
strLastYear = Year(Date.Now).ToString
End If
End Sub


Now we can look at the Function Called CreateGridviewRow . This will take parameters like subtotal, Color, num of columns and create a table row to be returned to the gridview. We need to take into account the following:

  1. 1.      IntNumCells = how many cells are in the gridview – I don’t use this here, but it would be used to figure out how many  cells are in the gridview, so you can ignore it
  2. 2.      Icurrentindex = what row are we on in the gridview
  3. 3.      Strtablebackcolor = match the color of the gridvew
  4. 4.      Incolumnspan = this is used for the text in the footer, so for instance, if our first column in the gridview is year (column 0), and our second column is month (column 1) we would set the column span to 2 so the text (strtabletext) spans across 2 rows
  5. 5.      Strtabletext = this is what is in the footer, so for instance “Totals for 2011”
  6. 6.      inTableHeight = what is the height of the table
  7. 7.      strSales and StrCost = The subtotal values that we will write to the row



Private Function CreateGridviewRow(ByVal intNumCells As Integer, ByVal icurrentindex As Integer, ByVal strtablebackcolor As String, ByVal intColumnspan As Integer, ByVal strTableText As String, ByVal intTableHeight As Integer, ByVal strSales As String, ByVal strCost As String) As GridViewRow
'this function creates a subtotal row for the gridview

'create the gridview row using the parameters we passed to this function
Dim gvrTemp As New GridViewRow(icurrentindex, icurrentindex, DataControlRowType.Separator, DataControlRowState.Normal)

'now we create the cells to be added to the gvrTemp above
'Create The title of the row spanning 2 columns, using the parameters above
Dim celltemp As TableCell = New TableCell 'title of row
celltemp.BackColor = System.Drawing.ColorTranslator.FromHtml(strtablebackcolor)
celltemp.Font.Bold = True
celltemp.ColumnSpan = intColumnspan
celltemp.HorizontalAlign = HorizontalAlign.Left
celltemp.Text = strTableText
celltemp.Height = Unit.Pixel(intTableHeight)

'Create the sales cell
Dim celltemp2 As TableCell = New TableCell 'title of row
celltemp2.BackColor = System.Drawing.ColorTranslator.FromHtml(strtablebackcolor)
celltemp2.Font.Bold = True
celltemp2.ColumnSpan = 1
celltemp2.HorizontalAlign = HorizontalAlign.Right
celltemp2.Text = strSales.ToString
celltemp2.Height = Unit.Pixel(intTableHeight)

'Create the Cost cell
Dim celltemp3 As TableCell = New TableCell 'title of row
celltemp3.BackColor = System.Drawing.ColorTranslator.FromHtml(strtablebackcolor)
celltemp3.Font.Bold = True
celltemp3.ColumnSpan = 1
celltemp3.HorizontalAlign = HorizontalAlign.Right
celltemp3.Text = strCost
celltemp3.Height = Unit.Pixel(intTableHeight)

'now that we have all cells, added to the gridview row return the row
Return gvrTemp
End Function


That should be it. You should get something that looks like this (created this in excel but it’s what the gridview should look like). Hope this helps