导言
分页和排序是在WEB应用程序中展现数据常见的功能。比如,当我们在一个网上书店搜索ASP.NET书籍的时候,可能有几百本相关书籍,但是我们只希望每页显示10条有效记录。而且,我们还希望结果能根据标题、价格、页数和作者等等来进行排序。过去的23个教程中我们研究了如何建立各种报表,包括在界面上添加编辑和删除数据。但是我们没有研究如何对数据进行排序,对于分页我们也仅在研究DetailsView和FormView控件的时候看到。
Step 1:添加分页和排序页面
在我们开始以前,首先让我们花些时间来添加包括本篇在内的最近四篇教程需要用到的页面。我们先在项目中新建一个称作PagingAndSorting的文件夹,接下来,为目录新增以下几个页面,并配置为使用Site.master母板页。
Default.aspx
SimplePagingSorting.aspx
EfficientPaging.aspx
SortParameter.aspx
CustomSortingUI.aspx
图1:创建一个PagingAndSorting文件夹并且添加教程的页面
下一步,让我们打开Default.aspx页面并且从UserControls中拖拽SectionLevelTutorialListing.ascx用户控件到设计界面。我们在母板页和站点导航教程中创建的这个用户控件遍历站点地图并且以符号列表形式把它们呈现出来。
图2:把SectionLevelTutorialListing.ascx用户控件加入Default.aspx
要让显示我们将要创建的分页和排序教程,我们需要把他们加入站点地图中。打开Web.sitemap文件并且把下列代码加在“编辑、插入和删除”siteMapNode标记之后:
- <siteMapNode title="Paging and Sorting" url="~/PagingAndSorting/Default.aspx"
- description="Samples of Reports that Provide Paging and Sorting Capabilities">
- <siteMapNode url="~/PagingAndSorting/SimplePagingSorting.aspx"
- title="Simple Paging & Sorting Examples"
- description="Examines how to add simple paging and sorting support." />
- <siteMapNode url="~/PagingAndSorting/EfficientPaging.aspx"
- title="Efficiently Paging Through Large Result Sets"
- description="Learn how to efficiently page through large result sets." />
- <siteMapNode url="~/PagingAndSorting/SortParameter.aspx"
- title="Sorting Data at the BLL or DAL"
- description="Illustrates how to perform sorting logic in the Business Logic
- Layer or Data Access Layer." />
- <siteMapNode url="~/PagingAndSorting/CustomSortingUI.aspx"
- title="Customizing the Sorting User Interface"
- description="Learn how to customize and improve the sorting user interface." />
- </siteMapNode>
图3:更新站点地图使之包含新的页面
Step 2:在GridView中显示产品信息
在我们真正实现分页和排序功能以前,让我们首先创建一个标准的,没有排序和分页功能的GridView来显示产品信息。其实这个工作我们已经做过很多次了,大家也应该很熟悉了。首先打开SimplePagingSorting.aspx页面并且从工具箱中拖一个GridView控件到设计器,配置它的ID属性为Products。接着,新建一个ObjectDataSource并使用ProductsBLL类的GetProducts()方法来获取所有的产品信息。
图4:使用GetProducts()方法获取所有产品信息
因为这个报表是只读的,我们不需要把ObjectDataSource的Insert(), Update(), 和 Delete()方法映射到相应的ProductsBLL方法,因此,对于UPDATE, INSERT, 和 DELETE页我们从下拉列表中选取(None)。
图5:对于UPDATE, INSERT, 和DELETE页,我们从下拉列表中选择(None)选项
下一步,让我们调整GridView的字段使之只显示产品名、供应商、分类、价格和状态。另外,我们可以尽管进行一些格式上的调整,比如配置价格的HeaderText以符合我们的货币形式。经过这些修改之后,我们的GridView代码应该和下面的差不多:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<asp:GridView ID= "Products" runat= "server" AutoGenerateColumns= "False" DataKeyNames= "ProductID" DataSourceID= "ObjectDataSource1" EnableViewState= "False" > <Columns> <asp:BoundField DataField= "ProductName" HeaderText= "Product" SortExpression= "ProductName" /> <asp:BoundField DataField= "CategoryName" HeaderText= "Category" ReadOnly= "True" SortExpression= "CategoryName" /> <asp:BoundField DataField= "SupplierName" HeaderText= "Supplier" ReadOnly= "True" SortExpression= "SupplierName" /> <asp:BoundField DataField= "UnitPrice" HeaderText= "Price" SortExpression= "UnitPrice" DataFormatString= "{0:C}" HtmlEncode= "False" /> <asp:CheckBoxField DataField= "Discontinued" HeaderText= "Discontinued" SortExpression= "Discontinued" /> </Columns> </asp:GridView> |
图6显示了在浏览器中的效果,但是注意到,我们在一个屏幕上显示产品。显示了每个产品的名字、分类、供应商、价格和状态。
图6:每个产品都列出来了
Step 3:添加分页支持
在一个屏幕上列出所有的产品对于用户查看数据非常不方便。要让结果更加可以管理,我们应该把数据分几个页面来呈现,并提供用户切换页面的功能。要实现这个只需要选择GridView智能标签前的Paging复选框即可(其实就是把AllowPaging属性设置为true)。
图7:点击Enable Paging复选框来支持分页
开启分页以后就能限制每页显示的记录数量并且在GridView中增加了分页导航。默认如图7,是一系列页面的数字,运行用户快速从一个页面切换到另一个。其实我们并不陌生,在为过去的教程中我们已经为DetailsView个FormView控件提供过分页支持。
DetailsView和FormView控件仅仅支持每一页显示一条记录。但是对于GridView,有一个PageSize 属性,能让我们配置每页显示的记录数(默认是设置为10)。
GridView, DetailsView 和 FormView分页导航能使用下面的属性来配置:
PagerStyle –指示分页导航的样式,能设置BackColor, ForeColor, CssClass, HorizontalAlign等等。
PagerSettings –包含大量属性来自定义分页导航的功能;PageButtonCount代表显示在底部分页导航的最大页面数(默认为10);Mode 属性 代表分页操作的形式,能设置为:
NextPrevious –显示下一页和上一页按钮,让用户一次朝后或者朝前翻一页
NextPreviousFirstLast –除了下一页和上一页按钮外,还提供第一页和最后一页按钮,能让用户快速定位到首页或者末页数据
Numeric –显示一系列页面数字,让用户直接点击数字切换到相应页面
NumericFirstLast –除了页面数字以外还提供第一页和最后一页按钮,让用户能快速定位到首页或者末页数据,只有当没有显示首页或者末页数字时才显示按钮
此外,GridView, DetailsView和 FormView还提供了PageIndex 和 PageCount属性来指示当前呈现的页面和页面总数。PageIndex属性从0开始编号,因此我们浏览第一页的时候就为0,而PageCount是从1开始编号的,因此PageIndex的范围在0和PageCount – 1之间。
让我们再花一些时间来改进GridView分页导航的默认外观。首先,我们把分页导航居右并且设置为灰色背景色。我们不希望直接设置GridView的PagerStyle属性来实现,而是在Styles.css中创建一个称作PagerRowStyle 的CSS类,然后设置主题文件中PagerStyle的CssClass属性进行关联。首先打开Styles.css然后把下面CSS类定义加入文件:
1
2
3
4
5
|
.PagerRowStyle { background-color : #ddd ; text-align : right ; } |
接着,打开App_Themes 文件夹中DataWebControls 文件夹下的GridView.skin文件。我们在母板页和站点导航教程中提到过,Skin文件能为WEB控件指定一个默认的属性值。因此,我们设置PagerStyle的CssClass属性为PagerRowStyle。同样,让我们配置分页导航来显示5个页面数字(NumericFirstLast模式)。
1
2
3
4
5
6
7
8
9
|
<asp:GridView runat= "server" CssClass= "DataWebControlStyle" > <AlternatingRowStyle CssClass= "AlternatingRowStyle" /> <RowStyle CssClass= "RowStyle" /> <HeaderStyle CssClass= "HeaderStyle" /> <FooterStyle CssClass= "FooterStyle" /> <SelectedRowStyle CssClass= "SelectedRowStyle" /> <PagerStyle CssClass= "PagerRowStyle" /> <PagerSettings Mode= "NumericFirstLast" PageButtonCount= "5" /> </asp:GridView> |
分页用户体验
我们为GridView启用了分页又在GridView.skin文件中配置了PagerStyle和PagerSettings,图8显示了浏览器中的呈现。注意到,每页只有10条记录,从分页导航上我们可以知道现在浏览的是第一页的数据。
图8:启用分页后每次只显示一部分记录
当用户点击分页导航中某一个页面数字,页面回发并且呈现所请求的页面的数据。图9显示了点击最后一页的效果。注意到,最后一页只有一条记录,因为总共有81条记录,每页显示10条记录,8页80条,最后一页就剩下一条了。
图9:点击一个页面数字页面回发显示相应的一组记录
分页服务端工作方式
当用户点击了分页导航中的按钮后,页面回发并开始下面服务端工作流:
1.GridView(或者 DetailsView 或者 FormView) PageIndexChanging时间触发
2.ObjectDataSource从BLL获取所有数据;GridView的PageIndex和PageSize属性用来检测哪些从BLL获取的数据需要显示在页面上
3.GridView的PageIndexChanged事件触发
在第二步中,ObjectDataSource从数据源获取所有数据。如果我们仅仅是把AllowPaging属性设置为true来进行分页的话,默认方式分页的WEB控件就会获取所有数据并从中挑选合适的以HTML呈现在浏览器上。出为数据库中的数据被BLL或者ObjectDataSource进行缓存,否则对于大数据量的系统或者大并发的应用程序来说这种工作方式是非常低效的。
在下一个教程中,我们将会研究如何实现自定义分页。使用自定义分页我们就能指示ObjectDataSource精确地获取用户请求的那些数据。你能想象到,对于大数据的记录集,自定义分页能极大增加效率。
注意:默认的分页方式不适合大数据集合系统和大流量的多并发情况,自定义分页能改善但是它确实需要很多修改来实现(而不是象默认分页方式那样仅仅选择一个复选框)。因此,默认的分页方式对于小型的,小流量的网站来说比较合适的,因为它的实现确实非常简单和快速。
例如,如果我们确信数据库内不会多余100个产品。如果我们使用自定义分页的话,多花的那些时间和赢得的效率来说是不值得的。然而,如果我那把有几千几万的产品的话,不实现自定义分页的话就会极大地降低我们应用程序的性能。
Step 4:自定义分页体验
数据Web控件提供了一些属性来增进分页体验。例如,PageCount属性指示总共有多少页面,PageIndex属性指示当前访问的页面,并能通过设置它来快速定位到某一页。为了演示如何使用这些属性来增进用户分页体验,让我们在页面上添加一个Label Web控件来显示用户当前访问的页面,添加一个DropDownList控件来让用户快速切换到某个页面。
首先,在页面上添加一个Label Web控件,设置它的ID属性为PagingInformation,然后把Text清空。接着,为GridView的DataBound事件创建一个事件处理器,然后添加如下代码:
1
2
3
4
5
|
protected void Products_DataBound( object sender, EventArgs e) { PagingInformation.Text = string .Format( "You are viewing page {0} of {1}..." , Products.PageIndex + 1, Products.PageCount); } |
这个事件处理器指定了PagingInformation标签的Text属性为用户当前访问的页面-Products.PageIndex + 1(我们在这里+1因为Products.PageIndex属性是从0开始编号的)和页面总数(Products.PageCount)。我在DataBound事件处理器而不是PageIndexChanged事件处理器中进行这个操作的原因在于,DataBound事件在每次数据绑定到GridView的时候都会触发,而PageIndexChanged仅仅在页面切换的时候触发。当GridView绑定首页的时候PageIndexChanging还没有触发(而DataBound事件能触发)。
好了,用户现在能看到他们正在访问的页面和页面总数。
图10:显示当前页和页面总数
除了Label控件,我们再来添加一个DropDownList控件来显示所有的页数并选定当前浏览的页面。这样,用户就能选择DropDownList中的某一选项来快速切换到新的页面索引,我们首先拖一个DropDownList到设计器,然后设置ID属性为PageList然后选择启用AutoPostBack。
接着,在DataBound中加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
// Clear out all of the items in the DropDownList PageList.Items.Clear(); // Add a ListItem for each page for ( int i = 0; i < Products.PageCount; i++) { // Add the new ListItem ListItem pageListItem = new ListItem( string .Concat( "Page " , i + 1), i.ToString()); PageList.Items.Add(pageListItem); // select the current item, if needed if (i == Products.PageIndex) pageListItem.Selected = true ; } |
这段代码首先清楚了PageList DropDownList中所有的项。既然我们不能预料到页面数会不会改变,看上去这个操作可能有些多余。但是其它用户可能会并发使用系统来从Products表中添加或者移除记录。这样的插入或者删除操作可能会改变数据的页数。
接着,我们重新创建页数并选择GridView PageIndex作为默认。我们循环0到PageCount – 1进行新增每一个ListItem,如果当前循环所以等于GridView的PageIndex属性的话,我们把这个项的Selected属性设置为true。
最后,我们需要为DropDownList的SelectedIndexChanged事件创建一个事件处理器。当用户每次选择了一个不同页面的时候触发,我们只需要双击设计器中的DropDownList来创建事件处理器,然后添加下面代码:
1
2
3
4
5
|
protected void PageList_SelectedIndexChanged( object sender, EventArgs e) { // Jump to the specified page Products.PageIndex = Convert.ToInt32(PageList.SelectedValue); } |
如图11显示,只不过是改变GridView的PageIndex属性并重新绑定GridView。在GridView的DataBound事件处理器中,相应的DropDownList ListItem被选择。
图11:用户选择下拉列表Page 6项后就能切换到第六页
Step 5:添加双向排序支持
增加双向排序的支持和增加分页支持一样简单-只需要选择GridView 智能标签的Enable Sorting选项(它会设置GridView的AllowSorting property 属性为true)。这样,GridView每一个字段的标题都会显示为LinkButtons,点击后页面就会回发,所点击列的所有数据就会以升序显示。再次点击同一个LinkButton就能以降序显示。
注意:如果你使用一个自定义的数据访问层而不是强类型DataSet的话,你可能找不到GridView的Enable Sorting选项。因为ADO.NET DataTable提供了Sort方法使用指定标准对DataTable的DataRow进行排序,因此强类型DataSet提供了排序支持。
如果你的DAL不返回支持排序的对象,我们就需要配置ObjectDataSource来实现对业务逻辑层返回数据的排序。我们将会在将来的教程中研究如何排序业务逻辑或者数据访问层中的数据。
排序的LinkButton以HTML链接的形式呈现,当前的颜色(未访问过未蓝色访问过为暗红色)和标题的背景色有了冲突,让我们设置所有标题中的链接在访问过和未访问的情况下都为白色。我们通过在Styles.css中添加如下的类来实现:
1
2
3
4
|
.HeaderStyle a, .HeaderStyle a:visited { color : White; } |
这段代码表示,使用HeaderStyle类中的所有链接都以白色进行显示。
在定义了CSS后,页面浏览器中的效果如图12,图1也显示了Price字段标题上的链接被点击后的效果。
图12:结果根据UnitPrice以正序形式进行排序
研究排序工作方式
所有的GridView字段-BoundField, CheckBoxField, TemplateField等等-都有SortExpression属性指示当标题上的排序链接点击后的排序方式。GridView同样也有一个SortExpression属性。当排序LinkButton被点击后,GridView把它的SortExpression设置为该字段的SortExpression,接着,数据就重新按照GridView的SortExpression属性以一定次序从ObjectDataSource返回。下面列出了用户排序时GridView具体的流程步骤:
1.GridView的Sorting event 触发
2.GridView的SortExpression 属性设置为点击LinkButton所在字段的SortExpression
3.ObjectDataSource重新从BLL中获取所有数据并根据GridView的 SortExpression来排序数据
4.GridView的PageIndex被置0,也就是用户转到了第一页数据(假设分页是开启的)
5.GridView的Sorted 事件触发
和默认分页方式一样,默认排序方式从BLL中获取所有数据进行排序。在未分页或者使用默认方式分页的情况下,我们没有办法改善性能(除了缓存数据)。然而在后续教程中,我们能通过自定义分页来使排序更有效率。
When binding an ObjectDataSource to the GridView through the drop-down list in the GridView's smart tag, each GridView field automatically has its SortExpression property assigned to the name of the data field in the ProductsRow class. For example, the ProductName BoundField's SortExpression is set to ProductName, as shown in the following declarative markup:
如果我们通过GridView智能标签的下拉框把ObjectDataSource绑定到GridView的话,GridView的字段就会自动把SortExpression属性设置为相应的ProductsRow类。比如,ProductName BoundField的SortExpression就设置为ProductName,代码如下:
1
2
|
<asp:BoundField DataField= "ProductName" HeaderText= "Product" SortExpression= "ProductName" /> |
一个字段能通过清除SortExpression(设置为空字符串)属性来禁止排序。想象下如果我们不希望用户根据价格来排序产品,我们就可以通过在代码标签或者在字段对话框(点击GridView智能标签的Edit Columns链接)中移除UnitPrice BoundField的SortExpression属性例子实现。
图13:结果根据UnitPrice以升序进行排序
一旦移除UnitPrice BoundField 的SortExpression属性,标题上就只是文字而不是链接了,防止用户根据价格进行排序。
图14:移除了SortExpression属性,用户就不能再根据产品价格进行排序了
编程排序GridView
我们还可以使用GridView的Sort 方法编程对GridView的内容进行排序。只需要传入SortExpression和SortDirection(Ascending或者Descending)即可。
我们关闭了UnitPrice的排序是考虑到不希望用户只去买最便宜的东西。然而,我们希望鼓励用户去买最贵的产品,所以我们希望能按照价格排序但是仅提供从价格最高到价格最低这么一种排序方式。
要实现这样的需求,我们首先在页面上添加一个Button Web控件,设置它的ID属性为SortPriceDescending,Text属性为“Sort by Price”。然后在设计器双击按钮控件来创建按钮的Click事件处理器,加入如下代码:
1
2
3
4
5
|
protected void SortPriceDescending_Click( object sender, EventArgs e) { // Sort by UnitPrice in descending order Products.Sort( "UnitPrice" , SortDirection.Descending); } |
点击按钮页面转向第一页并以价格排序,从最贵的到最便宜的(见图15)。
图15:点击按钮让产品从最贵到最便宜进行排序
总结
在这个教程中我们已经说了如何实现默认分页和排序,仅仅需要选择一个复选框来实现!当用户排序或者切换页面的时候都会有类似的工作方式:
1.页面回发
2.数据Web控件pre-level的事件触发(PageIndexChanging 或者 Sorting)
3.所有信息从ObjectDataSource重新获取
4.数据Web控件post -level的事件触发(PageIndexChanged或者 Sorted)
我们实现了报表的基本分页和排序,但是要取得更好的性能我们还需要创建自定义的分页来或者进一步改善分页和排序界面。后面的教程会继续讨论相关主题。
编程愉快!
关于作者
Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用微软Web技术。Scott是个独立的技 术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作,24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也可以通过他的博客http://ScottOnWriting.NET与他联系。