Introduction

While the out of the box GridView is enough for most applications, often there is a need to customize it. One such customization requirement is for the Top Pager to be different from the Bottom Pager. GridView allows you to customize the Pager by using a Template construct, but it uses that same template for the Top Pager as well as the Bottom Pager. Another feature missing from GridVew is filtering. Filtering allows the user to restrict the row set by specifying column values.

Background

In order to customize GridView, we need to first understand how it builds the control structure on the server side. On the client side, the GridView is rendered as a <table> within a <div> element. On the server side, the control tree is built within the GridView.controls member variable as shown:

So, we have a ChildTable at the root with all GridViewRows as its children. The very first row is the Pager row if it is needed. Next is the Header row. Then, we have one or more DataRows. This is followed by the Footer row and then the Pager row if these are needed.

Using the Code

This is how the code for the custom grid is rendered in the ASPX file. Notice that for the columns that need filtering, the headertext attribute should have a space at the end. Here we have a space for the City and State columns.

Design Details

The design is explained in two sections namely Custom Paging and Custom Filtering.

1. Custom Paging

When creating the Pager row (both Top and Bottom), the GridView calls a virtual method called InitializePager. We override this method and provide two new virtual methods called InitializeTopPager and InitializeBottomPager as follows:

Refer to the commented line in the above code and note that we cannot use GridView.TopPagerRow to find out if IntializePager is being called for creating the Top or the Bottom pager. Reason being, whenever GridView.CreateChildControls gets called, it will in turn call InitializePager twice, first time for the top pager and the second time for the bottom pager. Now, since CreateChildControls itself may get called twice during postback/callback, first while getting created from ViewState and next when creating from the database (for a paged GridView, the user requested a new page, for example), the TopPagerRow would already exists from the previous run. So, we directly look at the control tree; if it is empty, then GridView has started creating the Top pager row.

We let the GridView create the Bottom pager by calling base.InitializePager. The bottom pager is created as a Table (PagerTable) with a single Row (TableRow) and a single Cell (TableCell). Then, we customize the bottom pager by adding another Cell (goToCell) to the TableRow as shown:

The InitializeHeaderRow method in turn calls AddGlyphs to add an up/down arrow for sorting, and then calls AddFilters to find if filtering is enabled for columns, and if it is, then it calls AddFilter to add a TextBox and a DropDownList to that column's header cell. To enable filtering for a column, include a space at the end of the column header text in the ASPX file or in the code-behind (this is kludgy).

The important thing to note here is that we set RequiresDataBinding to true; this statement is checked by GridView during the PreRender stage and it rebinds the data to the grid if it is true. This is exactly what we want; we don’t want to bind it immediately, we delay it till the very last stage. Meanwhile, we also provide a virtual method called OnFilterCommand for derived classes/pages to hook into our filtering.

Here, we call the GetFilterCommand method, which loops through all the columns to form a where clause and returns it as a filterCommand. Note that we check for a space character at the end of Columns[i].HeaderText to find if the user has enabled filtering for Columns[i].