4.3 GridView控件开发
数据显示控件——GridView的运行界面,如图4-2所示。
从图中可以看出,GridView控件具有分页、排序功能,单击GridView控件下方的分页链接,可以显示指定页面的数据。在分页功能中,最左边是“前一页”链接,最右边则是“后一页”链接,并显示最前面的两个页面及最后面的两个页面,页面的导航功能比较强大。
单击GridView控件上方相关的字段链接,可以对该字段实现排序,如图4-3所示。
图4-2 GridView控件的运行界面
图4-3 GridView控件的排序
图4-3是对字段“City”实现排序后的结果,在GridView控件的最右边,还显示了每条记录所对应的“Edit”链接和“Delete”链接,单击“Edit”链接,就会转到相关的数据编辑页面;单击“Delete”链接,就会删除指定的数据。
为了对需要显示的数据,构建功能较为强大的分页功能,这里构建了一个泛型的分页列表——PagedList<T>类,用于表示分页的基本数据,PagedList<T>类的UML类图如图4-4所示。
图4-4 PagedList<T>的UML类图
从图4-4中可以看出,为实现分页功能,这里定义了分页的几个基本属性,它们分别是页面索引PageIndex(表示当前页面)、一个页面中所显示的数据记录数量PageSize、数据的总记录数量TotalItemCount及总的页面数量TotalPageCount,还定义了SortExpression属性,用于表示需要排序的字符串,IdentityColumnName属性主要表示数据表中的主键字段的名称,以便实现相关的编辑、删除等操作。
PagedList<T>类的实现代码,见代码清单4-5。
代码清单4-5 PagedList<T>的实现代码
1: public class PagedList<T> : List<T>
2: {
3: public PagedList(IEnumerable<T> items, int pageIndex, int pageSize,
int totalItemCount,string identityColumnName,
string sortExpression)
4: {
5: this.AddRange(items);
6: this.PageIndex = pageIndex;
7: this.PageSize = pageSize;
8: this.SortExpression = sortExpression;
9: this.TotalItemCount = totalItemCount;
10: this.TotalPageCount = (int)Math.Ceiling(
totalItemCount / (double)pageSize);
11: this.IdentityColumnName = identityColumnName;
12: }
13:
14: public int PageIndex { get; set; }
15: public int PageSize { get; set; }
16: public string SortExpression { get; set; }
17: public int TotalItemCount { get; set; }
18: public int TotalPageCount { get; private set; }
19: public string IdentityColumnName { get; set; }
20: }
上述代码的实现较为简单,第14行到第19行分别定义了6个属性的读写器,这是C# 3.0中读写器的写法。
代码第3行到第12行定义了PagedList<T>类的构造函数,主要实现列表中元素的添加,以及6个属性的初始化。
在ASP.NET 3.5中,开发者可以使用LINQ关键技术,非常方便地实现各种数据的查询,为实现将LINQ的数据查询结果显示在GridView控件中,可以采用构建LINQ查询的扩展方法,这里构建了一个PageLinqExtensions类,PageLinqExtensions类的UML类图如图4-5所示。
图4-5 PageLinqExtensions的UML类图
从图中可以看出,针对LINQ查询及List类型数据实现了3个重载的扩展方法,通过设置页面索引PageIndex、页面大小PageSize、排序字符串SortExpression及主键字段名称IdentityColumnName,使得LINQ的查询数据(IQueryable)或者List类型的数据是分页列表PagedList<T>类的实例化对象,具有分页功能。
PageLinqExtensions类的实现代码,见代码清单4-6。
代码清单4-6 PageLinqExtensions的实现代码
1: public static class PageLinqExtensions
2: {
3: public static PagedList<T> ToPagedList<T>(
this IQueryable<T> allItems,
int? pageIndex,
int pageSize)
4: {
5: return ToPagedList<T>(allItems, pageIndex, pageSize,null,
String.Empty);
6: }
7:
8: public static PagedList<T> ToPagedList<T>(
this IQueryable<T> allItems,
int? pageIndex,
int pageSize,
string identityColumnName)
9: {
10: return ToPagedList<T>(allItems, pageIndex, pageSize,
identityColumnName, String.Empty);
11: }
12:
13: public static PagedList<T> ToPagedList<T>(
this IQueryable<T> allItems,
int? pageIndex,
int pageSize,
string identityColumnName,
string sort)
14: {
15: var truePageIndex = pageIndex ?? 0;
16: var itemIndex = truePageIndex * pageSize;
17: var pageOfItems = allItems.Skip(itemIndex).Take(pageSize);
18: var totalItemCount = allItems.Count();
19:
20: return new PagedList<T>(pageOfItems, truePageIndex, pageSize,
totalItemCount,identityColumnName, sort);
21: }
22:
23: }
在上述代码中,对LINQ的查询数据IQueryable<T>对象,定义了3个重载的扩展方法ToPagedList<T>,它们分别是第3行到第6行的扩展方法,其中有输入当前页面pageIndex和页面大小pageSize;第8行到第11行的扩展方法,其中有输入当前页面pageIndex、页面大小pageSize及主键字段名称identityColumnName;第13行到第21行的扩展方法,其中有输入当前页面pageIndex、页面大小pageSize、主键字段名称identityColumnName及排序的字符串。
通过定义PageLinqExtensions类,使得LINQ的查询数据IQueryable<T>对象可以使用ToPagedList<T>()方法,使得LINQ的查询数据具有分页、排序功能。
为了让GridView控件具有编辑、删除链接,这里专门创建了一个GridViewOption类,其UML类图如图4-6所示。
图4-6 GridViewOption的UML类图
在GridViewOption类中,定义了表头的显示文字Columns,编辑、删除链接的相关属性,例如,是否显示编辑链接ShowEditButton、编辑链接的文字EditButtonText及编辑功能的动作方法EditAction等。
GridViewOption类的实现代码,见代码清单4-7。
代码清单4-7 GridViewOption的实现代码
1: public class GridViewOption
2: {
3: private bool showEditButton = true;
4: private bool showDeleteButton = true;
5:
6: private string editButtonText = "Edit";
7: private string deleteButtonText = "Delete";
8:
9: private string editAction = "Edit";
10: private string deleteAction = "Delete";
11:
12: public bool ShowEditButton
13: {
14: get { return showEditButton; }
15: set { showEditButton = value; }
16: }
17:
18: public bool ShowDeleteButton
19: {
20: get { return showDeleteButton; }
21: set { showDeleteButton = value; }
22: }
23:
24: public string EditButtonText
25: {
26: get { return editButtonText; }
27: set { editButtonText = value; }
28: }
29:
30: public string DeleteButtonText
31: {
32: get { return deleteButtonText; }
33: set { deleteButtonText = value; }
34: }
35:
36: public string EditAction
37: {
38: get { return editAction; }
39: set { editAction = value; }
40: }
41:
42: public string DeleteAction
43: {
44: get { return deleteAction; }
45: set { deleteAction = value; }
46: }
47:
48: private string[] columns;
49:
50: public string[] Columns
51: {
52: get { return columns ; }
53: set { columns= value; }
54: }
55: }
上述的代码实现也比较简单,主要定义了7个基本属性的读写器,而且这7个属性还具有默认值。也就是说,在默认情况下,是显示“Edit”、“Delete”链接的,它们的动作方法名称分别为Edit和Delete。
开发者如果需要改变这些默认值,只需要重新设置GridViewOption类中的相关属性即可。
在实现了LINQ查询数据的分页、排序功能,定义了编辑、删除链接的相关属性之后,此时就需要专门实现GridView控件,也就是说,需要对HtmlHelper类实现相关的扩展方法,从而实现GridView控件的输出。
这里专门定义了一个GridViewHelper类,它的UML类图如图4-7所示。
图4-7 GridViewHelper的UML类图
在GridViewHelper类中,定义了3个重载的GridView<T>扩展方法,通过设置模型数据、显示字段,以及编辑、删除链接等参数,即可显示功能较为强大的GridView数据结果。
GridViewHelper类的实现代码,见代码清单4-8。
代码清单4-8 GridViewHelper的实现代码
1: public static class GridViewHelper
2: {
3: public static string GridView<T>(this HtmlHelper helper)
4: {
5: return GridView<T>(helper, null, null,new GridViewOption());
6: }
7:
8: public static string GridView<T>(this HtmlHelper helper, object data)
9: {
10: return GridView<T>(helper, data, null,new GridViewOption());
11: }
12:
13: public static string GridView<T>(this HtmlHelper helper, object data,
string[] columns, GridViewOption options)
14: {
15: var items = (IEnumerable<T>)data;
16: if (items == null)
17: items = (IEnumerable<T>)helper.ViewData.Model;
18:
19: if (columns == null)
20: columns = typeof(T).GetProperties().Select(p => p.Name)
.ToArray();
21:
22: var writer = new HtmlTextWriter(new StringWriter());
23: writer.RenderBeginTag(HtmlTextWriterTag.Table);
24:
25: writer.RenderBeginTag(HtmlTextWriterTag.Thead);
26: RenderHeader(helper, writer, columns, options);
27: writer.RenderEndTag();
28:
29: string identityColumnName= ((PagedList<T>)items)
.IdentityColumnName ;
30:
31: writer.RenderBeginTag(HtmlTextWriterTag.Tbody);
32: foreach (var item in items)
33: RenderRow<T>(helper, writer, columns, item, identityColumnName,
options);
34: writer.RenderEndTag();
35:
36: RenderPagerRow<T>(helper, writer, (PagedList<T>)items,
columns.Count());
37:
38: writer.RenderEndTag();
39:
40: return writer.InnerWriter.ToString();
41: }
42:
43: private static void RenderHeader(HtmlHelper helper,
HtmlTextWriter writer, string[] columns, GridViewOption options)
44: {
45: writer.RenderBeginTag(HtmlTextWriterTag.Tr);
46: foreach (var columnName in columns)
47: {
48: writer.RenderBeginTag(HtmlTextWriterTag.Th);
49: var currentAction = (string)helper.ViewContext
.RouteData.Values["action"];
50: string link=null;
51: if (options.Columns == null)
52: link = helper.ActionLink(columnName, currentAction,
new { sort = columnName });
53: else {
54: link = helper.ActionLink(options.Columns[i], currentAction,
new { sort = columnName });
55: i++; }
56: writer.Write(link);
57: writer.RenderEndTag(); }
58: bool showEditColumn = options.ShowEditButton ||
options.ShowDeleteButton;
59: if (showEditColumn){
60: writer.RenderBeginTag(HtmlTextWriterTag.Th);
61: writer.Write(helper.Encode(""));
62: writer.RenderEndTag(); }
63: writer.RenderEndTag();
64: }
65:
66: private static void RenderRow<T>(HtmlHelper helper,
HtmlTextWriter writer, string[] columns, T item ,
string identityColumnName, GridViewOption options)
67: {
68: writer.RenderBeginTag(HtmlTextWriterTag.Tr);
69: foreach (var columnName in columns)
70: {
71: writer.RenderBeginTag(HtmlTextWriterTag.Td);
72: var value = typeof(T).GetProperty(columnName)
.GetValue(item, null) ?? String.Empty;
73: writer.Write(helper.Encode(value.ToString()));
74: writer.RenderEndTag();
75: }
76:
77: bool showEditColumn = options.ShowEditButton ||
options.ShowDeleteButton;
78:
79: if (showEditColumn)
80: {
81: var identityVaule = typeof(T).GetProperty(identityColumnName)
.GetValue(item, null);
82: writer.RenderBeginTag(HtmlTextWriterTag.Td);
83:
84: if ( options.ShowEditButton)
85: {
86: var link = helper.ActionLink(options.EditButtonText,
options.EditAction , new { id =identityVaule });
87: writer.Write(link);
88: writer.Write(" ");
89: }
90:
91: if (options.ShowDeleteButton )
92: {
93: var link = helper.ActionLink(options.DeleteButtonText,
options.DeleteAction , new { id = identityVaule });
94: writer.Write(link);
95: }
96: writer.RenderEndTag();
97: }
98: writer.RenderEndTag();
99: }
100:
101: private static void RenderPagerRow<T>(HtmlHelper helper,
HtmlTextWriter writer, PagedList<T> items, int columnCount)
102: {
103: int nrOfPagesToDisplay = 10;
104:
105: if (items.TotalPageCount == 1)
106: return;
107:
108: writer.RenderBeginTag(HtmlTextWriterTag.Tr);
109: writer.AddAttribute(HtmlTextWriterAttribute.Colspan,
columnCount.ToString());
110: writer.RenderBeginTag(HtmlTextWriterTag.Td);
111: var currentAction = (string)helper.ViewContext.RouteData
.Values["action"];
112:
113: if (items.PageIndex >= 1)
114: {
115: var linkText = String.Format("{0}", "<<<");
116: var link = helper.ActionLink(linkText, currentAction,
new { page = items.PageIndex, sort = items.SortExpression });
117: writer.Write(link + " ");
118: }
119:
120: int start = 0;
121: int end = items.TotalPageCount;
122:
123: if (items.TotalPageCount > nrOfPagesToDisplay)
124: {
125: int middle = (int)Math.Ceiling(nrOfPagesToDisplay / 2d) - 1;
126: int below = (items.PageIndex - middle);
127: int above = (items.PageIndex + middle);
128:
129: if (below < 4)
130: {
131: above = nrOfPagesToDisplay;
132: below = 0;
133: }
134: else if (above > (items.TotalPageCount - 4))
135: {
136: above = items.TotalPageCount;
137: below = (items.TotalPageCount - nrOfPagesToDisplay);
138: }
139:
140: start = below;
141: end = above;
142: }
143:
144: if (start > 3)
145: {
146: var linkText = String.Format("{0}", "1");
147: var link = helper.ActionLink(linkText, currentAction,
new { page = 1, sort = items.SortExpression });
148: writer.Write(link + " ");
149:
150: linkText = String.Format("{0}", "2");
151: link = helper.ActionLink(linkText, currentAction,
new { page = 2, sort = items.SortExpression });
152: writer.Write(link + " ");
153: writer.Write(String.Format("{0}", "..."));
154: }
155:
156: for (var i = start; i < end; i++)
157: {
158: if (i == items.PageIndex)
159: {
160: writer.Write(String.Format("<strong>{0}</strong> ",
i + 1));
161: }
162: else
163: {
164: var linkText = String.Format("{0}", i + 1);
165: var link = helper.ActionLink(linkText, currentAction,
new { page = i + 1, sort = items.SortExpression });
166: writer.Write(link + " ");
167: }
168: }
169:
170: if (end < (items.TotalPageCount - 3))
171: {
172: writer.Write(String.Format("{0}", "..."));
173: var linkText = String.Format("{0}", items.TotalPageCount - 1);
174: var link = helper.ActionLink(linkText, currentAction,
new { page = items.TotalPageCount - 1,
sort = items.SortExpression });
175: writer.Write(link + " ");
176:
177: linkText = String.Format("{0}", items.TotalPageCount);
178: link = helper.ActionLink(linkText, currentAction,
new { page = items.TotalPageCount,
sort = items.SortExpression });
179: writer.Write(link + " ");
180: }
181:
182: if (items.PageIndex + 2 <= items.TotalPageCount)
183: {
184: var linkText = String.Format("{0}", ">>>");
185: var link = helper.ActionLink(linkText, currentAction,
new { page = items.PageIndex + 2,
sort = items.SortExpression });
186: writer.Write(link + " ");
187: }
188:
189: writer.RenderEndTag();
190: writer.RenderEndTag();
191: }
192: }
上述的代码比较长,不过可以分为4个部分,它们分别是第1部分的第3行到第41行、第2部分的第43行到第64行、第3部分的第66行到第99行及第4部分的第101行到第191行。
第1部分是代码的主要部分,其中定义了3个重载的GridView<T>扩展方法。在第3行到第6行的扩展方法中,不需要输入任何参数;在第8行到第11行的扩展方法中,需要输入模型数据参数;而在第13行到第41行的扩展方法中,则需要输入模型数据、显示数据字段,以及编辑、删除等相关设置。
在扩展方法中,如果没有输入模型数据,代码第17行会通过HtmlHelper类,自动获取视图ViewData中的模型数据Model;如果没有输入需要显示的数据字段,代码第20行会自动获得指定数据表的所有字段名称。
代码第22行创建HtmlTextWriter类的一个实例化对象writer,以便构建显示数据表格的HTML语句,其中第26行构建数据表的表头,第29行获得数据表中的主键字段,而代码第33行则实现数据显示的HTML语句,第36行代码构建分页界面的HTML语句。
第2部分代码主要实现数据表的表头。第45行构建表格中的多行标记,第48行则构建表格中的多列标记。第51行到第57行,用于设置表头的字段;第59行到第63行,则根据用户对编辑、删除的相关设置,判断是否显示相对应的空白表头。
第3部分代码主要实现数据表的数据显示。其中第68行到第75行显示数据表的数据,第79行到第97行用于显示数据的编辑、删除链接。
第4部分代码主要实现数据页码的显示。其中第103行设置页码显示的个数为10,第105行判断总的页码数量,如果仅为一个页面,则在106行直接退出。第113行到第118行显示最左边的页码显示部分“<<<”; 第123行到第138行计算需要显示页码的开始和结束部分;第144行到第154行显示左边的页码省略部分“…”;第156行到第168行显示中间部分的页码;第170行到第180行显示右边的页码省略部分“…”;第182行到第187行显示最右边的页码显示部分“>>>”。
LINQ是ASP.NET 3.5所提供的一种新的查询语言,开发者可以利用LINQ非常方便地构建类型安全的查询语句,例如如下语句:
var model = (from c in db.Customers select c).OrderBy(c=>c.CompanyName);
针对数据表Customers查询所有的字段,并且按照CompanyName字段排序,在实现查询的排序功能时,这里采用了Lambda表达式,实际上是一种静态的排序,也就是说,不能实现参数化的字段排序,即LINQ动态查询。
微软公司的LINQ开发团队,为了让开发者实现LINQ动态查询,即提供字符串形式的参数,专门开发了实现LINQ动态查询的类库——DynamicLibrary。
开发者可以免费使用这个类库DynamicLibrary,轻松实现LINQ动态查询,代码如下:
var model = (from c in db.Customers select c).OrderBy(sort);
在上述代码中,排序字段中输入了字符串参数sort变量,实现了LINQ的参数化,即动态排序。
关于类库DynamicLibrary的其他使用方法,请参照相关的文档说明。
在使用GridView控件时,需要在控制器中设置分页、排序参数,在视图中设置编辑、删除等链接选项,以下分别予以说明。
GridView中分页、排序参数的设置,是在控制器相关的方法中实现的,例如如下代码:
var model1 = (from c in db.Customers select c).OrderBy(sort)
.ToPagedList(page, 5,"CustomerID", sort);
在上述代码中,排序字段中输入了字符串参数sort变量,实现了LINQ的动态排序,然后通过ToPagedList()扩展方法,设置了当前页面page、页面显示数为5、数据表主键字段“CustomerID”及排序参数sort。
在视图页面中,代码设置如下:
<% =Html.GridView<Customers>(Model, new string[] { "CustomerID",
"CompanyName", "ContactName", "Address", "City" }),
new GridViewOption() %>
在上述代码中,由于显示的是数据表Customers中的数据,因此GridView控件的类型为Customers,其中传入了两个参数,它们分别是模型数据Model和需要显示的数据字段,上述代码的运行界面如图4-8所示。
图4-8 GridView控件的运行界面
如果需要修改编辑、删除链接的文字显示等,可以设置如下的代码:
<% =Html.GridView<Customers>(Model, new string[] { "CustomerID",
"CompanyName", "ContactName", "Address", "City" },
new GridViewOption() { EditButtonText="编辑",DeleteButtonText ="删除" })
%>
上述代码设置了编辑、删除链接的文字分别为“编辑”和“删除”,其运行界面如图4-9所示。
图4-9 GridView控件的编辑、删除链接设置
如果需要修改表格头部字段的文字显示等,可以设置如下的代码:
<% =Html.GridView<Customers>(Model,
new string[] { "CustomerID", "CompanyName", "ContactName",
"Address", "City" },
new GridViewOption() { EditButtonText = "编辑",
DeleteButtonText = "删除",
Columns =new string[] {"编号" ,"公司名称", "联系人", "地址", "城市"} })
%>
在上述代码中,通过GridViewOption类中的Columns属性设置了表格头部的文 字分别为“编号”、“公司名称”、“联系人”、“地址”和“城市”,其运行界面如图4-10所示。
图4-10 GridView控件的表格头部设置
这里需要说明的是,Columns属性要与其前面所设置的显示字段一致。