5,421,850 members and growing! (17,403 online)
Email Password   helpLost your password?
Web Development » ASP.NET » General     Beginner License: The Code Project Open License (CPOL)

GridView Enhancements and Fixes

By M. Shehabeddeen

Enhancements and Fixes that add features: more control over column widths, preventing text wrapping, formatting Auto-Generated Columns.
Javascript, CSS, HTML, XHTML, .NET (.NET 1.1, .NET 2.0, .NET), ASP.NET

Posted: 15 Jun 2008
Updated: 3 Aug 2008
Views: 6,383
Bookmarked: 24 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
17 votes for this Article.
Popularity: 5.50 Rating: 4.47 out of 5
3 votes, 17.6%
1
0 votes, 0.0%
2
0 votes, 0.0%
3
1 vote, 5.9%
4
13 votes, 76.5%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Note: Please vote.


Introduction

Have you ever needed to control the widths of your columns in a GridView especially when the GridView's width is relative to the container (for example: width="100%") and the columns keep getting the total width of the GridView distributed among them? All you want is for each column to take just enough width to display its contents and the remaining width can be allocated to the last column, but that did not work did it? Have you ever tried using the Wrap="False" in a GridView's RowStyle or HeaderStyle and were surprised it did not work in all browsers? This article is my take at solving these 2 issues, and it works in both IE and FF.

In addition, I present a method to solve a problem when using Automatically Generated columns in the GridView. As is known, there is a Property of the GridView AutoGenerateColumns that when set to true would generate the columns automatically from the DataSource. The problem is that we have limited control (if any) on those generated columns. So for example if the DataSource contains a Date column the displayed data would not show as formatted, showing for example (1/1/2008 12:00:00 AM) instead of just (1/1/2008) or (01/01/2008), etc... That is a problem because we cannot (directly) change the formatting to meet our requirements. The best way to solve this would be to inherit from GridView and customize GridView to our liking. For those (like me) that don't want to deal with another extra Reference in their project, I have another way.

Background

My solution depends on javascript and DOM. I am using Element Injection into the existing HTML. The code is not that complex and you don't need to be scared by these intimidating terms. You would need though to know a bit of javascript and what DOM (Document Object Model) actually means. If you find you lack such knowledge, a few minutes with Google will save you.

For the Auto-Generated Columns problem, my solution depends on reflection to set values of members that don't have public accessibility. Reflection has a performance penalty that at situations can be afforded. If you find that my solution has weak performance (which I did not notice neither did I test for) then try the alternative: Inheritance.

Illustration

Following are a few images that illustrate what my solution does:

This is what the sample code would look like without my solution
NoneFixed.jpg


This is how it looks like with Columns formatted, and Wrapping not changed
WrappingNotFixed.jpg

This is how it looks like with Columns formatted and Wrapping fixed for the header
WrappingFixedHeader.jpg


This is when all features enabled (wrapping fixed for both Header and Data rows)
AllFixed.jpg

This is how Date fields appear without formatting
AutoGenColsNoFormatting.jpg


This is how Date fields appear after formatting is applied
AutoGenColsWithFormatting.jpg

Column Widths and Wrapping

ColumnWidthsAndWrapping.aspx

There are 2 parts to the solution, the javascript which resides in the aspx page, and the code that registers calls to those functions, which resides in the Code-Behind of that page.

Let us start with the javascript functions:

function formatGrid(el)
{
    el = document.getElementById(el);               
    var numOfCols = el.getElementsByTagName("TR")[1].getElementsByTagName("TD").length;                
    var colGrp = document.createElement("colgroup");                
    var col = document.createElement("col");
    col.setAttribute("span", numOfCols-1);
    col.setAttribute("width", "1");
    col.setAttribute("white-space", "nowrap");
    col.setAttribute("padding-left", "2");
    col.setAttribute("padding-right", "2");
    col.setAttribute("border-width", "2");
    colGrp.appendChild(col);                
    el.insertBefore(colGrp, el.firstChild);                
}

