Friday, June 15, 2012

Insert Sub-header Rows and footers for the merged rows dynamically in Grid view Using C#

Insert Sub-header Rows and footers for the merged rows dynamically in Grid view Using C#

   Came across a scenario where i need to create Subheader 1, Subheader 2 and Subfooter 1 based on category,Name and Cost for each row. Initially i thought to achieve this using datatable with row comparing ,row  but if i proceed that performance will be extremely bad. As a developer we need to develop code with max to adhere NFR's(Non Functional Requirements mainly: performance for me). So, I didn't proceed with datatable logic & changed my views to onRowDatabound event for the same.

  I googled a lot & came across an excellent reference <http://www.carlj.ca/2008/04/20/adding-or-inserting-subheader-rows-into-a-gridview/>. I took the base logic from this & built in my stuff on top of them. 
  
  My grid view looks like below  (note: no changes to onrowdatabound event):
 
The below image is the modified version of above image which i want. Here i grouped the records based on category first as sub-header1 and grouped against Name as Sub-header 2 from sub-header 1 and grouped cost against Name as Sub-Footer1.










First Let us place the grid view in the page as below which is the easiest stuff in the whole logic.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default4.aspx.cs" Inherits="Default4" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h3>Restaurant Listing</h3>
        <asp:gridview id="gdvRestaurants" autogeneratecolumns="false"
                      onrowdatabound="gdvRowDatabound" cellpadding="5"
                      width="860" runat="server">
            <headerstyle backcolor="#507CD1" forecolor="white" />
            <rowstyle font-size="Small" />
            <alternatingrowstyle backcolor="#DCE4F9" />
            <emptydatarowstyle font-bold="true" />
            <emptydatatemplate>
                No Restaurants were found.
            </emptydatatemplate>
            <columns>
                <asp:boundfield datafield="RestaurantId" headertext="Id" />
                <asp:boundfield datafield="Category" headertext="Category" />
                <asp:boundfield datafield="Name" headertext="Name" />
                <asp:boundfield datafield="Phone" headertext="Phone" />
                <asp:boundfield datafield="Address" headertext="Address" />
                <asp:boundfield datafield="Zip" headertext="Zip" />
                <asp:boundfield datafield="Cost" headertext="Cost" />
            </columns>            
        </asp:gridview>
    </div>
    </form>
</body>
</html>

  If you notice clearly, in the grid view we are invoking an event as OnRowDataBound - performs the core logic. This event is the heart for entire logic. As well ensure to turn off the autogeneratecolumns.
Declared some of the variables in class level and my page load looks like below:

// Used to store the last Category Name between each RowDataBound
    private string tmpCategoryName = "";
    private string tmpName = "";
    private string tmpCost = "";
    decimal TotalCost = (decimal)0.0;
    Decimal SubTot = (decimal)0.0;
    decimal GrandTot = (decimal)0.0;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            // GetRestaurants() returns a DataSet of Restaurants.
            this.gdvRestaurants.DataSource = GetRestaurants();
            this.gdvRestaurants.DataBind();
        }
    }

GetRestaurants() rturns dataset which contains restaurant info keyed in manually as below:

