# Thursday, June 26, 2008

Binding to a GridView

ASP.Net data binding is a great technology for developers who want to create interactive web applications without writing a lot of code. For example, a developer can drop a GridView and a DataObject onto a web form, set a few properties and have a fully editable grid, allowing users to easily view and update data. 

Even sorting the grid data can be performed without writing any code.  Just set the GridView's AllowSorting property to "true" and set the SortExpression property of any "sortable" column. 

<asp:GridView
   ID="GridView1"
   runat="server"
   AllowSorting="true"

...

   <asp:BoundField
      DataField="Name"
      HeaderText="Product Name"
      SortExpression="Name" />

...

By default, the grid renders a link at the top of each sortable column.  Clicking this link toggles the sort order between 3 states: Ascending, Descending and No Sort. 


Sorting Limitations

But there is a "Gotcha".  The GridView can be bound to any set of data - that is, to any object that implements the System.Collections.IEnumerable interface.  This includes a DataTable, ArrayList, List and HashTable, among others.  However the automatic sorting only works when binding to a DataTable.

Personally, I prefer to work with and bind to generic Lists.  A List is more flexible than a DataTable and does a better job of abstracting the user interface from the back-end data source, making it easier to swap out one or the other.

But by binding to a List, I sacrifice the automatic sorting that happens when I bind to a DataTable.  Fortunately, it doesn't take a lot of code to implement sorting on a GridView bound to a generic List.

How to sort a GridView bound to a List

First, we set the GridView AllowSorting property and each column's SortExpression property as described above.  This provides "sort" links at the top of each column.  Unfortunately these links will not work properly - in fact, clicking them will generate an error.

To get this to work, you must do the following

  1. Add an extra "sortby" parameter of type string to your Select method.
  2. Add code to your sort method to sort the list before returning it (more on this later)
  3. Add a SortParameterName attribute to your ObjectDataSource.  The value of this parameter should be the same as the parameter you added to your Select method.

By setting the SortParameterName attribute, we are telling ASP.Net to pass sorting information to the Select method and which parameter to pass it to.  The Select method gets called when the grid loads or refreshes and whenever the user clicks the "sort" links at the top of each column.  Most of the time, the value passed to the sortby parameter is an empty string (indicating no sort order), but if the user clicks a "sort" link, this parameter will contain one of the following three values

  • "<SortField> ASC"
  • "<SortField> DESC"
  • ""

where <SortField> is the string specified in the SortExpression attribute of the column clicked.  With each click, the grid column cycles its sort between ascending order, descending order, and no sort.  The parameter value passed depends on which is currently the active sort. 


<asp:ObjectDataSource ID="ProductObjectDataSource" runat="server" 
      SelectMethod="GetProductsList"
      TypeName="DemoGridBusLayer.NWindBL"
      SortParameterName="sortBy">
</asp:ObjectDataSource>

Now, how do we sort a List?  More specifically, how do we sort a list when we don't know in advance on which column we are sorting or in which direction (ascending or descending)? 

A List has a Sort method, so we can call that.  But what does it mean to sort a list of objects?  An object has properties and we can sort on any one (or more) of those properties, as long as the property is of a type that can be sorted.  We need to specify on which object property we will be sorting.  To do this, we use an overload of the List.Sort method that accepts an IComparer object.  IComparer has a Compare method that tells the Sort method how to order each pair of objects in the list.  We can create a class that implements IComparer, override the Compare method and use reflection to determine at runtime on which property to sort.  The name of the property and the sort order (Ascending or Descending) can be passed into the class constructor. 

The code for this class (named GenericComparer) below:

using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Reflection;

namespace DemoGridBusLayer
{
    /// <summary>
    /// This class is used to compare any
    /// type(property) of a class for sorting.
    /// This class automatically fetches the
    /// type of the property and compares.
    /// </summary>
    public sealed class GenericComparer<T> : IComparer<T>
    {
        public enum SortOrder { Ascending, Descending };

        #region Constructors
        public GenericComparer(string sortColumn, SortOrder sortingOrder)
        {
            this._sortColumn = sortColumn;
            this._sortingOrder = sortingOrder;
        }

