Paging and Sorting with DataGrid Controls

A great thing about components is that you get a lot of goodies with them. You just need to know where to look. Two such goodies that come with DataGrid controls are (1) the ability to split up data into logical pages (paging) instead of a single, monolithic page, and (2) the ability to reorder the data in the DataGrid control by column (sorting). When you know where to find all the pieces, it is a relatively straightforward matter to use these capabilities. We will start with paging, continuing to use the BindControlsDemo.sln for this example, too.

Using a DataGrid Control for Paging

Suppose again that we are working with the customer contact list. We may ultimately want to view all customers but need only a handful at a time, or we may want all the data to fit on one screen without having to scroll the browser. We can use paging for these purposes.

To use the built-in paging capability of a DataGrid control, we need to modify a property and implement an event handler. The property we must modify is the DataGrid.AllowPaging property. AllowPaging turns on paging; combined with an event handler for DataGrid.PageIndexChanged, we get paging. I will talk about the event handler in a moment.

For additional paging customizations we can change the default DataGrid.PageSize from 10 to some other number. PageSize determines the maximum number of rows displayed on each page. We can also change the DataGrid.PagerStyle.Mode property from the default NextPrev, which gives pager controls < and > (less than and greater than), to NumericPages, which gives numbered links—up to 10—and adds ellipses on each end if there are more than 10 pages of data. (I prefer the number links because it seems easier to jump to a relative point in the list.)

Implementing the event handler is accomplished by switching to the Code Editor view, selecting the DataGrid control from the class name combobox, and choosing the PageIndexChanged event from the method name combobox. All we really have to do to support paging programmatically is to set DataGrid.CurrentPageIndex equal to e.NewPageIndex (where e is the event argument name for the DataGridPageChangedEventArgs parameter), set the DataGrid control's DataSource property, and rebind the DataGrid control. Listing 16.8 shows an excerpt demonstrating this code.

Listing 16.8. Implementing an Event Handler for the DataGrid.PageIndexChanged Event
Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) _
  Handles DataGrid1.PageIndexChanged

  DataGrid1.CurrentPageIndex = e.NewPageIndex
  DataGrid1.DataSource = Contacts
  DataGrid1.DataBind()

End Sub

Figure 16.10 in the next subsection shows the combined effect of the paging and column sorting features as they appear applied to the grid.

Using a DataGrid Control for Sorting

Customers will more than likely want to reorganize data on an individual-user basis. You can give them this feature at a modest charge. (Sorry, a momentary lapse into mercenary contractor humor.) To implement sorting when the user clicks on a column header you need to set the DataGrid.AllowSorting property to True, implement an event handler for the SortCommand event, assign the DataView.Sort property to e.SortExpression, assign the DataGrid.DataSource property, and rebind the DataGrid control. (This is assuming we are using the DataView object, as we are in this example.)

The DataView.Sort property might sound a little ominous, but this simply refers to the column name. The right-hand-side value e.SortExpression is an instance of the DataGridSortCommandEventArgs parameter passed to the SortCommmand event handler. Listing 16.9 gives the excerpt from BindControlsDemo.sln that shows the SortCommand event handler.

Listing 16.9. Implementing an Event Handler for the DataGrid.SortCommand Event
Private Sub DataGrid1_SortCommand(ByVal source As Object, _
  ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) _
  Handles DataGrid1.SortCommand

  Contacts.Sort = e.SortExpression
  DataGrid1.DataSource = Contacts
  DataGrid1.DataBind()

End Sub

Things might go slightly and momentarily wrong if you set AutoGenerateColumns to False as we did earlier in the chapter. I mention it because this is the kind of thing that can frustrate anyone. Because we turned off the DataGrid.AutoGenerateColumns property, we need to return to the DataGrid control's Properties dialog (Figure 16.9) and indicate the column name as the sort expression. For example, we named our header for the contact name just Contact, but the sort expression needs to be ContactName. Just type this information in the Sort expression field in the Properties window. If we want to specify a sort order, we can add DESC—for descending—after the column name (or ASC for ascending). Thus the total value for the sort expression on the ContactName column is ContactName DESC. Repeat the process for each column for which you want to support sorting.

