Click here to Skip to main content
Click here to Skip to main content

"One size fits all" solution for freezing a Grid's header rows, why not?

By , 23 Jan 2013
 
Freezing_TableHeader_Demo.zip
GridHeaderFreezingDemo.suo
GridHeaderFreezingDemo
App_Code
App_Data
NORTHWND.MDF
NORTHWND_log.ldf
bin
AjaxControlToolkit.dll
GridHeaderFreezing.dll
CSS
Images
asc.gif
bg.gif
desc.gif
Images
collapse.gif
collapse_disabled.gif
expand.gif
expand_disabled.gif
gradient_background.png
grid_header_background.gif
icon_calendar.gif
icon_freeze.png
icon_freeze_inactive.png
InProcess.gif
loading.gif
stripped_background.gif
Thumbs.db
JS
Thumbs.db
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Globalization;
using System.Text;
using System.Collections.Generic;

public partial class FrozenGridHeader_Solution3 : System.Web.UI.Page
{
    #region Variables
    private string[] _dateFormats = { "dd/MM/yyyy", "yyyyMMdd" };
    private static DataProvider _controller;
    /// <summary>
    /// Stores the identifications of buttons.
    /// <para>Will be used to enable/disable all buttons.</para>
    /// </summary>
    System.Collections.Generic.LinkedList<String> _buttons;
    /// <summary>
    /// Number of visible columns
    /// </summary>
    private int _VisibleColumns = -1;
    private static DateTime? _from;
    private static DateTime? _to;
    #endregion
    #region Properties
    /// <summary>
    /// Number of visible columns
    /// </summary>
    private int VisibleColumns
    {
        get
        {
            if (_VisibleColumns == -1)
            {
                _VisibleColumns = 0;
                for (int i = 0; i < gridOrders.Columns.Count; i++)
                {
                    if (gridOrders.Columns[i].Visible) _VisibleColumns++;
                }
            }
            return _VisibleColumns;
        }
    }
    #endregion
    #region Page Events
    protected void Page_Init(object sender, EventArgs e)
    {
        btnSearch.Click += new EventHandler(btnSearch_Click);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        PrepareExpandableLines();
        SetupGridHeaderFreezing();
    }

    private void SetupGridHeaderFreezing()
    {
        HeaderFreezeParameters parentGridPara = new HeaderFreezeParameters();
        parentGridPara.GridCss_Class = "gridview_style";
        parentGridPara.HeaderRowOnSight_CssClass = "parentgrid_header_row";
        parentGridPara.FreezingPosition = 0; //Freeze table at the top of browser
        parentGridPara.ShowTableFreezingIcon = true; //Show the freezing icon

        HeaderFreezeParameters childGridPara = new HeaderFreezeParameters();
        childGridPara.GridCss_Class = "childgridview_style";
        childGridPara.HeaderRowOnSight_CssClass = "HeaderRowOnSight";
        childGridPara.HeaderRowOutOfSight_CssClass = "HeaderRowOutOfSight";
        childGridPara.FreezingPosition = 80; //80 is the height of header rows of parent grid. Child grid will freeze at the point which is under the top of browser as "80px".
        childGridPara.ShowTableFreezingIcon = true;

        List<HeaderFreezeParameters> objGridHeaderFreezeSettingList = new List<HeaderFreezeParameters>();
        objGridHeaderFreezeSettingList.Add(parentGridPara);
        objGridHeaderFreezeSettingList.Add(childGridPara);

        GridHelper.FreezeTableHeader(objGridHeaderFreezeSettingList);
    }

    /// <summary>
    /// Releases allocated memory
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void Page_UnLoad(object sender, EventArgs e)
    {
        if (_controller != null)
        {
            _controller.Dispose();
            _controller = null;
        }
    }
    #endregion
    #region Grid Events
    /// <summary>
    /// Function : AddMergedCells
    /// Purpose: Adds merged cell in the header
    /// </summary>
    /// <param name="objgridviewrow"></param>
    /// <param name="objtablecell"></param>
    /// <param name="colspan"></param>
    /// <param name="celltext"></param>
    private static void AddMergedCells(GridViewRow objgridviewrow, TableCell objtablecell, int colspan, string celltext)
    {
        objtablecell.ColumnSpan = colspan;
        if(celltext != "")
            objtablecell.Text = celltext;

        objgridviewrow.Cells.Add(objtablecell);
    }