        /// <summary>
        /// Constructor when passing in a sort expression containing both the Sort Column and the Sort Order
        /// e.g., "BPCode ASC".
        /// </summary>
        /// <param name="sortExpression"></param>
        /// <remarks>
        /// This constructor is useful when using this with the ASP.NET ObjectDataSource,
        /// which passes the SortParameterName in this format
        /// </remarks>
        public GenericComparer(string sortExpression)
        {
            string[] sortExprArray = sortExpression.Split(" ".ToCharArray());
            string sortColumn = sortExprArray[0];
            SortOrder sortingOrder;
            sortingOrder = SortOrder.Ascending;
            if (sortExprArray.Length > 1)
            {
                if (sortExprArray[1].ToUpper() == "DESC")
                {
                    sortingOrder = SortOrder.Descending;
                }
            }

            this._sortColumn = sortColumn;
            this._sortingOrder = sortingOrder;
        }

        #endregion



        #region public properties
        /// <summary>
        /// Column Name(public property of the class) to be sorted.
        /// </summary>
        private string _sortColumn;
        public string SortColumn
        {
            get { return _sortColumn; }
        }

        /// <summary>
        /// Sorting order (ASC OR DESC)
        /// </summary>
        private SortOrder _sortingOrder;
        public SortOrder SortingOrder
        {
            get { return _sortingOrder; }
        }

        #endregion

        /// <summary>
        /// Compare two objects of the same class,
        /// based on the value of a given property
        /// </summary>
        /// <param name="x">First Object</param>
        /// <param name="y">Second Object</param>
        /// <returns>int</returns>
        public int Compare(T x, T y)
        {

            // User reflection to get the property
            PropertyInfo propertyInfo = typeof(T).GetProperty(_sortColumn);

            // Cast the property to IComparable, so we can use the built-in compare.
            IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
            IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);

            // Order depends on Asc vs Desc.
            if (_sortingOrder == SortOrder.Ascending)
            {
                return (obj1.CompareTo(obj2));
            }
            else
            {
                return (obj2.CompareTo(obj1));
            }
        }
    }
}


Within our select method, we can call the List's Sort method and pass in an instance of the GenericComparer class, specifying on which column and in which direction to sort the list.  The SelectMethod is shown below.  (The details of querying a database and storing results into a List of objects is omitted.)

        /// <summary>
        /// Get a sorted list of all products
        /// </summary>
        /// <param name="sortBy"></param>
        /// <returns></returns>
        public static List<Product> GetProductsList(string sortBy)
        {
            // Get a list of Business Processes
            List<Product> prodList = GetProductsList();

            // Sort list
            if (sortBy != "")
            {
                GenericComparer<Product> cmp = new GenericComparer<Product>(sortBy);
                prodList.Sort(cmp);
            }
            return prodList;

        }

    }
}

With a small amount of code, we can enable sorting of a List of objects bound to a GridView in the same way that sorting is enabled for a DataTable bound to a GridView.

Demo

You can download the entire sample described in this article here: DemoGridSort.zip (39.63 KB).  You will need the AdventureWorks sample SQL Server database for this demo.

Thursday, June 26, 2008 5:05:33 PM (GMT Daylight Time, UTC+01:00)
# Tuesday, June 24, 2008

Did you know that every time an Ajax control performs a partial postpack, every event in the life cycle of the control's containing page or pages fires?

To me, this seems counterintuitive - There is no refresh of the containing page or of the master page, yet the Page_Load of both events fire.

I ran into it when I witnessed some unexpected behavior in a colleague's Ajax control.  After some investigation I saw the behavior was caused by code in the Page_Load event handler.  I thought this was a bug until I learned it was by design.  So we ended up bracketing some of the Page_Load code, testing the value of Page.IsPostBack to prevent code from running when it should not.

Tuesday, June 24, 2008 3:51:57 PM (GMT Daylight Time, UTC+01:00)
# Sunday, June 22, 2008

I had a terrific time yesterday at the Lansing Day of .Net yesterday. 