Note: Included New row as cost for footer purpose.
protected DataSet GetRestaurants()
    {
        DataSet ds = new DataSet();

        DataRow dr;

        DataTable dtRestaurants = new DataTable("Restaurants");
        dtRestaurants.Columns.Add("RestaurantId");
        dtRestaurants.Columns.Add("Category");
        dtRestaurants.Columns.Add("Name");
        dtRestaurants.Columns.Add("Phone");
        dtRestaurants.Columns.Add("Address");
        dtRestaurants.Columns.Add("City");
        dtRestaurants.Columns.Add("State");
        dtRestaurants.Columns.Add("Zip");
        dtRestaurants.Columns.Add("Cost");
        dtRestaurants.PrimaryKey = new DataColumn[] { dtRestaurants.Columns["RestaurantId"] };

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 1;
        dr["Category"] = "Fast Food";
        dr["Name"] = "Burger King";
        dr["Phone"] = "510 793-6434";
        dr["Address"] = "39156 Paseo Padre Pkwy";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "125";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 2;
        dr["Category"] = "Fast Food";
        dr["Name"] = "Burger King";
        dr["Phone"] = "510 794-0440";
        dr["Address"] = "5245 Mowry Ave";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "125";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 3;
        dr["Category"] = "Fast Food";
        dr["Name"] = "Carl's Jr Restaurant";
        dr["Phone"] = "510-490-0150";
        dr["Address"] = "44388 Old Warm Springs Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "150";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 4;
        dr["Category"] = "Fast Food";
        dr["Name"] = "Classic Diner";
        dr["Phone"] = "510-651-7241";
        dr["Address"] = "39403 Fremont Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "175";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 5;
        dr["Category"] = "Italian";
        dr["Name"] = "Lucia's Pizza & Italian Restaurant";
        dr["Phone"] = "510-796-4430";
        dr["Address"] = "700 Mowry Ave D";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94536";
        dr["Cost"] = "200";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 6;
        dr["Category"] = "Italian";
        dr["Name"] = "Massimo's Deli & Trattoria";
        dr["Phone"] = "510-792-2000";
        dr["Address"] = "5200 Mowry Ave";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "200";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 7;
        dr["Category"] = "Italian";
        dr["Name"] = "Spin-a-Yarn Restaurant";
        dr["Phone"] = "(510)656-9141";
        dr["Address"] = "45919 Warm Springs Boulevard";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "200";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 8;
        dr["Category"] = "Japanese";
        dr["Name"] = "Happi House Teriyaki";
        dr["Phone"] = "510-656-9123";
        dr["Address"] = "40559 Fremont Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 9;
        dr["Category"] = "Japanese";
        dr["Name"] = "Koma Restaurant";
        dr["Phone"] = "510-797-8533";
        dr["Address"] = "39158 Argonaut Way";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "220";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 10;
        dr["Category"] = "Japanese";
        dr["Name"] = "Toyko Ichiban";
        dr["Phone"] = "510-353-1133";
        dr["Address"] = "47890 Warm Springs Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94539";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 11;
        dr["Category"] = "Pizza";
        dr["Name"] = "America's Pizza";
        dr["Phone"] = "510-656-0294";
        dr["Address"] = "40645 Fremont Blvd 20";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "250";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 12;
        dr["Category"] = "Pizza";
        dr["Name"] = "Bronco Billys Pizza Palace #1";
        dr["Phone"] = "510-792-1070";
        dr["Address"] = "37651 Niles Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94536";
        dr["Cost"] = "250";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 13;
        dr["Category"] = "Pizza";
        dr["Name"] = "New York Pizza";
        dr["Phone"] = "510-770-0123";
        dr["Address"] = "41300 Fremont Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "250";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 14;
        dr["Category"] = "Seafood";
        dr["Name"] = "Champion Teppanyaki";
        dr["Phone"] = "510-770-0918";
        dr["Address"] = "46851 Warm Springs Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94539";
        dr["Cost"] = "500";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 15;
        dr["Category"] = "Seafood";
        dr["Name"] = "Fortune Seafood Restaurant";
        dr["Phone"] = "510-713-0228";
        dr["Address"] = "3625 Thornton Ave";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94536";
        dr["Cost"] = "500";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 16;
        dr["Category"] = "Seafood";
        dr["Name"] = "Nile's Grill And Alehouse";
        dr["Phone"] = "510-494-1863";
        dr["Address"] = "37378 Niles Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94536";
        dr["Cost"] = "500";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 17;
        dr["Category"] = "Seafood";
        dr["Name"] = "My Thai Restaurant";
        dr["Phone"] = "510-797-8980";
        dr["Address"] = "34265 Fremont Blvd";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94555";
        dr["Cost"] = "500";
        dtRestaurants.Rows.Add(dr);

        dr = dtRestaurants.NewRow();
        dr["RestaurantId"] = 18;
        dr["Category"] = "Seafood";
        dr["Name"] = "Tavin's Thai Bbq & Grill";
        dr["Phone"] = "510-792-0770";
        dr["Address"] = "39170 State St";
        dr["City"] = "Fremmont";
        dr["State"] = "CA";
        dr["Zip"] = "94538";
        dr["Cost"] = "500";
        dtRestaurants.Rows.Add(dr);

        ds.Tables.Add(dtRestaurants);


        return ds;
    }

We declared few variables above Page_Load()
  Above the Page_Load(), add the following line.

// Used to store the last Category Name,Name and Cost between each RowDataBound
private string tmpCategoryName = "";
private string tmpName = "";
private string tmpCost = "";

//Used to calculate total cost for single row & multiple rows inside grouping:
  decimal TotalCost = (decimal)0.0;
  decimal SubTot = (decimal)0.0;


We wil refer these variables as below:
 Check whether the current row’s category is the same as the previous row’s category. If it’s not the same, then we will need to create the sub header1 row. If it is the same as the previous row, then we can skip creating the sub header 1.
 Check whether the current row’s Name is the same as the previous row’s Name for the added category. If it’s not the same, then we will need to create the sub header2 row. If it is the same as the previous row, then we can skip creating the sub header2. 
 Check whether the current row’s Cost value is not "" then store that value in TotalCost and add as Subfooter1 (Note: If subheader2 contains 1 child row).
 If subheader2 contains more than 1 child row then remove the added subfooter and sum the totalcost as subtoatl and add as Subfooter1.

If we were to declare this variable within the OnRowDatabound, it would always be start off being empty, and therefore we wouldn’t be able to compare it’s value to the current row’s value. So, these variables are acting as global variable.

Let us take a look on our OnRowDataBound:
  1. Check rowtype, we need to proceed only if it is datarow to perform the row inclusion else not.

// If the current row is a DataRow (and not a Header or Footer row), then do stuff.
if (e.Row.RowType == DataControlRowType.DataRow)
{
}

  After this check we need to assign theh values to our globalvariables in the order of category,Name,cost.
     a ) To perfom this we need to cast the current row as a DataRowView, and then compare it’s “category” field to the value in the tmpCategoryName variable. If the two value are not the same, then we will need to set the value of tmpCategoryName to the new value.
     b) Compare Name field to key in tmpName variable.If the two value are not the same, then we will need to set the value of tmpCategoryName to the new value. As well fetch the Cost field and feed the same in tmpCost variable for the corrresponding tmpnamevariable. (Note: To perform Sub Total for Single row under Name.)
     c) Compare Name field to key in tmpName variable.If the two value are same, then fetch the Cost field and feed the same in tmpCost variable for the corrresponding tmpnamevariable.
    (Note: To perform Sub Total for multiple row s under Name.)

Things to Consider:
  1. Row creations are done by GridviewRow.
  2. Column creations are done by TableCell.
  3. Values are feeded in columns using Span control innerHTml:

Let us see the most awaiting code over here:

protected void gdvRowDatabound(object sender, GridViewRowEventArgs e)
    {
        // If the current row is a DataRow (and not a Header or Footer row), then do stuff.
        if (e.Row.RowType == DataControlRowType.DataRow)
        {

            DataRowView drv = (DataRowView)e.Row.DataItem;

            if (tmpCategoryName != drv["Category"].ToString())
            {
                tmpCategoryName = drv["Category"].ToString();

                // Get a reference to the current row's Parent, which is the Gridview (which happens to be a table)
                Table tbl = e.Row.Parent as Table;
                if (tbl != null)
                {
                    GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
                  
                    TableCell cell = new TableCell();
                  
                    cell.ColumnSpan = this.gdvRestaurants.Columns.Count;
                    cell.Width = Unit.Percentage(100);
                    cell.Style.Add("font-weight", "bold");
                    cell.Style.Add("background-color", "#c0c0c0");
                    cell.Style.Add("color", "Black");

                    HtmlGenericControl span = new HtmlGenericControl("span");
                    span.InnerHtml = tmpCategoryName;

                    cell.Controls.Add(span);
                    row.Cells.Add(cell);
                    tbl.Rows.AddAt(tbl.Rows.Count - 1, row);
                }
            }
            if (tmpName != drv["Name"].ToString())
            {
                tmpName = drv["Name"].ToString();

                tmpCost = drv["Cost"].ToString();

                // Get a reference to the current row's Parent, which is the Gridview (which happens to be a table)
                Table tbl = e.Row.Parent as Table;

                if (tbl != null)
                {
                    GridViewRow row1 = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
                    GridViewRow row2 = new GridViewRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);


                    TableCell cell2 = new TableCell();
                    TableCell cell3 = new TableCell();

                    // Span the row across all of the columns in the Gridview

                    cell2.ColumnSpan = this.gdvRestaurants.Columns.Count;
                    cell2.Style.Add("font-size","20px");
                    cell3.ColumnSpan = this.gdvRestaurants.Columns.Count;
                    cell3.Style.Add("text-align", "right");

                    HtmlGenericControl span1 = new HtmlGenericControl("span");
                    span1.InnerHtml = tmpName;

                    if (tmpCost != "")
                    {
                        TotalCost = Convert.ToDecimal(tmpCost);
                    }

                    HtmlGenericControl span2 = new HtmlGenericControl("span");
                    span2.InnerHtml = "Sub Total:" + TotalCost.ToString();

                    cell2.Controls.Add(span1);
                    row1.Cells.Add(cell2);
                    tbl.Rows.AddAt(tbl.Rows.Count - 1, row1);

                    if (span2.InnerHtml != "")
                    {
                        cell3.Controls.Add(span2);
                        row2.Cells.Add(cell3);
                        tbl.Rows.AddAt(tbl.Rows.Count, row2);
                    }
                }
            }
            else if (tmpName == drv["Name"].ToString())
            {
                if (tmpCost != null)
                {
                    SubTot = Convert.ToDecimal(tmpCost) + Convert.ToDecimal(drv["Cost"].ToString());
                }
                Table tbl = e.Row.Parent as Table;

                if (tbl != null)
                {
                    GridViewRow row2 = new GridViewRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);
                    TableCell cell3 = new TableCell();
                    cell3.ColumnSpan = this.gdvRestaurants.Columns.Count;
                    cell3.Style.Add("text-align", "right");
                    HtmlGenericControl span2 = new HtmlGenericControl("span");
                    span2.InnerHtml = "Sub Total:" + SubTot.ToString();
                    cell3.Controls.Add(span2);
                    row2.Cells.Add(cell3);
                    tbl.Rows.RemoveAt(tbl.Rows.Count - 2);
                    tbl.Rows.AddAt(tbl.Rows.Count, row2);
                    tmpCost = "";
                }
            }
        }  
    }

  Indexes were played the key role for positioning as header or footer.

I hope this post will be a feast for the learners. I thank Carl J to restart my blog after 5 years and able to publish this post.

0 Comments:

Post a Comment

<< Home