    protected void gridOrders_RowCreated(object sender, System.Web.UI.WebControls.GridViewRowEventArgs e) 
    {
        if (e.Row.RowType == DataControlRowType.Header)
        {
            //e.Row.Cells[0].RowSpan = 2;
            //e.Row.Cells[1].RowSpan = 2;
            //e.Row.Cells[2].RowSpan = 2;
            //e.Row.Cells[3].RowSpan = 2;
            //e.Row.Cells[4].RowSpan = 2;

            ////TableCell mergeCell = new TableCell();
            ////mergeCell.Text = "Ship Information";
            ////mergeCell.ColumnSpan = 2;
            //e.Row.Cells[5].ColumnSpan = 2;

            ////Creating a gridview row object
            //GridViewRow objgridviewrow = new GridVieRow(1, 0, DataControlRowType.Header, DataControlRowState.Insert);

            ////Creating a table cell object
            //TableCell objtablecell;
            //objtablecell = e.Row.Cells[6];
            //AddMergedCells(objgridviewrow, objtablecell, 1);
            ////e.Row.Cells.RemoveAt(6);

            //objtablecell = e.Row.Cells[7];
            //AddMergedCells(objgridviewrow, objtablecell, 1);
            ////e.Row.Cells.RemoveAt(7);

            //e.Row.Cells[6].RowSpan = 2;
            //e.Row.Cells[7].RowSpan = 2;

            //gridOrders.Controls[0].Controls.AddAt(0, objgridviewrow);

            //Creating a gridview row object
            GridViewRow objgridviewrow = new GridViewRow(1, 0, DataControlRowType.Header, DataControlRowState.Insert);
            objgridviewrow.CssClass = "parentgrid_header_row";
            
            //Creating a table cell object
            TableCell objtablecell = new TableHeaderCell();
            objtablecell.RowSpan = 2;
            objtablecell.Text = e.Row.Cells[0].Text;
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 1, "");

            objtablecell = new TableHeaderCell();
            objtablecell.RowSpan = 2;
            objtablecell.Text = e.Row.Cells[1].Text;
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 1, "");

