|
download source code
The GridView is a great control that allows you to display paginated results. But what happens if you want to change the underlying display for all of your projects? This article shows you how to extend the GridView so that it displays a summary row and allows pagination and sorting in that summary row.
The Premise
The original request was to create a result set that looked like this:
At first blush, a GridView or DataGrid was going to provide 95% of the functionality. The only thing it wouldn't do was create the summary row in between the pagination display. I decided to use the GridView as my starting point and build in the summary row from there.
Creating the Control
The first step is to create the control. I created a Web Control Library project and changed the name of the default class to EnhancedGridView.cs. I changed the default class declaration to:
public class EnhancedGridView : GridView
which indicates that the control should inherit its functionality from the GridView object.
Providing Stylesheet Options
The first thing to do was to provide CssClass options for the various components so that they could be styled outside of the control. To do that, the following properties were created, with values stored in the ViewState so that the information is maintained across post backs if the values are set programmatically:
public string CustomPagerCssClass
{
get { return (ViewState["CustomPagerCssClass"] == null) ? String.Empty : (string)ViewState["CustomPagerCssClass"]; }
set { ViewState["CustomPagerCssClass"] = value; }
}
public string CustomHeaderCssClass
{
get { return (ViewState["CustomHeaderCssClass"] == null) ? String.Empty : (string)ViewState["CustomHeaderCssClass"]; }
set { ViewState["CustomHeaderCssClass"] = value; }
}
public string CustomShowingCssClass
{
get { return (ViewState["CustomShowingCssClass"] == null) ? String.Empty : (string)ViewState["CustomShowingCssClass"]; }
set { ViewState["CustomShowingCssClass"] = value; }
}
public string CustomResultsPerPageCssClass
{
get { return (ViewState["CustomResultsPerPageCssClass"] == null) ? String.Empty : (string)ViewState["CustomResultsPerPageCssClass"]; }
set { ViewState["CustomResultsPerPageCssClass"] = value; }
}
public string CustomSortingCssClass
{
get { return (ViewState["CustomSortingCssClass"] == null) ? String.Empty : (string)ViewState["CustomSortingCssClass"]; }
set { ViewState["CustomSortingCssClass"] = value; }
}
Creating DropDownLists and Maintaining State
Because of the .NET page life cycle, any controls that need to maintain state must be created on every page load (that includes postbacks!) and must be created prior to ViewState being loaded. Therefore we overload the OnItit function with the DropDownLists for the sorting and results per page.
private string _DefaultSortExpression = string.Empty;
private DropDownList ddlSort = new DropDownList();
private DropDownList ddlNumberOfPages = new DropDownList();
public string DefaultSortExpression
{
get { return _DefaultSortExpression; }
}
protected override void OnInit(EventArgs e)
{
ddlNumberOfPages.AutoPostBack = true;
ddlSort.AutoPostBack = true;
ddlSort.SelectedIndexChanged += new EventHandler(ddlSort_SelectedIndexChanged);
ddlNumberOfPages.SelectedIndexChanged +=new EventHandler(ddlNumberOfPages_SelectedIndexChanged);
ddlNumberOfPages.ID = "ddlNumberOfPages";
ddlNumberOfPages.Items.Add("10");
ddlNumberOfPages.Items.Add("20");
ddlNumberOfPages.Items.Add("50");
ddlNumberOfPages.Items.Add("100");
this.PageSize = Convert.ToInt32(ddlNumberOfPages.SelectedItem.Value);
ddlSort.ID = "ddlSort";
if (ddlSort.Items.Count == 0 && this.AllowSorting)
{
for (int i = 0; i < base.Columns.Count; i++)
{
if (base.Columns[i].SortExpression != string.Empty && ddlSort.Items.FindByValue(base.Columns[i].SortExpression) == null)
{
ddlSort.Items.Add(new ListItem(base.Columns[i].HeaderText, base.Columns[i].SortExpression));
}
}
}
if (ddlSort.Items.Count > 0)
{
_DefaultSortExpression = ddlSort.Items[0].Text;
}
base.OnInit(e);
}
Handling the DropDownList PostBack Events
Next we work with the postback events for the DropDownLists. The sort functionality is pretty straightforward:
protected void ddlSort_SelectedIndexChanged(object sender, System.EventArgs e)
{
DropDownList ddlSort = (DropDownList)sender;
base.Sort(ddlSort.SelectedValue, SortDirection.Ascending);
}
More difficult is changing the results per page, which works like the OnPageIndexChanging event. We check first to see if the event is handled somewhere else. If so, we raise the event using the number of pages selected from the dropdown. If the event isn't handled elsewhere, we manually change the page size and rebind the data.
public event PageSizeChangeHandler PageSizeChanging;
public delegate void PageSizeChangeHandler(object s, PageSizeChangeEventArgs e);
protected void ddlNumberOfPages_SelectedIndexChanged(object sender, System.EventArgs e)
{
if (this.PageSizeChanging != null)
{
int iPageSize = Convert.ToInt32(ddlNumberOfPages.SelectedItem.Value);
PageIndex = 0;
this.PageSizeChanging(this, new PageSizeChangeEventArgs(iPageSize));
}
else
{
DropDownList ddlNumberOfPages = (DropDownList)sender;
this.PageIndex = 0;
this.PageSize = int.Parse(ddlNumberOfPages.SelectedValue);
this.DataBind();
}
}
The first two lines define the event and the event handler. The method defines what happens after the event is raised. The last thing we need is a definition for the PageSizeChangeEventArgs class, which should be defined outside of the EnhancedGridView class definition.
public class PageSizeChangeEventArgs : System.EventArgs
{
public int NewPageSize;
public PageSizeChangeEventArgs(int pNewPageSize)
{
NewPageSize = pNewPageSize;
}
}
Displaying the Summary Row
To display the summary row, we have to override the OnRowCreated and the PerformDataBinding methods. The PerformDataBinding method allows us to access the total row count for the data source provided. The OnRowCreated replaces the existing header with a summary of the results, including the DropDownLists for results per page and sorting. Note how we make sure if there are any custom CSS classes defined, we add them.
private int intTotal = 0;
protected override void PerformDataBinding(IEnumerable data)
{
if (data is IListSource)
{
IListSource oList = (IListSource)data;
intTotal = oList.GetList().Count;
}
else if (data is ICollection)
{
ICollection oCollection = (ICollection)data;
intTotal = oCollection.Count;
}
base.PerformDataBinding(data);
}
protected override void OnRowCreated(GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
int intColumnCount = e.Row.Cells.Count;
e.Row.Cells.Clear();
Panel oPnlInner;
Literal oLit;
Panel oPnl = new Panel();
if (this.CustomHeaderCssClass != string.Empty) { oPnl.CssClass = this.CustomHeaderCssClass; }
// Showing X-Y of Z
int iUpto = 0;
int iStart = PageIndex * PageSize + 1;
if ((PageIndex + 1) == PageCount)
iUpto = intTotal;
else
iUpto = iStart + PageSize - 1;
oPnlInner = new Panel();
if (CustomShowingCssClass != String.Empty) { oPnlInner.CssClass = CustomShowingCssClass; }
oLit = new Literal();
oLit.Text = string.Format("Showing {0}-{1} of {2}", iStart, iUpto, intTotal);
oPnlInner.Controls.Add(oLit);
oPnl.Controls.Add(oPnlInner);
// Results per page
oPnlInner = new Panel();
if (CustomResultsPerPageCssClass != String.Empty) { oPnlInner.CssClass = CustomResultsPerPageCssClass; }
oLit = new Literal();
oLit.Text = "Results per page: ";
oPnlInner.Controls.Add(oLit);
oPnlInner.Controls.Add(ddlNumberOfPages);
oPnl.Controls.Add(oPnlInner);
// Sorting
if (this.AllowSorting == true)
{
oPnlInner = new Panel();
if (CustomSortingCssClass != String.Empty) { oPnlInner.CssClass = CustomSortingCssClass; }
oLit = new Literal();
oLit.Text = "Sort: ";
oPnlInner.Controls.Add(oLit);
oPnlInner.Controls.Add(ddlSort);
oPnl.Controls.Add(oPnlInner);
}
// Add div to clear out styles
oPnlInner = new Panel();
oPnlInner.Style.Add("clear", "both");
oPnl.Controls.Add(oPnlInner);
e.Row.Cells.Add(new TableCell());
e.Row.Cells[0].ColumnSpan = intColumnCount;
e.Row.Cells[0].Controls.Add(oPnl);
}
else if (e.Row.RowType == DataControlRowType.Pager)
{
Panel oPnl = new Panel();
if (CustomPagerCssClass != String.Empty) { oPnl.CssClass = CustomPagerCssClass; }
Table oTab = (Table)e.Row.Cells[0].Controls[0];
oTab.Rows[0].Cells.AddAt(0, new TableCell());
oTab.Rows[0].Cells[0].Text = "<strong>Page:</strong>";
oPnl.Controls.Add(oTab);
e.Row.Cells[0].Controls.Clear();
e.Row.Cells[0].Controls.Add(oPnl);
}
}
Column Headers
To display the column headers, we override the OnRowDataBound method to create a brand new row with the column headers. We can use the e.Row.Parent property to access the table that contains the output for the GridView. The logic for adding the row is required because the index of the row where we want the column headers to appear is different if the PagerSettings.Position is PagerPosition.Top or PagerPosition.TopAndBottom.
To maintain similar functionality as the GridView, we also provide a ShowColumnHeader property and store the value in ViewState so that it is maintained correctly across post backs. This property allows the ASPX/codebehind control over whether the column row appears since the ShowHeader property now controls whether the summary row appears.
Lastly, we check the UseAccessibleHeader property to determine whether to use a td or a th in the HTML markup for the table.
public bool ShowColumnHeader
{
get { return (ViewState["ShowColumnHeader"] == null) ? true : (bool)ViewState["ShowColumnHeader"]; }
set { ViewState["ShowColumnHeader"] = value; }
}
protected override void OnRowDataBound(GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
if (ShowColumnHeader && Columns.Count > 0)
{
Table tbl = (Table)e.Row.Parent;
GridViewRow row = this.CreateRow(-1, -1, DataControlRowType.Header, DataControlRowState.Normal);
for (int i = 0; i < this.Columns.Count; i++)
{
if (UseAccessibleHeader)
{
row.Cells.Add(new TableHeaderCell());
}
else
{
row.Cells.Add(new TableCell());
}
row.Cells[i].Text = this.Columns[i].HeaderText;
}
tbl.Rows.AddAt((PagerSettings.Position != PagerPosition.Bottom) ? 2 : 1, row);
}
}
base.OnRowDataBound(e);
}
Using the EnhancedGridView in Your Web Project
Now that the control is complete, you can compile it and add it as a reference in your Web project. You'll have to reference the DLL in your ASPX page in order to use it.
Here's an example of how to use the EnhancedGridView. Note that you have to put this control inside a form with runat="server" so that the dropdownlists will work.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="default.aspx.cs" Inherits="UI.clsDefault" Title="Home Page" %>
<%@ Register TagPrefix="cc" Assembly="CustomControls" Namespace=" CustomControls" %>
<html>
<body>
<form runat="server">
<cc:EnhancedGridView ID="gvResults" runat="server"
GridLines="None"
AutoGenerateColumns="false"
AllowPaging="true" PagerSettings-Mode="Numeric"
PagerSettings-Position="TopAndBottom"
AllowSorting="true" AlternatingRowStyle-CssClass="alt"
DataSourceID="sqlSelect"
CssClass="dotnet_table"
HeaderStyle-CssClass="dotnet_columnhead"
CustomPagerCssClass="dotnet_pages"
CustomHeaderCssClass="dotnet_head"
CustomShowingCssClass="dotnet_showing"
CustomSortingCssClass="dotnet_sorting"
CustomResultsPerPageCssClass="dotnet_results"
>
<Columns>
<asp:BoundField DataField="Column1" HeaderText="Column1" SortExpression="Column1" />
<asp:BoundField DataField="Column2" HeaderText="Column2" SortExpression="Column2" />
</Columns>
</cc:EnhancedGridView>
<asp:SqlDataSource ID="sqlSelect" runat="server"
ConnectionString="<%$ ConnectionStrings:Web %>"
DataSourceMode="DataSet"
SelectCommand="SELECT [Columns] FROM [Table]"
/>
</form>
</body>
</html>
Opportunities for Improvement
One way to improve this control would be to allow the values for the results per page DropDownList to be supplied in the ASPX page or code behind instead of being hardcoded into the control.
Another opportunity for improvement lies in determining what columns are available for sorting. The control relies on the columns being defined. If AutoGenerateColumns="true" the sorting dropdown isn't populated correctly.
Conclusion
The GridView is not only a great control for displaying data but is easily enhanced to provide custom interfaces based on your needs.
About The Author
Rachael Schoenbaum has been programming since 1999 and specializes in ASP, .NET, Visual Basic, SQL Server, XML, and related technologies.
She currently works as a consultant for Lucidea.
If you have questions or comments about this article, email Rachael at rachael_schoenbaum(at)yahoo.com.
|