This was the last in an ambitious string of community-sponsored events in Michigan and Ohio under the "Day of .Net" branding.  In the past two months, DODN events have been held in Wilmington, OH, Grand Rapids, MI, Cleveland, OH, and Lansing, MI.  I managed to make the two Michigan events but family commitments kept me from the ones in Ohio.

A Day of .Net event features numerous speakers (usually about 30) speaking on topics related to software development.  The primary focus is .Net development but peripheral topics are almost always included.  I heard a very good talk yesterday by Dan Rigsby on the agile methodology in which software was barely mentioned.

Prior to yesterday, I wondered if the Lansing event might be anticlimactic coming so soon after three similar events.  I worried for nothing.  In fact, the opposite was true.  They managed to attract an excellent group of speakers, a full slate of sponsors (meaning, among other things, many cool door prizes) one of the better facilities I've seen (Lansing Community College West Campus) and the mayor of Lansing.  People were generally excited about this event.  I've heard - but can't confirm - that the Day of .Net was covered by two TV stations.  Jeff McWhirter and his group did a great job putting this together.  I don't know who thought of inviting the mayor, but that was a good idea.

The best part of these events is interacting with the people in the community.  There was a lot of good discussions about various projects, the state of the industry, the role of the community and the various approaches to developing software. 

When it was over, many of us headed over to Jeff's house to celebrate into the night.  I left at around 11 and the place was still packed and the bonfire was blazing.  Mike Wood, an old friend from my Cincinnati days stayed at my house before heading home this morning.

I picked up some nice swag - a copy of Camtasia, a logo t-shirt, and a pint glass featuring the LDODN logo.  This morning, I noticed that the t-shirt includes the slogan "I was there" but the pint glass has a modified slogan "I think I was there".

Here are some photos of the day: Photos.

 

>Lansing Day of .Net, 21 June 2008 - I'll be there!
Sunday, June 22, 2008 5:13:40 PM (GMT Daylight Time, UTC+01:00)
# Tuesday, June 17, 2008

I've heard people suggest that developing web applications is easier than building Windows applications.  Common sense tells us that this is not true - The tools for Windows applications are far more powerful and give us access to the entire Windows API as well as APIs that access the Internet).  In addition, we typically don't need to deal with messy things like viewstate and session state when writing Windows apps.  Because Windows apps tend to be stateful, this stuff is far easier to design and code.

What most people mean when they say that Windows apps are easier is that user's expectations tend to be lower for web application.  Most users understand the limitations of building a web app and will not be as demanding when requesting features.

However the tools available are changing that.  With tools like Silverlight and Flash, we can now build web applications that have a look and feel similar to fat clients.  And the advances in databinding controls make it far easier to manage state in a web app than just a few years ago.

As a result, user expectations of web applications are increasing.  Why shouldn't they demand the same rich user experience they get from their desktop apps? 