            objtablecell = new TableHeaderCell();
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 3, "Basic Information");

            objtablecell = new TableHeaderCell();
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 2, "Ship Information");

            objtablecell = new TableHeaderCell();
            objtablecell.RowSpan = 2;
            objtablecell.Text = e.Row.Cells[7].Text;
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 1, "");

            objtablecell = new TableHeaderCell();
            objtablecell.RowSpan = 2;
            objtablecell.ApplyStyle(e.Row.Cells[0].ControlStyle);
            AddMergedCells(objgridviewrow, objtablecell, 1, "");

            gridOrders.Controls[0].Controls.AddAt(0, objgridviewrow);
            e.Row.Cells.RemoveAt(0); //Remove the first column
            e.Row.Cells.RemoveAt(0); //Remove the second column
            e.Row.Cells.RemoveAt(5); //Remove the 7th column
            e.Row.Cells.RemoveAt(5); //Remove the 8th column
        }

        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            Label lblRowNo = (Label)(e.Row.Cells[0].Controls[0]);

            //Set Label text equal to the rowIndex +1 
            lblRowNo.Text = (e.Row.RowIndex + 1).ToString();
        }
    }

    /// <summary>
    /// Called after the main grid was bound
    /// <para>Will create a Javascrip array, used to enable/disable the buttons</para>
    /// <para>See function EnableExpandButtons in ExpandPanel.js</para>
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void gridOrders_DataBound(object sender, EventArgs e)
    {
        if (_buttons != null && _buttons.Count > 0)
        {
            foreach (string element in _buttons)
            {
                ScriptManager.RegisterArrayDeclaration(
                    this.Page,
                    "ExpandButtons", // The array's name
                    String.Format(CultureInfo.InvariantCulture, "'{0}'", element));  // The button ID
            }
        }

        //String arrValue = "\"10284\", \"11077\", \"10251\"";
        ScriptManager.RegisterArrayDeclaration(this, "amoutCells", _arrAmountCells);

        //Re setup Freezing function
        //SetupFrozenGridHeaderRow();
        SetupGridHeaderFreezing();
    }

    private string _arrAmountCells = "";
    //private string[] _arrCellWidth = new string[20];
    /// <summary>
    /// Called for each row of the main grid, calls a method to prepare the details
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void gridOrders_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType != DataControlRowType.DataRow)
            return;

        try
        {
            //if (e.Row.RowIndex == 0)
            //{
            //    for (int j = 0; j < e.Row.Cells.Count; j++)
            //    {
            //        _arrCellWidth[j] = e.Row.Cells[j].Style["width"];
            //    }
            //}

            //if (e.Row.RowIndex == 1)
            //{
            //    for (int j = 0; j < e.Row.Cells.Count; j++)
            //    {
            //        e.Row.Cells[j].Style.Add("width", _arrCellWidth[j]);
            //    }
            //}

            Order orderRow = e.Row.DataItem as Order;
            GridViewRow gridRow = e.Row;
            
            Label lblOrderAmount = gridRow.FindControl("lblOrderAmount") as Label;
            _arrAmountCells = string.Format("{0}\"{1}:{2}\"", (_arrAmountCells == "" ? "" : _arrAmountCells + ","), lblOrderAmount.ClientID, orderRow.OrderID);
            lblOrderAmount.ID = orderRow.OrderID.ToString();
            lblOrderAmount.Text = String.Format("{0:#,##0;($#,##0);Zero}", _controller.SumOrderAmount(orderRow.OrderID));

            BuildNestedPanel(gridRow, orderRow);
        }
        catch (System.Exception ex)
        {
            ShowErrorMessage(ex.Message);
        }
    }
    #endregion
    #region ObjectDataSource Events
    /// <summary>
    /// Instantiates the business logic layer object, in order to fill the main grid
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void odsOrders_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
    {
        try
        {
            _controller = new DataProvider();

            e.ObjectInstance = _controller;
        }
        catch (System.Exception ex)
        {
            ShowErrorMessage(ex.Message);
        }
    }

    /// <summary>
    /// Select simulated or data base stored data
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void odsOrders_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    {
        btnSearch_Click(null, null);

        if (e.ExecutingSelectCount) return;

        e.InputParameters["from"] = _from;
        e.InputParameters["to"] = _to;
    }

    /// <summary>
    /// This method is called when the object odsStores releases memory used by the business logic layer object.
    /// <para>If some error was found when reading from database, this method will show the error message.</para>
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void odsOrders_ObjectDisposing(object sender, ObjectDataSourceDisposingEventArgs e)
    {
        DataProvider controller = e.ObjectInstance as DataProvider;

        if (controller != null && controller.Error)
        {
            ShowErrorMessage(controller.Exception);
            gridOrders.Visible = false;
        }

        e.Cancel = true;
    }
    #endregion
    #region Control Events
    protected void btnSearch_Click(object sender, EventArgs e)
    {
        try
        {
            //Parameters Handling:
            //Parse parameters and pass on to the underlying ObjectDataSource
            txtFromDate.Text = txtFromDate.Text.Replace("__/__/____", "");
            txtToDate.Text = txtToDate.Text.Replace("__/__/____", "");

            if (string.IsNullOrEmpty(txtFromDate.Text))
                _from = null;
            else
                _from = DateTime.ParseExact(txtFromDate.Text, _dateFormats, CultureInfo.CurrentCulture, DateTimeStyles.None);

            if (string.IsNullOrEmpty(txtToDate.Text))
                _to = null;
            else
                _to = DateTime.ParseExact(txtToDate.Text, _dateFormats, CultureInfo.CurrentCulture, DateTimeStyles.None);
            //End Of Parameters Handling

            PrepareExpandableLines();
        }
        catch (System.Exception ex)
        {
            ShowErrorMessage(ex.Message);
        }
    }
    #endregion
    #region Private Methods
    #region Dynamically populate data on demand
    /// <summary>
    /// Register the script to expand/contract details in order to show order details
    /// </summary>
    private void PrepareExpandableLines()
    {
        // First, handle some specifics from Firefox
        // Reference : IE/Firefox show/hide/colspan
        //             http://forums.codewalkers.com/client-side-things-82/ie-firefox-show-hide-colspan-902435.html

        string StyleDisplayBlock = "block";

        if (this.Request.Browser.Browser.ToLower() == "firefox")
        {
            StyleDisplayBlock = "table-row";
        }
        
        //Added by Henry Truong, 2012/10/30
        if (Request.UserAgent.ToLower().IndexOf("chrome") > 0)
        {
            StyleDisplayBlock = "table-row";
        }

        string js = String.Format(CultureInfo.InvariantCulture,
            "var StyleDisplayBlock = '{0}';\n;"+
            "var ExpandableLines = '{1}';\n",
            StyleDisplayBlock, pnlExpandableLines.ClientID);

        ScriptManager.RegisterClientScriptBlock(this.Page, this.Page.GetType(), "StyleDisplayBlock", js, true);

        // Then, include the script
        ScriptManager.RegisterClientScriptInclude(
            this.Page,
            this.Page.GetType(),
            "expand_models",
            "JS/ExpandPanel.js");
    }

    /// <summary>
    /// Builds a string to populate the expanded line with the models
    /// </summary>
    /// <param name="contextKey">CustomerId + radSimulatedData.Checked, separated by '-'</param>
    /// <returns>HTML table filled with drilled down data</returns>
    [System.Web.Services.WebMethod(CacheDuration = 45, Description = "Get data")]
    [System.Web.Script.Services.ScriptMethod]   
    public static string DataDrillDownService(string contextKey)
    {
#if DEBUG
            // Small delay to provide time to show 'loading.gif'
            System.Threading.Thread.Sleep(1000);
#endif

        StringBuilder sb = new StringBuilder();

        System.Collections.Generic.List<OrderDetail> objOrderDetails = null;

        // Extracts OrderID and 'chkCachedData.Checked' from the context key

        string[] keys = contextKey.Split('-');

        bool cachedData = (keys[1][0] == '1');

        int orderID = 0;

        bool hasError = false;

        if (!Int32.TryParse(keys[0], out orderID))
        {
            // TODO: Error LOG
            hasError = true;
            sb.Append("<span class=\"error-models\">ERROR : Can not load the detail data</span>");
        }
        else
        {
            try
            {
                objOrderDetails = (System.Collections.Generic.List<OrderDetail>)(new DataProvider()).GetOrderDetails(orderID);
            }
            catch (System.Exception ex)
            {
                // TODO: Error LOG
                hasError = true;
                sb.Append("<span class=\"error-models\">ERROR : Can not get the data. Details: " + ex.Message + "</span>");
            }

            if (!hasError)
            {
                if (objOrderDetails == null || objOrderDetails.Count == 0)
                {
                    sb.Append("<span class=\"notfound-models\">Order details do not found for this order.</span>");
                }
                else
                {
                    sb.Append(PopulateDetailsTable(objOrderDetails));
                }
            }
        }

        if (objOrderDetails != null)
        {
            objOrderDetails = null;
        }

        return sb.ToString(); ;
    }

    /// <summary>
    /// Populates a HTML table with the order detail data
    /// </summary>
    /// <param name="topModels">Data about the order detail</param>
    /// <returns>HTML table filled with order details</returns>
    private static string PopulateDetailsTable(System.Collections.Generic.List<OrderDetail> objOrderDetails)
    {
        StringBuilder sb = new StringBuilder("<table id='tblDrillDown' class = 'childgridview_style'> \n"
        + "<tr height='40px' class='HeaderRowOnSight'> \n"
        + "<th>ProductName</th> \n"
        + "<th>Unit Price</th> \n"
        + "<th>Quantity</th> \n"
        + "<th>Discount</th> \n"
        + "</tr> \n",
        objOrderDetails.Count * 180);

        foreach (OrderDetail orderDetail in objOrderDetails)
        {
            sb.Append("<tr> \n");

            sb.AppendFormat(CultureInfo.CurrentCulture,
                "<td>{0}</td> ",
                orderDetail.ProductName);

            sb.AppendFormat(CultureInfo.CurrentCulture,
                "<td class=\"cellright\">{0}</td> ",
                orderDetail.UnitPrice);

            sb.AppendFormat(CultureInfo.CurrentCulture,
                "<td class=\"cellright\">{0:n2}</td> ",
                orderDetail.Quantity);

            sb.AppendFormat(CultureInfo.CurrentCulture,
                "<td class=\"cellcenter\">{0}</td> ",
                orderDetail.Discount);

            sb.Append("</tr> \n");
        }

        sb.Append("</table> \n");

        return sb.ToString();
    }

    /// <summary>
    /// Shows a generic error message to the user
    /// </summary>
    private void ShowErrorMessage(string message)
    {
        lblMsgErroTitulo.Text = "ERROR";
        lblMsgErro.Text = "Can not query the database. Error: " + message;

        popupMsgErro.Show();
    }

    /// <summary>
    /// Prepares to show the details for one order.
    /// <para>A new hidden row is inserted below the current row in the grid.</para>
    /// <para>That row will have only one cell, spanned to the entire row.</para>
    /// <para>Inside the new row, a panel is inserted as a simple HTML 'fieldset', just to show a border and some header.</para>
    /// <para>Inside the fieldset, a new panel is inserted. That panel will be filled with details when the user click the button.</para>
    /// </summary>
    /// <param name="gridViewRow">One row from the main grid</param>
    /// <param name="orderRow">One order</param>
    private void BuildNestedPanel(GridViewRow gridViewRow, Order orderRow)
    {
        // Gets the index for the new row
        Table htmlTab = gridOrders.Controls[0] as Table;

        int newRowIndex = htmlTab.Rows.GetRowIndex(gridViewRow) + 1;

        // Creates two panels : external and internal

        // External panel : A simple <fieldset>
        System.Web.UI.WebControls.Panel externalPanel = new Panel();
        externalPanel.EnableViewState = false;
        externalPanel.GroupingText = "Order Detail";
        externalPanel.ID = String.Format(CultureInfo.InvariantCulture, "pDrillDownE_{0}", newRowIndex);
        externalPanel.CssClass = "expand-panel";

        // Internal panel : A <div> as a container for the nested grid
        System.Web.UI.WebControls.Panel internalPanel = new Panel();
        internalPanel.EnableViewState = false;
        internalPanel.ID = String.Format(CultureInfo.InvariantCulture, "pDrillDown_{0}", newRowIndex);

        externalPanel.Controls.Add(internalPanel);

        // Inserts the panels in one cell

        TableCell cell = new TableCell();
        //cell.HorizontalAlign = HorizontalAlign.Left;
        cell.BorderStyle = BorderStyle.None;
        cell.Controls.Add(externalPanel);
        cell.ColumnSpan = VisibleColumns;

        // Inserts the cell in a collection of cell (only one cell)

        TableCell[] cells = new TableCell[1];

        cells[0] = cell;

        // Inserts one row under the current row
        GridViewRow newRow = new GridViewRow(
            newRowIndex,
            newRowIndex,
            DataControlRowType.DataRow,
            DataControlRowState.Normal);

        newRow.ID = String.Format(CultureInfo.InvariantCulture, "linM_{0}", newRowIndex);

        newRow.Style.Add(HtmlTextWriterStyle.Display, "none");

        // Inserts the cell in the new row

        newRow.Cells.AddRange(cells);

        // Inserts the new row in the controls collection of the table (grid)

        htmlTab.Controls.AddAt(newRowIndex, newRow);

        string contextKey = String.Format(CultureInfo.InvariantCulture,
            "{0}-{1}",
            orderRow.OrderID, (chkCachedData.Checked ? 1 : 0));

        // Locates the button and prepares the 'onclick' event

        Image btn = gridViewRow.FindControl("imgModel") as Image;
        //CssClass = "shadowed"
        btn.Attributes.Add("onmouseover", "this.classList.add('shadowed')");
        btn.Attributes.Add("onmouseout", "this.classList.remove('shadowed')");
       
        //btn.Attributes.Add("onmouseover", "if(this.src.endsWith('Images/expand.gif')) this.src='Images/expand_mouseover.gif'");
        //btn.Attributes.Add("onmouseout", "if(this.src.endsWith('Images/expand_mouseover.gif')) this.src='Images/expand.gif'");

        btn.Attributes["onClick"] = String.Format(CultureInfo.InvariantCulture,
            "ExpandDetails('{0}', '{1}', '{2}', '{3}');",
            contextKey,
            newRow.ClientID,
            internalPanel.ClientID,
            btn.ClientID);

        // Insert the ID into the buttons list
        if (_buttons == null)
            _buttons = new System.Collections.Generic.LinkedList<string>();

        _buttons.AddLast(btn.ClientID);
    }
    #endregion
    #endregion
}

By viewing downloads associated with this article you agree to the Terms of use and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Pham Dinh Truong
Technical Lead SmartWeb Inc.
Vietnam Vietnam
I have been working in ASP.NET for more than 6 years. I've also been working on Java and Windows-based application development for more than 4 years. My core competences include ASP.NET Web Development, Design Patterns, UnitTest, SubSonic, Ajax, Asynchronous and Multi-threading. I'm particularly interested in building smart software with great Usability and rich User Experience.
 
Visit my blog at:
http://ontheflyweb.blogspot.com
Follow on   Google+

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130617.1 | Last Updated 24 Jan 2013
Article Copyright 2013 by Pham Dinh Truong
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid