分页和排序报表数据 (C#)

作者 :Scott Mitchell

下载 PDF

分页和排序是联机应用程序中显示数据的两个非常常见的功能。 在本教程中,我们将首先了解向报表添加排序和分页,然后在将来的教程中基于这些内容进行构建。

简介

分页和排序是联机应用程序中显示数据的两个非常常见的功能。 例如,在联机书店搜索 ASP.NET 书籍时,可能有数百种此类书籍,但列出搜索结果的报告每页仅列出十个匹配项。 此外,结果可以按标题、价格、页计数、作者姓名等进行排序。 虽然过去的 23 个教程已研究如何生成各种报表,包括允许添加、编辑和删除数据的接口,但我们没有研究如何对数据进行排序,我们所看到的唯一分页示例是使用 DetailsView 和 FormView 控件。

在本教程中,我们将了解如何向报表添加排序和分页,只需选中几个复选框即可完成此操作。 遗憾的是,这种简单实现有其缺点,排序接口留出一点需要,分页例程并非设计用于通过大型结果集高效分页。 未来的教程将探讨如何克服现成分页和排序解决方案的限制。

步骤 1:添加分页和排序教程网页

在开始本教程之前,让我们先花点时间添加本教程所需的 ASP.NET 页面以及接下来的三个页面。 首先,在名为 PagingAndSorting的项目中创建一个新文件夹。 接下来,将以下五个 ASP.NET 页添加到此文件夹,使其全部配置为使用母版页 Site.master

  • Default.aspx
  • SimplePagingSorting.aspx
  • EfficientPaging.aspx
  • SortParameter.aspx
  • CustomSortingUI.aspx

创建 PagingAndSorting 文件夹并添加教程 ASP.NET 页面

图 1:创建分页AndSorting 文件夹并添加教程 ASP.NET 页面

接下来,打开 Default.aspx 页面,将“用户控件” SectionLevelTutorialListing.ascxUserControls 文件夹拖到“设计”图面上。 我们在 母版页和网站导航 教程中创建的此用户控件枚举网站地图,并在项目符号列表的当前部分中显示这些教程。

将 SectionLevelTutorialListing.ascx 用户控件添加到 Default.aspx

图 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>

更新站点地图以包含新 ASP.NET 页

图 3:更新站点地图以包含新 ASP.NET 页

步骤 2:在 GridView 中显示产品信息

在实际实现分页和排序功能之前,让我们先创建一个列出产品信息的标准不可排序、不可分页的 GridView。 这是我们之前在本教程系列中多次完成的任务,因此这些步骤应该很熟悉。 首先打开页面,SimplePagingSorting.aspx将一个 GridView 控件从工具箱拖到Designer,并将其ID属性设置为 Products。 接下来,创建一个新的 ObjectDataSource,该对象使用 ProductsBLL 类 s GetProducts() 方法返回所有产品信息。

使用 GetProducts () 方法检索有关所有产品的信息

图 4:使用 GetProducts () 方法检索有关所有产品的信息

由于此报表是只读报表,因此无需将 ObjectDataSource 的 Insert()Update()Delete() 方法映射到相应的 ProductsBLL 方法;因此,请从“更新”、“插入”和“删除”选项卡的下拉列表中选择“ (无) ”。

在“更新”、“插入”和“删除”选项卡的“Drop-Down 列表中,选择”无 (“) 选项

图 5:在“更新”、“插入”和“删除”选项卡 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 s AllowPaging 属性设置为 true) 。

选中“启用分页”复选框以添加分页支持

图 7:选中“启用分页”复选框以添加分页支持 (单击以查看全尺寸图像)

启用分页会限制每页显示的记录数,并将 分页接口 添加到 GridView。 图 7 所示的默认分页界面是一系列页码,允许用户快速从一页数据导航到另一页数据。 此分页界面看起来应该很熟悉,正如我们在以前的教程中向 DetailsView 和 FormView 控件添加分页支持时所看到的那样。

DetailsView 和 FormView 控件仅显示每页一条记录。 但是,GridView 会查阅其 PageSize 属性 以确定每页要显示的记录数, (此属性默认为 10) 。

可以使用以下属性自定义此 GridView、DetailsView 和 FormView 分页界面:

  • PagerStyle指示分页接口的样式信息;可以指定 、BackColorForeColorCssClassHorizontalAlign等设置。

  • PagerSettings 包含一系列可自定义分页接口功能的属性; PageButtonCount 指示分页接口中显示的数字页码的最大数目 (默认值为 10) ; Mode 属性 指示分页接口的操作方式,并且可以将其设置为:

    • NextPrevious 显示“下一步”和“上一页”按钮,允许用户一次向前或后退一页
    • NextPreviousFirstLast 除了“下一页”和“上一页”按钮外,还包括“第一个”和“最后一个”按钮,使用户能够快速移动到数据的第一页或最后一页
    • Numeric 显示一系列页码,允许用户立即跳转到任何页面
    • NumericFirstLast 除页码外,还包括“第一页”和“最后一页”按钮,允许用户快速移动到数据的第一页或最后一页;仅当所有数字页码都不适合时,才会显示“第一个/最后一个”按钮

此外,GridView、DetailsView 和 FormView 都提供 PageIndexPageCount 属性,分别指示正在查看的当前页和数据页总数。 属性 PageIndex 从 0 开始编制索引,这意味着查看第一页数据 PageIndex 时等于 0。 PageCount另一方面,从 1 开始计数,这意味着 PageIndex 仅限于介于 0 和 PageCount - 1之间的值。

让我们花点时间改进 GridView 分页接口的默认外观。 具体而言,让我们将分页界面与浅灰色背景右对齐。 与其直接通过 GridView 属性PagerStyle设置这些属性,不如在 名为 PagerRowStyle 中创建 Styles.css CSS 类,然后通过 Theme 分配PagerStyle属性CssClass。 首先打开 Styles.css 并添加以下 CSS 类定义:

.PagerRowStyle
{
    background-color: #ddd;
    text-align: right;
}

接下来,打开 文件夹中GridView.skinDataWebControls的 文件夹中的文件App_Themes。 正如我们在 母版页和网站导航 教程中所述,外观文件可用于指定 Web 控件的默认属性值。 因此,请扩充现有设置,以包括将 PagerStyle s 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 条记录,导致 8 页每页 10 条记录加上 1 页具有一条单条记录。

单击页码会导致回发并显示相应的记录子集

图 9:单击页码会导致回发,并显示相应的记录子集 (单击以查看全尺寸图像)

分页 Server-Side 工作流

当最终用户单击分页界面中的按钮时,将随之回发,并开始以下服务器端工作流:

  1. GridView (或 DetailsView 或 FormView) PageIndexChanging 事件触发
  2. ObjectDataSource 从 BLL 重新请求所有数据;GridView 和PageIndexPageSize属性值用于确定从 BLL 返回的哪些记录需要显示在 GridView 中
  3. GridView 事件 PageIndexChanged 触发

在步骤 2 中,ObjectDataSource 从其数据源重新请求所有数据。 这种分页样式通常称为 默认分页,因为它是将 属性设置为 AllowPagingtrue时默认使用的分页行为。 使用默认分页时,数据 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);
}

此事件处理程序将 Label s 属性分配给PagingInformation一条消息,通知用户他们当前访问Products.PageIndex + 1的页面总数 Products.PageCount (我们向Products.PageIndex属性添加 1,因为 PageIndex 从 0) 开始编制Text索引。 我选择在事件处理程序中DataBound分配此 Label s Text 属性,而不是事件处理程序,PageIndexChanged因为DataBound每次将数据绑定到 GridView 时都会触发该事件,PageIndexChanged而事件处理程序仅在页面索引更改时触发。 当 GridView 最初在访问第一页时绑定数据时, PageIndexChanging 事件不会触发 (而 DataBound 事件) 。

通过此添加,用户现在会显示一条消息,指示他们访问的页面以及数据的总页数。

显示“当前页码”和“总页数”

图 10:显示当前页码和总页数 (单击以查看全尺寸图像)

除了 Label 控件之外,我们还添加一个 DropDownList 控件,该控件列出 GridView 中的页码,其中选择了当前查看的页面。 此处的想法是,用户只需从 DropDownList 中选择新页面索引,即可快速从当前页跳转到另一页。 首先将 DropDownList 添加到Designer,将其 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 的 PageIndex 属性,则在每个迭代中添加一个新的 ListItem ,并将其 Selected 属性设置为 true。

最后,我们需要为 DropDownList 事件 SelectedIndexChanged 创建事件处理程序,每当用户从列表中选取不同的项时,该事件都会触发。 若要创建此事件处理程序,只需双击Designer中的 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

选择第 6 页 Drop-Down 列表项时,用户会自动转到第六页

图 11:选择第 6 页 Drop-Down 列表项时,用户自动转到第六页 (单击以查看全尺寸图像)

步骤 5:添加 Bi-Directional 排序支持