You will know that column sorting is supported because the column header will be rendered as a hyperlink (Figure 16.10).

Figure 16.10. The combined effect of paging and sorting in a DataGrid control.


Binding a System.Array Object to a DataGrid Control

Microsoft has made the base class libraries (BCLs) available for download. This source code for .NET is referred to as the SSCLI (shared source code library) or Rotor. I was poking around in the BCLs because I wanted to be sure how serialization for binding worked. Tracing it to a single line of code, it appears that as long as an object implements IEnumerable, you are going to have a pretty good chance of binding to controls like DataGrid or serializing that class. It is worth mentioning that while it is helpful to understand how things work, it may be a mistake to write code that is completely dependent on that knowledge.

A simple and obvious candidate is any instance of System.Array; that is, an array of any type can be bound to a DataGrid control because System.Array implements IEnumerable. Generally, using a plain array of objects is weaker implementation then using a strongly typed collection or data set, but occasionally it may be convenient to use a simple array, or that may be all you have. (For example, you might have only an array of types if that is what a vendor returns in the code or in a Web Service.) Listing 16.10 demonstrates a custom type, Contact, and an array of Contact objects bound to a DataGrid control.

Listing 16.10. Binding an Array of Contact Objects to a DataGrid Control
1: Public Class WebForm1
2:    Inherits System.Web.UI.Page
3:    Protected WithEvents DataGrid2 As _
4:      System.Web.UI.WebControls.DataGrid
5:    [ Web Form Designer generated code ]
6:
7:    Private Sub Page_Load(ByVal sender As System.Object, _
8:      ByVal e As System.EventArgs) Handles MyBase.Load
9:
10:     DataGrid2.DataSource = GetContactList()
11:     DataGrid2.DataBind()
12:
13:   End Sub
14:
15:   Private Sub DataGrid2_SortCommand(ByVal source As Object, _
16:     ByVal e As _
17:     System.Web.UI.WebControls.DataGridSortCommandEventArgs) _
18:     Handles DataGrid2.SortCommand
19:
20:     Dim List() As Contact = GetContactList()
21:     Array.Sort(List, New StringComparer(e.SortExpression))
22:
23:     DataGrid2.DataSource = List
24:     DataGrid2.DataBind()
25:   End Sub
26:
27:   Public Function GetContactList() As Contact()
28:     GetContactList = New Contact() { _
29:       New Contact("Mark Davis", "(503) 555-1212"), _
30:       New Contact("Robert Golieb", "(608) 555-1212"), _
31:       New Contact("Paul Kimmel", "(517) 555-1212"), _
32:       New Contact("Alex Kimmel", "Unlisted")}
33:
34:   End Function
35: End Class
36:
37: Public Class Contact
38:
39:   Private FName As String
40:   Private FPhone As String
41:
42:   Public Sub New(ByVal Name As String, ByVal Phone As String)
43:     Me.FName = Name
44:     Me.FPhone = Phone
45:   End Sub
46:
47:   Public Property Name() As String
48:   Get
49:     Return FName
50:   End Get
51:   Set(ByVal Value As String)
52:     FName = Value
53:   End Set
54:   End Property
55:
56:   Public Property Phone() As String
57:   Get
58:     Return FPhone
59:   End Get
60:   Set(ByVal Value As String)
61:     FPhone = Value
62:   End Set
63:   End Property
64: End Class

The Contact class in lines 37 to 64 is trivial. It is worth pointing out that if you bind to a DataGrid control, it will use Reflection to autogenerate the columns, and it will only reflect properties. Thus, if you deviate from convention by using public fields instead of properties, the DataGrid control will not be able to generate columns for you.