Take a look at the Cooper Mini site (http://www.miniusa.com) where you can find a dealer or build a cooper with the options you want. 
This site was built using Flash.

Or view the memorabilia collection at HardRock.com (http://memorabilia.hardrock.com/) where the developers used Silverlight to provide a very cool way of viewing the collection.

The User interface of each of the above sites exceeds that of most Windows applications.

Each quarter, the line blurs more between web applications and desktop applications.  Soon users will no longer temper their expectations based on the platform on which you develop.

Will you be ready for these increased expectations?

Tuesday, June 17, 2008 2:26:34 PM (GMT Daylight Time, UTC+01:00)
# Sunday, June 15, 2008
 #
 

Today is Father's Day here in America.  I spent the day with my two sons.  We went to church, then to lunch, then back home, where my sons hung out with their friends and I stayed in the background, content in knowing that they are safe and happy.

I look back on the difficulties of my life the last few years - an unwelcome separation, move, and divorce - and I realize that the one good, stabilizing thing in my life is the love of my boys.  I don't know how I would have made it without them.  I try to give them as much as I can and I hope that someday they recognize that.  But I know that they have given to me something that I could never receive from any other source - a reason to keep going; a reason to believe; a reason to hope for a better future. 

I know my boys will never doubt the fact that I love them with all my heart; but I doubt they realize the positive impact they have just by being in my life.  I've tried to communicate this but words fall short.

Nick and Timmy, if you are reading this, I love you more each day.

And to all the fathers out there, I wish for you the ability to express to your children the love you feel.  May they never spend one second doubting that their father loves them.

Sunday, June 15, 2008 11:52:27 AM (GMT Daylight Time, UTC+01:00)
# Thursday, May 22, 2008

Last night at the Southeast Michigan .Net User Group (http://www.migang.org/), I saw a great presentation on Microsoft Silverlight.  Jason Beres of Infragistics was the presenter. 

In the past six months, I've probably seen half a dozen presentations on the topic.  What set this one apart from the others was the amount of code that was shown.  While other presentations featured on showing off the eye candy (such as streaming video and rotating objects) that Silverlight supports, Jason stepped through the code in each of his demos. 

It made the process of writing Silverlight applications seem less intimidating and now I am resolved to explore this technology shortly.

Thursday, May 22, 2008 5:43:18 PM (GMT Daylight Time, UTC+01:00)
# Sunday, May 11, 2008

WM Day of .Net May 10, 2008 - I'll be there!

Yesterday I drove to Grand Rapids for the West Michigan Day of .Net.

I enjoyed the event, despite suffering from heartburn most of the day,

Here is a brief summary of the best sessions I saw

How to Architect Rich Internet Applications

Microsoft architect Josh Holmes gave an overview of what makes up a good rich internet application and when you should attempt to build one.

Getting Started with the ASP.Net MVC Framework
Tim Wingfield of Quick Solutions discussed the MVC framework.  This one had a lot of code samples.

A trip around the block with Rhino.Mocks
This session (led by Quick's Steve Hartman) was more like a roundtable discussion than a presentation.   Steve went through some definitions; stepped through a code sample; then started a discussion with the attendees about different Mock frameworks.

Reliable Messaging in WCF
James Bender of Quick Solutions gave this demo which also contained some good code samples.

A similar event is scheduled next Saturday in Cleveland.   I doubt I'll make that one, but I plan to attend the recently-announced Lansing Day of .Net scheduled June 21.

Lansing Day of .Net, 21 June 2008 - I'll be there!
Sunday, May 11, 2008 8:42:02 AM (GMT Daylight Time, UTC+01:00)
# Thursday, May 8, 2008

Edward Tufte has spent a lifetime turning data into pictures and studying the best way to do so.

In his first (self-published) book The Visual Display of Quantitative Information, he describes what makes an excellent graph or map. 

Not all data sets are good candidates for charts.  For small data sets with exact values, Tufte recommends using tables.  However to compare values or present many pieces of data simultaneously, a graph is far superior.  Graphs, Tufte asserts, are most useful when showing complex data and displaying trends or observations that are not immediately obvious when the data is displayed in tabular form.  An excellent graph is one that is clear, precise and efficient - that is it "gives to the viewer the greatest number of ideas in the shortest time with the least ink in the smallest space."

Tufte provides some advice to accomplish this graphical excellence.  He introduces the concept of "Data-Ink" ratio.  This is the amount of information conveyed by a chart, relative to the amount of ink required to print that chart.  Generally, a graph can be improved by increasing its Data-Ink Ratio.  This can be accomplished by erasing non-data ink, such as unnecessary gridlines and labels; by erasing redundant data; and by labeling data directly, rather than forcing users to look up information in a legend.

Related to the Data-Ink ratio is his push for high data density - graphics that have maximum data per page, maximum data per square inch, and maximum data per amount of ink used.  As long as a graphic does not appear confusing, cluttered or overwhelming, you should pack as much information as you can into it.

Tufte warns against "chartjunk", his term for irrelevant text, lines, pictures or other decorations that contain no actual information.  This is ink that can be erased from a chart without reducing the amount of information in the chart.  Many graphs contain pictures, 3D effects and colors that don’t relate to the data.  Rather than enhancing the user’s understanding of the data, this “junk” distracts the user’s attention from the data, making the graph harder to understand.  Erasing chartjunk increases the Data-Ink ratio, which should be the goal of every designer of data graphics.

I appreciate that the book provides numerous examples of both the right way and the wrong way to represent data visually and that most of these examples came from real-world publication.  Tufte pulls no punches in his criticism of those who do things the wrong way.  In describing one graph published in American Education magazine - a confusing 3D graph that shows only 5 pieces of data and uses 5 different colors that in no way relate to that data - he writes "This may well be the worst graphic ever to find its way into print."

This is an excellent book for anyone who needs to present data to an audience.  Business analysts, managers and software developers can all increase their effectiveness by implementing Tufte’s ideas.

Links:
EdwardTufte.com
This book on Amazon

Thursday, May 8, 2008 11:21:44 PM (GMT Daylight Time, UTC+01:00)
# Friday, May 2, 2008

Those of us living in the Detroit area got a real treat last night.

The Tigers 8-4 victory completed a 3-game sweep of the Yankees - their first sweep in The Bronx since 1956.

The Red Wings dominated an injury-riddled Colorado Avalanche 8-2 to sweep the second round of the NHL playoffs.  Johan "Mule" Franzen notched a hat trick (3 goals in one game) and is playing as well as anyone in the playoffs.

Meanwhile the Pistons advanced to the second round of the NBA playoffs with a 100-77 rout of the Philadelphia 76ers.  Just a few days ago, Detroit trailed 2-1 in the series following a one-sided Philadelphia victory.

All three victories completed a victorious series and each occurred on the road. 

Detroit sports fans have had much to cheer about over the years, but I don't recall a day like Thursday, when all 3 of the major professional sports teams had such big victories simultaneously.  (I understand that Detroit once boasted a professional football team but I can't find anyone here who remembers them.)

Friday, May 2, 2008 12:33:41 PM (GMT Daylight Time, UTC+01:00)
# Thursday, April 17, 2008

Today, I was approached by someone with a request that sounded very simple.  She had a large Word document and she wanted to create an Excel spreadsheet in which each cell contained the name of a section in the Word document.  A hyperlink in each cell should open the Word document and navigate the user to the corresponding section.

Years ago, I did something similar using Office 97 or Office 2000, so I knew it was possible.

I opened the Word document and inserted a bookmark at the top of each section.  Inserting bookmarks in Word is pretty straightforward:

  1. Select the first line of the section
  2. From the menu/ribbon, select Insert Bookmark
  3. In the Bookmark dialog, type a name for that bookmark.

 

I became confused when I tried creating the hyperlinks in Excel.  Inserting a hyperlink in Excel hasn't changed much through the versions:

  1. Type some text in a cell
  2. Select that cell
  3. From the menu/ribbon, select Insert | Hyperlink
  4. Find and select the file to which you want to link.

This is where I became confused.  The "Insert Hyperlink" dialog contains a big button labeled "Bookmark".  Naturally I clicked this button to specify the bookmark within the Word document.  Unfortunately, clicking the button displayed an error.  According to Excel, Word documents don't support bookmarks, although my personal experience and the on-line help says that they do.

 

The secret is that you should not click the bookmark button in order to link to a bookmarked location in a Word document.  Rather, you should append the filename with the pound symbol ("#"), followed by the name of the bookmark to which you wish to link.  For example, I wanted to link to a bookmark named "Section1" in a document named "BigWordDoc.docx", so I entered "BigWordDoc.docx#Section1", as shown below.

Apparently, the "Bookmark" button is used for cells and defined names within an Excel document.

  

I'm not sure if Excel's "Insert Hyperlink" dialog has changed in the last few versions, but this strikes me as a flaw in the user interface.  The visual clues don't help me accomplish this task - they actually took me in a different direction.

Here is a working demo of an Excel spreadsheet with links to sections of a Word document: OfficeLinkDemo.zip (17.92 KB)

Thursday, April 17, 2008 9:56:20 PM (GMT Daylight Time, UTC+01:00)