添加双向排序支持非常简单,只需检查 GridView 智能标记 (启用排序选项即可添加分页支持,该选项将 GridView 属性AllowSorting设置为true) 。 这会将 GridView 字段的每个标头呈现为 LinkButtons,单击该标头时会导致回发,并返回按单击的列按升序排序的数据。 再次单击同一标头 LinkButton 将按降序对数据重新排序。

注意

如果使用自定义数据访问层而不是类型化数据集,则 GridView 智能标记中可能没有“启用排序”选项。 只有绑定到本机支持排序的数据源的 GridView 才提供此复选框。 Typed DataSet 提供现成的排序支持,因为 ADO.NET DataTable 提供了一种方法 Sort ,该方法在调用时使用指定的条件对 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 中对数据进行排序时发生的步骤序列:

  1. GridView 排序 事件 触发
  2. GridView 的 SortExpression 属性 设置为 SortExpression 单击其排序标头 LinkButton 的字段的
  3. ObjectDataSource 从 BLL 重新检索所有数据,然后使用 GridView 对数据进行排序 SortExpression
  4. GridView 的 PageIndex 属性重置为 0,这意味着在排序时,用户将返回到数据的第一页 (假设分页支持已实现)
  5. GridView 事件Sorted触发

与默认分页一样,默认排序选项会重新检索 BLL 中的所有 记录。 在没有分页的情况下使用排序或将排序与默认分页结合使用时,除了缓存数据库数据) , (无法避免这种性能问题。 但是,正如我们在将来的教程中看到的,在使用自定义分页时,可以有效地对数据进行排序。

通过 GridView 智能标记中的下拉列表将 ObjectDataSource 绑定到 GridView 时,每个 GridView 字段会自动将其 SortExpression 属性分配给类中的数据 ProductsRow 字段的名称。 例如, ProductName BoundField 设置为 SortExpressionProductName,如以下声明性标记所示:

<asp:BoundField DataField="ProductName" HeaderText="Product"
    SortExpression="ProductName" />

可以通过清除字段 SortExpression 的属性 (将其分配给空字符串) 来配置字段,使其不可排序。 为了说明这一点,假设我们不想让客户按价格对产品进行排序。 UnitPrice可以从声明性标记或通过“字段”对话框删除 BoundField 属性SortExpression, (可通过单击 GridView 智能标记) 中的“编辑列”链接进行访问。

“字段”窗口的屏幕截图,其中突出显示了 Price 和 SortExpression。

图 13:结果已按 UnitPrice 升序排序

SortExpression删除 BoundField 的 UnitPrice 属性后,标头将呈现为文本而不是链接,从而阻止用户按价格对数据进行排序。

通过删除 SortExpression 属性,用户无法再按价格对产品进行排序

图 14:通过删除 SortExpression 属性,用户无法再按价格 (单击以查看全尺寸图像)

以编程方式对 GridView 进行排序

还可以使用 GridView 方法Sort编程方式对 GridView 的内容进行排序。 只需传入 SortExpression 要排序的值以及 SortDirection (AscendingDescending) ,GridView 的数据将重新排序。

试想一下,我们关闭排序 UnitPrice 的原因是因为我们担心我们的客户只会购买价格最低的产品。 但是,我们希望鼓励他们购买最昂贵的产品,因此我们希望他们能够按价格对产品进行排序,但只能从最昂贵的价格到最少的价格。

若要完成此操作,请将 Button Web 控件添加到页面,将其 ID 属性设置为 SortPriceDescending,将其 Text 属性设置为“按价格排序”。 接下来,通过双击Designer中的 Button 控件,为 Button 事件Click创建事件处理程序。 将以下代码添加到此事件处理程序:

protected void SortPriceDescending_Click(object sender, EventArgs e)
{
    // Sort by UnitPrice in descending order
    Products.Sort("UnitPrice", SortDirection.Descending);
}

单击此按钮会将用户返回到第一页,其中包含按价格排序的产品,从最昂贵到最便宜的 (请参阅图 15) 。

单击按钮将产品从最昂贵到最低

图 15:单击按钮将产品从最昂贵到最低 (单击以查看全尺寸图像)

总结

在本教程中,我们了解了如何实现默认分页和排序功能,这两项功能都与选中复选框一样简单! 当用户对数据进行排序或分页时,类似的工作流将展开:

  1. 随后会进行回发
  2. data Web 控件的预级别事件触发 (PageIndexChangingSorting)
  3. ObjectDataSource 会重新检索所有数据
  4. data Web 控件的后期级别事件触发 (PageIndexChangedSorted)

虽然实现基本分页和排序是轻而易举的,但必须付出更多努力来利用更有效的自定义分页或进一步增强分页或排序接口。 后面的教程将探讨这些主题。

编程愉快!

关于作者

Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。