作者 :斯科特·米切尔
在联机应用程序中显示数据时,分页和排序是两个非常常见的功能。 在本教程中,我们将初步了解如何向报告中添加排序和分页,然后在未来的教程中进一步扩展这些功能。
介绍
在联机应用程序中显示数据时,分页和排序是两个非常常见的功能。 例如,在联机书店搜索 ASP.NET 书籍时,可能有数百本此类书籍,但列出搜索结果的报告仅列出每页 10 个匹配项。 此外,结果可以按标题、价格、页面计数、作者姓名等进行排序。 虽然过去 23 个教程已经介绍了如何生成各种报表,包括允许添加、编辑和删除数据的接口,但我们没有研究如何对数据进行排序以及我们所看到的唯一分页示例是 DetailsView 和 FormView 控件。
在本教程中,我们将了解如何向报表添加排序和分页,只需选中几个复选框即可完成此作。 遗憾的是,这种简单的实现有其缺点,排序接口还有改进的空间,而分页功能并不是为了有效地处理大型结果集而设计的。 未来的教程将探讨如何克服开箱即用的分页和排序解决方案的限制。
步骤 1:添加分页和排序教程网页
在开始本教程之前,让我们先花点时间添加本教程和接下来的三个教程所需的 ASP.NET 页面。 首先在项目中创建一个名为 PagingAndSorting
的新文件夹。 接下来,将以下五个 ASP.NET 页添加到此文件夹中,其中所有页面都配置为使用母版页 Site.master
:
Default.aspx
SimplePagingSorting.aspx
EfficientPaging.aspx
SortParameter.aspx
CustomSortingUI.aspx
图 1:创建 PagingAndSorting 文件夹并添加教学 ASP.NET 页面
接下来,打开Default.aspx
页面,并将SectionLevelTutorialListing.ascx
用户控件从UserControls
文件夹拖动到设计图面上。 我们在母版页和网站导航教程中创建的此用户控件会枚举网站地图,并以项目符号列表显示当前部分的教程。
图 2:将 SectionLevelTutorialListing.ascx 用户控件添加到Default.aspx
为了使项目符号式列表显示我们将要创建的分页和排序教程,我们需要将它们添加到网站地图。
Web.sitemap
打开该文件,并在编辑、插入和删除网站地图节点标记后添加以下标记:
<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:更新网站地图以包括新的 ASP.NET 页面
步骤 2:在 GridView 中显示产品信息
在实际实现分页和排序功能之前,让我们先创建列出产品信息的标准不可排序、不可分页的 GridView。 这是我们在本教程系列中之前多次完成的任务,因此这些步骤应该很熟悉。 首先打开页面并将 SimplePagingSorting.aspx
GridView 控件从工具箱拖到设计器上,将其 ID
属性设置为 Products
。 接下来,创建一个新的 ObjectDataSource,它使用 ProductsBLL 类的方法 GetProducts()
返回所有产品信息。
图 4:使用 GetProducts 方法检索有关所有产品的信息
由于此报表是只读报表,因此无需将 ObjectDataSource s Insert()
Update()
或Delete()
方法映射到相应的ProductsBLL
方法;因此,请从 UPDATE、INSERT 和 DELETE 选项卡的下拉列表中选择(无)。
图 5:在 UPDATE、INSERT 和 DELETE 选项卡的 Drop-Down 列表中选择“无”选项
接下来,让我们自定义 GridView 字段,以便仅显示产品名称、供应商、类别、价格和已停用状态。 此外,可以随意进行任何字段级格式更改,例如调整 HeaderText
属性或将价格的格式设置为货币。 这些更改后,GridView 的声明性标记应如下所示:
<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:列出了每个产品(单击以查看全尺寸图像)
步骤 3:添加分页支持
在一个屏幕上列出 所有 产品可能会导致用户浏览数据的信息过载。 为了帮助结果更易于管理,我们可以将数据划分为较小的页面,让用户逐页浏览数据。 若要完成此作,只需选中 GridView 智能标记中的“启用分页”复选框(这会将 GridView 的属性AllowPaging
设置为true
)。
图 7:选中“启用分页”复选框以添加分页支持(单击以查看全尺寸图像)
启用分页会限制每页显示的记录数,并将 分页接口 添加到 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 开始编制索引,这意味着查看数据 PageIndex
的第一页时将等于 0。
PageCount
相较之下,从 1 开始计数,这意味着 PageIndex
的取值限制在 0 到 PageCount - 1
之间。
让我们花点时间改进 GridView 分页接口的默认外观。 具体而言,让我们让分页界面与浅灰色背景右对齐。 与其直接通过 GridView 的PagerStyle
属性设置这些属性,我们不如先在Styles.css
中创建一个名为PagerRowStyle
的 CSS 类,然后通过我们的主题为PagerStyle
设置CssClass
属性。 首先打开 Styles.css
并添加以下 CSS 类定义:
.PagerRowStyle
{
background-color: #ddd;
text-align: right;
}
接下来,打开GridView.skin
文件夹中的DataWebControls
文件夹中的App_Themes
文件。 在 母版页和网站导航 教程中所述,皮肤文件可用于指定 Web 控件的默认属性值。 因此,增加现有设置,以包括将PagerStyle
的CssClass
属性设置为PagerRowStyle
。 此外,让我们配置分页接口,以使用 NumericFirstLast
分页接口最多显示五个数值页按钮。
<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>
分页用户体验
图 8 显示了在选中 GridView 的“启用分页”复选框并通过 PagerStyle
文件进行 PagerSettings
和 GridView.skin
配置后,通过浏览器访问的网页。 请注意,仅显示十条记录,分页接口指示我们正在查看数据的第一页。
图 8:启用分页后,一次仅显示一部分记录(单击以查看全尺寸图像)
当用户在分页界面中单击其中一个页码时,页面会回发并重新加载,显示该页的记录。 图 9 显示了选择查看最后一页数据后的结果。 请注意,最后一页只有一条记录,这是因为总共有 81 条记录,因此每页有 10 条记录,共 8 页,还有一页只有一条记录。
图 9:点击页码会导致回传并显示相应的记录子集(点击以查看完整尺寸图像)
分页 Server-Side 工作流
当最终用户单击分页界面中的按钮时,随后会进行回发,然后开始以下服务器端工作流:
- GridView(或 DetailsView 或 FormView)
PageIndexChanging
事件被触发 - ObjectDataSource 重新请求 BLL 中的所有数据;GridView 的
PageIndex
和PageSize
属性值用于确定从 BLL 返回的哪些记录需要在 GridView 中显示。 - GridView 的
PageIndexChanged
事件被触发
在步骤 2 中,ObjectDataSource 重新请求其数据源中的所有数据。 此分页样式通常称为默认分页,因为它是将属性设置为AllowPaging
true
时默认使用的分页行为。 使用默认分页时,数据 Web 控件不加选择地检索每页数据的所有记录,即使实际上只有一部分记录被呈现在发送到浏览器的 HTML 中。 除非数据库数据由 BLL 或 ObjectDataSource 缓存,否则对于足够大的结果集或具有多个并发用户的 Web 应用程序,默认分页是无法工作的。
在下一教程中,我们将介绍如何实现 自定义分页。 通过自定义分页,您可以明确指示 ObjectDataSource 仅检索请求数据页所需的精确记录集。 可以想见,自定义分页大幅提高了对大型结果集进行分页的效率。
注释
虽然在处理足够大的结果集或拥有大量同时在线用户的网站时,默认分页并不适用,但要意识到,自定义分页需要更多的更改和努力来实现,并不像选择默认分页时那样简单。 因此,默认分页可能是小型、低流量网站的理想选择,或者在通过相对较小的结果集进行分页时,因为实现起来要容易、更快。
例如,如果我们知道数据库中永远不会有超过 100 个产品,则自定义分页所享受的最小性能可能会抵消实现它所需的工作量。 但是,如果我们有一天可能有数千或数万个产品, 则不 实现自定义分页将极大地阻碍应用程序的可伸缩性。
步骤 4:自定义分页体验
数据 Web 控件提供了许多可用于增强用户分页体验的属性。 例如,该 PageCount
属性指示存在的总页数,而 PageIndex
该属性指示正在访问的当前页,并且可以设置为快速将用户移动到特定页面。 为了说明如何使用这些属性来改善用户的分页体验,让我们将标签 Web 控件添加到页面,告知用户他们当前访问的页面,以及一个 DropDownList 控件,允许他们快速跳转到任何给定页面。
首先,将标签 Web 控件添加到页面,将其 ID
属性设置为 PagingInformation
,并清除其 Text
属性。 接下来,为 GridView 事件 DataBound
创建事件处理程序并添加以下代码:
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
Label s Text
属性设为一条消息,告知用户当前正在访问第 Products.PageIndex + 1
页,共有 Products.PageCount
页(因为 Products.PageIndex
索引从 0 开始,所以需要向 PageIndex
属性加 1)。 我选择在Text
事件处理程序中分配此Label s DataBound
属性,而不是在PageIndexChanged
事件处理程序中,因为DataBound
每次数据绑定到GridView时都会触发事件,而PageIndexChanged
事件处理程序仅在页面索引更改时触发。 当 GridView 首次数据绑定到第一页时,PageIndexChanging
事件不会触发,而 DataBound
事件会触发。
添加后,现在会向用户显示一条消息,指示他们正在访问的具体页面以及数据的总页数。
图 10:显示当前页码和总页数(单击以查看全尺寸图像)
除了“标签”控件之外,我们还添加一个 DropDownList 控件,该控件列出 GridView 中的页码,其中选择了当前已查看的页面。 此处的想法是,用户只需从 DropDownList 中选择新页面索引即可快速从当前页跳转到另一个页面索引。 首先,将 DropDownList 添加到设计器中,将其 ID
属性设置为 PageList
,并从其智能标记中勾选“启用 AutoPostBack”选项。
接下来,返回到 DataBound
事件处理程序并添加以下代码:
// 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;
}
此代码首先清除 DropDownList 中的 PageList
项。 这似乎是多余的,因为人们不会期望页面数发生变化,但其他用户可能同时使用系统,添加或删除表中的 Products
记录。 此类插入或删除可能会更改数据页数。
接下来,我们需要重新创建页码,并默认选择映射到当前 GridView PageIndex
的页码。 通过从 0 到 PageCount - 1
的循环来实现此目的,如果当前迭代索引等于 GridView 的属性ListItem
的值,则在每次迭代中添加一个新的Selected
,并将其PageIndex
属性设置为true。
最后,我们需要为 DropDownList 事件 SelectedIndexChanged
创建事件处理程序,每次用户从列表中选取不同的项时都会触发该事件处理程序。 若要创建此事件处理程序,只需双击设计器中的 DropDownList,然后添加以下代码:
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:选择第 6 页时,用户会自动移动到第六页 Drop-Down 列表项(单击以查看全尺寸图像)
步骤 5:添加 Bi-Directional 排序支持
添加双向排序支持就像添加分页支持一样简单,只需检查 GridView 智能标记中的“启用排序”选项(将 GridView s AllowSorting
属性 设置为 true
)。 这会将 GridView 字段的每个标头呈现为具有链接功能的按钮,当点击时,会导致页面刷新,并返回按点击列升序排列的数据。 再次单击同一标头链接按钮,将数据重新排序为降序。
注释
如果使用自定义数据访问层而不是类型化数据集,则可能没有 GridView 智能标记中的“启用排序”选项。 只有绑定到原生支持排序的数据源的 GridView 才提供此复选框。 类型化数据集提供开箱即用的排序支持,因为 ADO.NET DataTable 提供了一种方法 Sort
,在调用时,使用指定的条件对 DataTable 的 DataRows 进行排序。
如果 DAL 不返回本身支持排序的对象,则需要配置 ObjectDataSource,以便将用于排序的信息传递给业务逻辑层,该层可以对数据进行排序,也可以交由 DAL 对数据排序。 我们将在未来的教程中了解如何在业务逻辑和数据访问层中对数据进行排序。
排序 LinkButtons 呈现为 HTML 超链接,其当前颜色(未访问链接为蓝色,已访问链接为深红色)与标题行的背景色相冲突。 相反,让我们让所有标题行链接都以白色显示,而不管是否已访问它们。 这可以通过将以下内容添加到 Styles.css
类来实现:
.HeaderStyle a, .HeaderStyle a:visited
{
color: White;
}
此语法指示在使用 HeaderStyle 类的元素中显示这些超链接时使用白色文本。
添加此 CSS 后,通过浏览器访问页面时,屏幕应类似于图 12。 具体而言,图 12 显示了单击“价格”字段标头链接后的结果。
图 12:结果已按升序按 UnitPrice 排序(单击以查看全尺寸图像)
检查排序工作流
BoundField、CheckBoxField、TemplateField 等的所有 GridView 字段都有一个 SortExpression
属性,该属性指示在单击该字段的排序标头链接时应用于对数据进行排序的表达式。 GridView 还有一个 SortExpression
属性。 单击排序标头 LinkButton 时,GridView 会将该字段 SortExpression
的值分配给其 SortExpression
属性。 接下来,将从 ObjectDataSource 重新检索数据,并根据 GridView 的属性 SortExpression
进行排序。 以下列表详细说明了用户对 GridView 中的数据进行排序时发生的步骤序列:
- GridView的Sorting 事件触发
- GridView 的
SortExpression
属性 被设置为单击其排序表头时的字段的SortExpression
- ObjectDataSource 重新检索 BLL 中的所有数据,然后使用 GridView s 对数据进行排序
SortExpression
- GridView 的属性
PageIndex
重置为 0,这意味着在对用户进行排序时返回到数据的第一页(假设已实现分页支持) - GridView
Sorted
事件触发时
与默认分页一样,默认排序选项会重新检索 BLL 中的所有 记录。 在没有分页或使用默认分页时进行排序,会导致性能损耗,除非缓存数据库数据才能避免这种情况。 但是,正如我们在将来的教程中看到的那样,在使用自定义分页时,可以有效地对数据进行排序。
当通过 GridView 智能标记中的下拉列表将 ObjectDataSource 绑定到 GridView 时,每个 GridView 字段会自动为其 SortExpression
属性分配给类中的数据 ProductsRow
字段的名称。 例如,ProductName
BoundFieldSortExpression
设置为 ProductName
,如下面的声明性标记中所示:
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
可以配置字段,以便它无法通过清除其 SortExpression
属性(将其分配给空字符串)进行排序。 为了说明这一点,假设我们不想让客户按价格对产品进行排序。 可以通过声明性标记或通过“字段”对话框(可通过单击 GridView 智能标记中的“编辑列”链接访问)删除UnitPrice
BoundField 的SortExpression
属性。
图 13:结果已按 UnitPrice 升序排序
删除 SortExpression
BoundField 的 UnitPrice
属性后,标题将作为文本而不是链接呈现,从而阻止用户按价格对数据进行排序。
图 14:通过删除 SortExpression 属性,用户无法再按价格对产品进行排序(单击以查看全尺寸图像)
以编程方式对 GridView 进行排序
还可以使用 GridView 方法Sort
以编程方式对 GridView 的内容进行排序。 只需传入 SortExpression
值以按照 SortDirection
(Ascending
或 Descending
) 进行排序,GridView 的数据将重新排序。
想象一下,我们关闭排序 UnitPrice
的原因是,我们担心我们的客户只会购买价格最低的产品。 但是,我们希望鼓励他们购买最昂贵的产品,因此我们希望他们能够按价格对产品进行排序,但只能从最昂贵的价格到最低价格。
为此,请将 Button Web 控件添加到页面,将其 ID
属性设置为 SortPriceDescending
,并将属性 Text
设置为“按价格排序”。 接下来,通过双击设计器中的 Button 控件,为 Button s Click
事件创建事件处理程序。 将以下代码添加到此事件处理程序:
protected void SortPriceDescending_Click(object sender, EventArgs e)
{
// Sort by UnitPrice in descending order
Products.Sort("UnitPrice", SortDirection.Descending);
}
单击此按钮会将用户返回到按价格排序的产品的第一页,从最昂贵到最贵的产品(见图 15)。
图 15:单击按钮对产品从最贵到最不贵的产品排序(单击以查看全尺寸图像)
概要
在本教程中,我们了解了如何实现默认分页和排序功能,这两项都与选中复选框一样简单! 当用户对数据进行排序或翻页时,类似的工作流将展开:
- 随后的回发
- 数据 Web 控件的预级别事件触发(
PageIndexChanging
或Sorting
) - ObjectDataSource 重新检索所有数据
- 数据 Web 控件的后期事件触发(
PageIndexChanged
或Sorted
)
虽然实现基本的分页和排序很容易,但需要付出更多努力来利用更高效的自定义分页,或者进一步增强分页和排序接口。 将来的教程将探讨这些主题。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是《Sams 教你自己学会 ASP.NET 2.0 在24小时内》。 他可以通过 mitchell@4GuysFromRolla.com 联系到。