isplaying two-dimensional table data in a grid is one of the most common programming tasks. When the number of rows is small, a single page with a scrollbar is sufficient, but when the data has many rows, it’s usually better to display a few rows at a time, adding controls that give the user a way to navigate from one screenful or “page” of the records to another, a process called “paging.” Having written paging functionality from scratch before the days of .NET, I was excited when Microsoft released ASP.NET and the DataGrid control. Using the ASP.NET DataGrid a programmer can easily display a grid complete with interface controls that provide paging capability using only a few lines of code. Yet as simple as that process is, I wanted a way to improve upon it
Specifically, I wanted a way to add paging to my DataGrids so that the paging mechanism worked similarly to most Web search engines. For example, if you search Google for a common word, you’ll get a page with a list of matches, and at the bottom of the page you’ll see a line containing paging links that let you jump to the next page or skip ahead to any of the next ten pages. Clicking your way through to, for example, the 20th page, changes the paging link scheme slightly; you’ll see that the paging links now include page-numbered links for the previous 10 pages and the next 10 pages, as well as next and previous links. In this article, I’ll show you how to create a simple server control so you can add this style of paging to your DataGrids.
Setting Up a DataGridPager Example
Start with a basic DataGrid on an aspx page as follows:
The preceding code sets the AllowPaging attribute to True to allow the control to page this DataGrid, and sets the PagerStyle-Visible attribute value to False to hide the DataGrid’s built-in DataGrid paging links.
Next, add code to the code-behind file to generate a dataset of random data and bind it to the DataGrid when the page loads. Here’s the code:
Protected WithEvents dgTest As DataGrid Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load If Not IsPostBack Then Session("MyDataSet") = BuildDataSet(1000) dgTest.DataSource = Session("MyDataSet") dgTest.DataBind() End If End Sub Private Function BuildDataSet(ByVal NumberOfRecords _ As Integer) As DataSet Randomize() Dim ds As New DataSet ds.Tables.Add() With ds.Tables(0).Columns .Add("RecordNumber", _ System.Type.GetType("System.Int32")) .Add("RandomNumber", _ System.Type.GetType("System.Int64")) .Add("RandomName", _ System.Type.GetType("System.String")) .Add("RandomDateTime", _ System.Type.GetType("System.DateTime")) End With For Counter As Integer = 1 To NumberOfRecords Dim dr As DataRow = ds.Tables(0).NewRow() dr("RecordNumber") = Counter dr("RandomNumber") = Rnd() * 1000 dr("RandomName") = GetRandomName() dr("RandomDateTime") = Date.Now.AddDays(Rnd() * -365) ds.Tables(0).Rows.Add(dr) Next Return ds End Function Private Function GetRandomName() As String Select Case CType(Rnd() * 20, Integer) Case 0 : Return "Abraham Bennet" Case 1 : Return "Reginald Blotchet-Halls" Case 2 : Return "Cheryl Carson" Case 3 : Return "Michel DeFrance" Case 4 : Return "Innes del Castillo" Case 5 : Return "Ann Dull" Case 6 : Return "Marjorie Green" Case 7 : Return "Morningstar Greene" Case 8 : Return "Burt Gringlesby" Case 9 : Return "Sheryl Hunter" Case 10 : Return "Livia Karsen" Case 11 : Return "Charlene Locksley" Case 12 : Return "Stearns MacFeather" Case 13 : Return "Heather McBadden" Case 14 : Return "Michael O'Leary" Case 15 : Return "Sylvia Panteley" Case 16 : Return "Albert Ringer" Case 17 : Return "Anne Ringer" Case 18 : Return "Meander Smith" Case 19 : Return "Dean Straight" Case 20 : Return "Dirk Stringer" End Select End Function
Don’t dwell on the preceding code too much. It simply builds a DataSet full of data on the fly. Just note that after building the dataset, the Page_Load method stores it in the user’s session so you can access it later as the user pages through the results.
When the page loads, it should look something like Figure 1.
|Figure 1. DataGrid with Auto Generated Data. The figure shows the first page of the data from the random dataset displayed in a DataGrid.|
Next, download the source code for this article and copy the code from the DataGridPager.vb file (see Listing 1) into your Web project. Add the following to the top of the aspx page to assign the “x” prefix to controls within your project:
<%@ Register TagPrefix="x" Namespace="MyProjectName" Assembly="MyProjectName" %>
Note that you should replace the MyProjectName values above with the name of the project you are working with. Also, “x” is just the prefix I choose, you can set this to whatever prefix you like. Just make sure that when you add the paging control that you use the prefix you assign here.
Now to add the paging control, add the following tag wherever you would like the paging links to appear:
Note the DatagGridToAttachTo attribute. This attribute is required and tells the control which DataGrid it should attach to and provide paging for. I’ll cover some of this control’s other attributes later and also get into the source code for the control. For now, it’s more important to get the example working.
Viewing the Page
With the DataGridPager control in place, run the page and click the paging links. Notice that the paging now emulates the functionality described earlier. Figure 2 and Figure 3 show how the paging feature looks by default on the initial page and after the user pages into the data.
The DataGridPager Control’s Properties
Now that you’ve seen the control in action, here are some of its other properties. You can view the control’s properties easily by switching your page to Design view, selecting the DataGridPager control, and then looking at the Properties pane. Figure 4 shows these properties with their default values. Table 1 lists the properties and a description of each.
|Figure 4. DataGridPager’s Properties: The default DataGridPager control properties.|
|Property Name||Property Description|
|DataGridToAttachTo||The ID of the DataGrid to attach to.|
|FirstPageLinkText||The text of the first page link. This property defaults to <<.|
|LastPageLinkText||The text of the last page link. This property defaults to >>.|
|PreviousPageLinkText||The text of the previous page link. This property defaults to < Previous.|
|NextPageLinkText||The text of the next page link. This property defaults to Next >.|
|MaxNumPageForwardLinks||The maximum number of numbered page links to show after the current page indicator. This property defaults to 10.|
|MaxNumPageBackLinks||The maximum number of numbered page links to show before the current page indicator. This property defaults to 10.|
|PageLinkSeparator||This property specifies the text used to separate the paging links. This property defaults to a space character.|
You can set these properties in the properties pane, at run-time, or using attributes in the HTML for the page, for example:
How the DataGridPager Control Works
Starting at the top of the DataGridPager.vb class file in Listing 1, notice that the DataGridPager control is derived from the System.Web.UI.Control object. This object contains all the functionality the DataGridPager control needs.
The DataGridPager works by dynamically creating LinkButton controls and adding them to its child Controls collection. This means the DataGridPager is a composite control because it contains child controls. Microsoft specifies that composite controls must implement the INamingContainer interface and override the CreateChildControls() method. This is necessary so that when the control’s containing page runs, it will call the control’s CreateChildControls() method to allow the control to recreate its child controls. It’s vital because the page must recreate the child controls during postbacks so their Click() events can be handled properly. Look at the following code from the “Overrides” region:
Protected Overrides Sub CreateChildControls() ' Builds child pager buttons so their click events can ' be handled If TypeOf _dg Is DataGrid Then BuildPagerButtons() End Sub
The preceding code simply recreates the paging buttons exactly as they were when last sent to the browser whenever the DataGridPager control is attached to a DataGrid.
Continuing through the code for Listing 1, you’ll notice that this control also overrides the OnInit() and Render() methods. OnInit() initializes the _dg member variable to point to the DataGrid object. The overridden Render() method allows the control to write out an error message to the page if the control is unable to find the DataGrid specified in its DataGridToAttachTo property.
The next two regions, “Member Variables” and “Properties” implement the control’s properties. These properties set or return the values from private variables within the class.
The final region, “Private Routines”, contains the BuildPagerButtons() and PagerLinkButtonClick() methods.
The BuildPagerButtons() method is the backbone of this control. This method creates and configures the paging link buttons and adds them to the control. Note that the control exposes properties (NextPageLinkText and PreviousPageLinkText) that you can use to specify the text for these controls. Also note that the code sets each LinkButton’s CommandName and CommandArgument properties, which the application needs when a user clicks one of the links.
Clicking one of the pager LinkButtons calls the PagerLinkButtonClick() method, which uses the clicked LinkButton’s CommandName and CommandArgument to determine which paging action to take. After the DataGrid has been moved to its new page, the control rebinds the DataGrid and calls the BuildPagerButtons() method, which rebuilds the paging links to match the grid’s updated page position.
As you’ve seen in this article, you can use a simple server control to add search-engine-style paging to a DataGrid. I’ve purposely kept the example control as simple as possible to make it easy to understand. You may choose to extend the control by adding additional properties, functionality, designer support, or other items. I hope you found this control handy and that it (or some variation of it) works its way into your development efforts. I know I’ll be carrying it in my bag of tricks for some time to come.