The abbreviated WebForm1 class is implemented in lines 1 through 35. A factory method exists for the array, GetContactList, in lines 27 through 34. (This simulates getting, creating, or having an array of Contact objects.) The Page_Load event uses this method to obtain the array, assign it to the DataGrid.DataSource property (line 10), and call DataGrid.DataBind.

Lines 15 through 25 implement the SortCommand event handler for this DataGrid control. Instead of assigning the DataView.Sort property to the e.SortExpression value we need to implement custom sorting behavior. In our example I resolved to use the inherent System.Array.Sort method and define a class that implements IComparer. The next section elaborates on IComparer.

Implementing IComparer to Support Sorting a System.Array Object

The C++ programming language and some others like SmallTalk support templates, also referred to as generics. Templates represent an idiom that supports separating type from algorithm. That is, we can write an algorithm and supply the type at runtime, changing the type as we go. Thus, instead of writing a sort, for example, for integers, strings, Contact objects, and whatever else comes along, we can write one sort and supply any type. Visual Basic .NET does not support templates (nor does C#) in part because they can be challenging to use. However, we can accomplish some of the same goals with interfaces.

The point is to be able to separate an algorithm from a type. In this instance we have a sort algorithm in System.Array, but arrays cannot know in advance how to compare every object, especially those that haven't been created yet. Instead the Sort method in System.Array relies on the objects to be sorted to supply their own Compare method via the IComparer interface. In other words, you provide an instance of an object that implements IComparer and this object will know how to compare the objects. As a result the sort algorithm can be genericized except for anywhere that a comparison needs to be made. When such a comparison is needed, the code relies on you to do it. The net effect is that we all get a flawless, prewritten sort algorithm and all we have to do is provide the comparator.

Line 21 from Listing 16.10 demonstrates how to call the shared System.Array.Sort method and passes an externally defined comparator to that method. The StringComparer class (Listing 16.11) implements the System.Collections.IComparer interface and will be used at the point in the Sort method where a comparison needs to be made. (If you open the Array.cs file in Rotor, you can see where the comparator is used to initialize an internal class named SorterGenericArray, which in turn uses a quick sort algorithm to sort the array.)

Listing 16.11. Implementing IComparer to Sort an Array of Any Kind of Object
1:  Public Class StringComparer
2:    Implements IComparer
3:
4:    Private FProperty As String
5:
6:    Public Sub New(ByVal Value As String)
7:      FProperty = Value
8:    End Sub
9:
10:   Public Function Compare( _
11:     ByVal x As Object, ByVal y As Object) As Integer _
12:     Implements System.Collections.IComparer.Compare
13:
14:     Return GetString(x).CompareTo( _
15:       GetString(y))
16:
17:   End Function
18:
19:   Private Function GetString(ByVal o As Object) As String
20:     Return o.GetType().GetProperty(FProperty). _
21:       GetValue(o, Nothing)
22:   End Function
23:
24: End Class

First, it is worth pointing out that I could have implemented IComparer in the Contact class. However, my solution simulates that case where you may not have access to the source type, in this case, Contact. Additionally, my solution is so generic that I can reuse it for any class that has string properties.

I certainly could have written the comparator to know about the Contact class, but that isn't very advanced. Not only is it not advanced but also it would mean that I would have to write a comparator for other types. The solution here doesn't know anything about the Contact class. Instead I used Reflection to request a named property—provided as an argument to the constructor in line 6—and read and compared the same property in both objects, x and y. The constructor is told about the property to compare. The private method GetString uses Reflection to obtain the value of that string, and the Compare method, which satisfies our contract with IComparer, calls the String.CompareTo method. I want to return an integer indicating whether the comparison is less than, equal to, or greater than, and String.CompareTo does this.

When StringComparer is finished I can use it as a comparator for an array of any object's string properties. StringComparer is definitely “write once, use many times” code that shows the kind of power and flexibility available in Visual Basic .NET.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.6.226