After playing around with ASP.NET 2.0's GridView control I was ready to put it to use. Problem was, ASP.NET 2.0's release will be...well when it's ready. In the interest of not having to write code for each datagrid's sorting and paging, I came up with the following. It works server-side, maintains it's state (even with a delete or modification that requires you to rebind the grid, and is a snap to implement. I have to agree that DataGrid's ViewState takes up some bloat in the page, but if you don't care...maybe modify this below to use querystrings or something.
You can pretty much copy and paste what's below, but I will explain the important parts below the code. (Note: I just pretty much copy and pasted directly from my own working code so if you use you'll probably want to put in your own comments :-P )
1 using System;
2 using System.Web.UI.WebControls;
3 using System.Web;
4
5 namespace SATK.Web.UI.WebControls
6 {
7 /// <summary>
8 /// <hr />
9 /// <para>
10 /// This data grid is customized for our specific
11 /// look and feel. It also extends the functionality
12 /// with security implementation, empty data
13 /// messages and other goodies.
14 /// </para>
15 /// <hr />
16 /// <h1>Release history</h1>
17 /// <code>
18 /// DEVELOPER VERSION DATE DESCRIPTION
19 /// W.Tilton 1.0 12/20/2004 Initial Creation
20 /// W.Tilton 1.1 01/18/2005 Added whereClause to binding delegate
21 /// W.Tilton 1.2 03/08/2005 Added ability to have more than one grid per page
22 /// </code>
23 /// <hr />
24 /// </summary>
25 public class DataGrid : System.Web.UI.WebControls.DataGrid
26 {
27 private string _emptyDataMessage, _heading;
28 private bool _isPaged = true;
29 private int _pageSize = 20;
30 /// <summary>
31 /// A delegate that must be set in order for this object to function properly. Specifies which helper method
32 /// will populate (bind) data in the given datagrid.
33 /// </summary>
34 public delegate void BindDelegate(Web.UI.WebControls.DataGrid dg, string whereClause, string orderByColumns, bool isAscending);
35
36 private BindDelegate bind;
37
38 /// <summary>
39 /// This method tells the datagrid object which method the system should use to bind the instance of a grid that you have
40 /// created.
41 /// <see cref="Web.UI.WebControls.DataGrid.BindDelegate"/>
42 /// </summary>
43 /// <param name="bd">Web.UI.WebControls.DataGrid.BindDelegate</param>
44 /// <example>
45 /// <code>
46 /// dg.SetBindDelegate(new Web.UI.WebControls.DataGrid.BindDelegate(Web.UI.Helpers.MyHelper.Bind));
47 /// </code>
48 /// <code>
49 /// dg.SetBindDelegate(new Web.UI.WebControls.DataGrid.BindDelegate(Method.That.Will.Bind.This.DataGrid));
50 /// </code>
51 /// </example>
52 public void SetBindDelegate(BindDelegate bd)
53 {
54 bind = bd;
55 }
56 /// <summary>
57 /// The amount of records that are displayed on the grid.
58 /// </summary>
59 /// <remarks>Defaults to 20.</remarks>
60 public override int PageSize
61 {
62 get
63 {
64 return this._pageSize;
65 }
66 set
67 {
68 this._pageSize = value;
69 }
70 }
71
72
73 /// <summary>
74 /// The message that is displayed when their are no items in the datagrid.
75 /// </summary>
76 /// <example>
77 /// <code><satk:DataGrid runat="Server" EmptyDataMessage="No records found!"></satk:DataGrid></code>
78 /// </example>
79 public string EmptyDataMessage
80 {
81 get
82 {
83 if(this._emptyDataMessage==null)
84 return "";
85 else
86 return this._emptyDataMessage;
87 }
88 set { this._emptyDataMessage = value; }
89 }
90 /// <summary>
91 ///
92 /// </summary>
93 public bool IsPaged
94 {
95 get { return this._isPaged; }
96 set { this._isPaged = value; }
97 }
98 /// <summary>
99 ///
100 /// </summary>
101 public string Heading
102 {
103 get
104 {
105 if(this._heading==null)
106 return "";
107 else
108 return this._heading;
109 }
110 set { this._heading = value; }
111 }
112 /// <summary>
113 /// Customized data grid, with secure columns
114 /// </summary>
115 public DataGrid()
116 {
117 this.Width = new Unit(100d,UnitType.Percentage);
118 this.CellPadding = 3;
119 this.AutoGenerateColumns = false;
120 this.CssClass = "datagrid";
121
122 this.AllowSorting = true;
123
124 this.HeaderStyle.CssClass = "th";
125 this.AlternatingItemStyle.CssClass = "alt";
126 this.SelectedItemStyle.CssClass = "selected";
127
128 if(this.IsPaged)
129 {
130 this.AllowPaging = true;
131 this.PagerStyle.Mode = PagerMode.NumericPages;
132 this.PagerStyle.CssClass = "pages";
133 this.PagerStyle.HorizontalAlign = System.Web.UI.WebControls.HorizontalAlign.Right;
134 }
135 }
136 /// <summary>
137 /// Bind the grid using existing values
138 /// </summary>
139 /// <param name="b">A delegate that is used to bind the specific information.</param>
140 public void BindGrid(BindDelegate b)
141 {
142 if(ViewState[this.UniqueID+"WhereClause"]==null)
143 ViewState[this.UniqueID+"WhereClause"] = "1=1";
144
145 if(ViewState[this.UniqueID+"SortExpression"]==null)
146 ViewState[this.UniqueID+"SortExpression"] = "1";
147
148 if(ViewState[this.UniqueID+"SortOrder"]==null)
149 ViewState[this.UniqueID+"SortOrder"] = true;
150
151 HttpContext.Current.Trace.Warn("Sort Order: " + ViewState[this.UniqueID+"SortOrder"]);
152 HttpContext.Current.Trace.Warn("Sort Expression: " + ViewState[this.UniqueID+"SortExpression"]);
153 HttpContext.Current.Trace.Warn("Where Clause: " + ViewState[this.UniqueID+"WhereClause"]);
154
155 b(this,ViewState[this.UniqueID+"WhereClause"].ToString(),ViewState[this.UniqueID+"SortExpression"].ToString(),Convert.ToBoolean(ViewState[this.UniqueID+"SortOrder"]));
156 }
157 /// <summary>
158 /// Takes the given values and passes it to the given Helper method that will bind this datagrid.
159 /// </summary>
160 /// <param name="b"></param>
161 /// <param name="whereClause"></param>
162 /// <param name="orderByColumns"></param>
163 /// <param name="isAscending"></param>
164 public void BindGrid(BindDelegate b, string whereClause, string orderByColumns, bool isAscending)
165 {
166 if(whereClause==null)
167 ViewState[this.UniqueID+"WhereClause"] = "1=1";
168 else
169 ViewState[this.UniqueID+"WhereClause"] = whereClause;
170
171 if(orderByColumns==null)
172 ViewState[this.UniqueID+"SortExpression"] = "1";
173 else
174 ViewState[this.UniqueID+"SortExpression"] = orderByColumns;
175
176 ViewState[this.UniqueID+"SortOrder"] = isAscending;
177
178 HttpContext.Current.Trace.Warn("Sort Order: " + isAscending);
179 HttpContext.Current.Trace.Warn("Sort Expression: " + ViewState[this.UniqueID+"SortExpression"]);
180 HttpContext.Current.Trace.Warn("Where Clause: " + ViewState[this.UniqueID+"WhereClause"]);
181
182 b(this,ViewState[this.UniqueID+"WhereClause"].ToString(),ViewState[this.UniqueID+"SortExpression"].ToString(),Convert.ToBoolean(ViewState[this.UniqueID+"SortOrder"]));
183 }
184 /// <summary>
185 ///
186 /// </summary>
187 /// <param name="writer"></param>
188 protected override void Render(System.Web.UI.HtmlTextWriter writer)
189 {
190 writer.Write("<h1>" + this.Heading + "</h1>");
191 if(this.Items.Count==0)
192 {
193 writer.Write(this.EmptyDataMessage);
194 }
195 else
196 {
197 base.Render (writer);
198 }
199 }
200
201 /// <summary>Event that fires when a user has specified a sort order.</summary>
202 /// <param name="e"></param>
203 protected override void OnSortCommand(DataGridSortCommandEventArgs e)
204 {
205 // Switch it up, the sort command was clicked so we need to do opposite sorting that we had prior
206 ViewState[this.UniqueID+"SortOrder"] = !Convert.ToBoolean(ViewState[this.UniqueID+"SortOrder"]);
207
208 // Grab the sort expression, so that if the index changes we'll keep the sort order
209 ViewState[this.UniqueID+"SortExpression"] = e.SortExpression;
210
211 this.SortGrid(Convert.ToBoolean(ViewState[this.UniqueID+"SortOrder"]));
212 base.OnSortCommand (e);
213 }
214 /// <summary>Event that fires when a user has specified which page they would like to view within paged records.</summary>
215 /// <param name="e"></param>
216 protected override void OnPageIndexChanged(DataGridPageChangedEventArgs e)
217 {
218 this.CurrentPageIndex = e.NewPageIndex;
219
220 this.SortGrid(Convert.ToBoolean(ViewState[this.UniqueID+"SortOrder"]));
221 base.OnPageIndexChanged (e);
222 }
223 private void SortGrid(bool isAscending)
224 {
225 if(ViewState[this.UniqueID+"WhereClause"]==null)
226 ViewState[this.UniqueID+"WhereClause"] = "1=1";
227
228 if(ViewState[this.UniqueID+"SortExpression"]==null)
229 ViewState[this.UniqueID+"SortExpression"] = "1";
230
231 if(ViewState[this.UniqueID+"SortOrder"]==null)
232 ViewState[this.UniqueID+"SortOrder"] = true;
233
234 HttpContext.Current.Trace.Warn("Sort Order: " + isAscending);
235 HttpContext.Current.Trace.Warn("Sort Expression: " + ViewState[this.UniqueID+"SortExpression"]);
236 HttpContext.Current.Trace.Warn("Where Clause: " + ViewState[this.UniqueID+"WhereClause"]);
237 this.BindGrid(bind,ViewState[this.UniqueID+"WhereClause"].ToString(),ViewState[this.UniqueID+"SortExpression"].ToString(),isAscending);
238 }
239 }
240 }
This extended datagrid allows for me to quickly create a new datagrid that has the same look and feel as the rest. If I want to change the look and feel, I just change the CSS and done! Binding data to the grid is accomplished in a few steps (takes me 2 minutes due to the fact that I have great
CodeSmith templates that work in conjunction with this control).
First I have to develop a method that I can set my delegate to that will bind the correct data to the grid. I have a presentation business layer type class UI.Helper that helps me with this. Basically, its a method that takes in the DataGrid we're working with and binds data to it.
19 20 /// Binds users to a datagrid
21 ///
22 ///
23 ///
24 ///
25 public static void Bind(SATK.Web.UI.WebControls.DataGrid dg,
string whereClause,
string orderByColumns,
bool isAscending)
26 {
27 dg.DataSource = SATK.BusinessLogic.User.LoadCollection(whereClause,orderByColumns.Split(','),isAscending);
28 dg.DataBind();
29 }
Now on my page I will need to set the datagrid's Bind method by using the delegate.
15 ///
16 protected SATK.Web.UI.WebControls.DataGrid dgUsers;
...
23 private SATK.Web.UI.WebControls.DataGrid.BindDelegate bd;
24
25 private void Page_Load(object sender, System.EventArgs e)
26 {
...
32 bd = new SATK.Web.UI.WebControls.DataGrid.BindDelegate(SATK.Web.UI.Helpers.UserHelper.Bind);
33 dgUsers.SetBindDelegate(bd);
then, we'll just need to Bind the grid.
38 if(!Page.IsPostBack)
39 {
...
42 dgUsers.BindGrid(bd,null,"DateAccountCreated",true);
43 }
On the HTML page you work it just like you would a DataGrid (I've also created my own custom columns, you can use regular asp:BoundColumn, TemplateColumn etc):
<%@ Register TagPrefix="satk" Namespace="SATK.Web.UI.WebControls" Assembly="SATK.Web.UI" %>
<%@ Register TagPrefix="satk" Namespace="SATK.Web.UI.WebControls" Assembly="SATK.Web.UI" %>
...
<satk:DataGrid Runat="server" ID="dgUsers" EmptyDataMessage="No users found.">
<Columns>
<satk:BoundColumn SortExpression="FirstName" DataField="FirstName" HeaderText="FirstName" />
<satk:BoundColumn DataField="LastName" SortExpression="LastName" HeaderText="LastName" />
<satk:BoundColumn DataField="Username" SortExpression="Username" HeaderText="Username" />
</Columns>
</satk:DataGrid>
And that's it. Honestly, there is a bit of code above, but once do it once, you'll be able to create sortable, pagable grids quickly. If you have something different setup for your business layer objects, feel free to modify the delegate and the other code and you'll have your own custom grid.