The function formatGrid receives the id of an element in the document, which in our case would normally be the GridView. What this function does is format the GridView so that each column get just enough width to display its contents and the LAST column gets all the remaining width. Remember we are usually dealing here with a GridView that has a width of 100%. The code is relatively clear, I will just point out the following:

  1. We are getting the number of columns in the GridView numOfCols and using numOfCols - 1 later.
  2. The ColGroup element is responsible for grouping columns and assigning them common properties.
  3. The Col element is a child of the ColGroup element and will override what is set on the ColGroup element and that is why it is sufficiant to set attributes on the Col element (I am using simple terms, for more info check http://www.w3.org/TR/html401/struct/tables.html#h-11.2.4.1).
  4. The span attribute indicates how many columns the Col element will affect, in our case we need to restrict the width of all the columns except the last one.
  5. The only required attributes are the span and the width attributes, the others are optional.

The following functions solve the wrapping part of the problem, where we would need something like "First Name" in the header of the GridView to appear on the same line and not wrap and split ver 2 lines. Also this applies to the Data Rows where the data can be long and thus be forced to wrap to another line thus being split across 2 or more lines. We might need them to appear as a unit on one line. Now using the following functions it is easier:

function addNoWrapSpan(el, noWrap)
{
    var span = document.createElement("span");
    while(el.childNodes.length > 0)
    {
        var child = el.firstChild;
        el.removeChild(child);
        span.appendChild(child);
    }                
    if(noWrap)
    span.style.whiteSpace = "nowrap";
    else
    span.style.whiteSpace = "inherit";
    el.appendChild(span);
}
function setNoWrap(el, noWrapTH, noWrapTD)
{
    el = document.getElementById(el);
    if(noWrapTH)
    {
        var allTHs = el.getElementsByTagName("TH");                
        for(var i = 0; i < allTHs.length; i++)
        {
            addNoWrapSpan(allTHs[i], noWrapTH);
        }
    }
    if(noWrapTD)
    {
        var allTDs = el.getElementsByTagName("TD");                
        for(var i = 0; i < allTDs.length; i++)
        {
            addNoWrapSpan(allTDs[i], noWrapTD);
        }
    }
} 
The first function addNoWrapSpan moves all elements inside the provided element el (be it a td or th) to a dynamically generated span element that has the proper style depending on whether noWrap is true or false. The style used is:
span.style.whiteSpace = "nowrap"; 

if noWrap is true and if it is false then the following style is set:

span.style.whiteSpace = "inherit";
setNoWrap receives 3 parameters: el which is the element that we are configuring, noWrapTH that tells us whether or not to allow wrapping for the child TH's, and noWrapTD indicating whether or not to allow wrapping on the TDs inside the element el. As you can notice, setNoWrap calls addNoWrapSpan where necessary.

The attached sample project includes a sample GridView:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" Width="100%" CssClass="GridClass">
    <Columns>
        <asp:BoundField DataField="FirstName" HeaderText="First Name" />
        <asp:BoundField DataField="LastName" HeaderText="Last Name" />               
        <asp:BoundField DataField="Age" HeaderText="Age" />
        <asp:CommandField ShowSelectButton="True" />
    </Columns>
</asp:GridView>

Simply it displays the data set in the code behind and also has a CommandField which has a select LinkButton. Now the Code Behind:

    private DataTable BuildDataSource()
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("FirstName", typeof(string));
        dt.Columns.Add("LastName", typeof(string));
        dt.Columns.Add("Age", typeof(int));
        
        DataRow dr;
        
        dr = dt.NewRow();
        dr["FirstName"] = "John the first";
        dr["LastName"] = "Doe";
        dr["Age"] = 23;
        dt.Rows.Add(dr);

        dr = dt.NewRow();
        dr["FirstName"] = "Clark";
        dr["LastName"] = "Kent";
        dr["Age"] = 28;
        dt.Rows.Add(dr);

        return dt;
    }

This is a simple way to show data for test purposes, this will be replaced by code to retrieve data from the database in real case scenarios.

protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        string formatScript = "";
        formatScript += string.Format("formatGrid('{0}');setNoWrap('{0}', false, true);", GridView1.ClientID);        
        ClientScript.RegisterStartupScript
            (this.GetType(), "format", formatScript, true);
    }

This is where our javascript is put to use. Notice that I am calling setNoWrap and assigning false for the noWrapTH parameter, this means that the header will still wrap if its contents are long, you probably will need to change that to true.

Automatically Generated Columns

AutoGeneratedColumns.aspx

Here I describe a method that allows us to format Dates inside Automatically Generated Columns (AutoGenerateColumns = true).

private void FormatDatesInGridView(GridView gv, GridViewRow gvr)
    {
        if (gv.DataSource != null)
        {
            DataTable dt = null;

            if (gv.DataSource is DataView)
            {
                dt = ((DataView)gv.DataSource).Table;
            }
            else
                if (gv.DataSource is DataSet)
                {
                    dt = ((DataSet)gv.DataSource).Tables[0];
                }
                else
                    if (gv.DataSource is DataTable)
                    {
                        dt = (DataTable)gv.DataSource;
                    }

            if (gvr.RowType == DataControlRowType.DataRow)
            {
                foreach (TableCell tc in gvr.Cells)
                {
                    DataControlFieldCell dcfc = (DataControlFieldCell)tc;
                    if (dcfc.ContainingField is AutoGeneratedField)
                    {
                        AutoGeneratedField agf = (AutoGeneratedField)dcfc.ContainingField;
                        if (agf.DataType == typeof(DateTime))
                        {
                            System.Reflection.FieldInfo fi;
                            fi = typeof(BoundField).GetField("_dataFormatString", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
                            fi.SetValue(agf, "{0:dd/MM/yyyy}");

                            fi = typeof(DataControlField).GetField("_statebag", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
                            ((StateBag)fi.GetValue(agf))["DataFormatString"] = "{0:dd/MM/yyyy}";

                            System.Reflection.MethodInfo mi;
                            mi = typeof(BoundField).GetMethod("OnFieldChanged", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                            mi.Invoke(agf, null);
                        }

                    }
                }
            }
        }
    } 

The method called FormatDatesInGridView is to be called from inside the RowCreated event handler of the GridView to be formatted. This method receives 2 parameters, the GridView and the current row. The calling event handler would look like:

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)
{
    if(e.Row.RowType == DataControlRowType.DataRow)
        FormatDatesInGridView(GridView1, e.Row);
}

The method first determines the DataSource of the GridView (could be modified if you have other types as DataSource). Then it checks all cells of the current GridViewRow and if the type of the ContainingField of the AutoGeneratedField is DateTime we do the following using reflection:

  • set _dataFormatString to "{0:dd/MM/yyyy}" (could be changed to meet requirements)
  • set Viewstate["DataFormatString"] to the same value "{0:dd/MM/yyyy}" by setting the value of _statebag["DataFormatString"]
  • call the OnFieldChanged method to apply these changes (this method eventually causes Binding to occur).
That's it, the Date fields are now formatted. How I came up with these steps is by using reflector and checking how the classes work internally.

Update History

22 June 2008: Added the Illustration section.
03 August 2008: Auto-Generated Columns

License

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

About the Author

M. Shehabeddeen


Web Developer, ASP.NET.
Love Computer Graphics and all its descendents.
Used to be an addictive gamer.
Knowledge seeker in most of its forms.

MCP in Developing Web Applications using ASP.NET, and in XML Web Services and Server Components.

Location: Lebanon Lebanon

Other popular ASP.NET articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 13 of 13 (Total in Forum: 13) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralGood approachmemberAdam Tibi1:15 6 Aug '08  
GeneralRe: Good approachmemberM. Shehabeddeen3:12 6 Aug '08  
GeneralWhy JavaScript?memberexistenz_23:40 4 Aug '08  
GeneralRe: Why JavaScript?memberM. Shehabeddeen0:06 5 Aug '08  
GeneralRe: Why JavaScript?memberexistenz_2:54 5 Aug '08  
GeneralNice ArticlememberMehdi Payervand1:46 4 Aug '08  
Generaluseful tips.memberRajib Ahmed11:25 24 Jun '08  
GeneralGreat job, useful for me!memberguaike4:23 22 Jun '08  
GeneralRe: Great job, useful for me!memberM. Shehabeddeen9:56 22 Jun '08  
GeneralGood One!memberIndy200510:15 20 Jun '08  
GeneralRe: Good One!memberM. Shehabeddeen9:53 21 Jun '08  
GeneralThanks Manmembermohamadabdou3:53 17 Jun '08  
GeneralRe: Thanks ManmemberM. Shehabeddeen4:01 17 Jun '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 3 Aug 2008
Editor: Chris Maunder
Copyright 2008 by M. Shehabeddeen
Everything else Copyright © CodeProject, 1999-2008
Web20 | Advertise on the Code Project