数据报表的分页和排序 (VB)

作者 :斯科特·米切尔

下载 PDF

在联机应用程序中显示数据时,分页和排序是两个非常常见的功能。 在本教程中,我们将首次初步了解如何向报表添加排序和分页,并在将来的教程中进一步深入。

介绍

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

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

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

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

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

创建一个分页和排序文件夹,并添加教程 ASP.NET 页面

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

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

将 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 控件从工具箱拖到设计器上,将其 ID 属性设置为 Products。 接下来,创建一个新的 ObjectDataSource,它使用 ProductsBLL 类的方法 GetProducts() 返回所有产品信息。

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

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

由于此报表是只读报表,因此无需将 ObjectDataSource s Insert()Update()Delete()方法映射到相应的ProductsBLL方法;因此,请从 UPDATE、INSERT 和 DELETE 选项卡的下拉列表中选择(无)。

在 UPDATE、INSERT 和 DELETE 选项卡的 Drop-Down 列表中选择“无”选项

图 5:在 UPDATE、INSERT 和 DELETE 选项卡的 Drop-Down 列表中选择“无”选项

接下来,让我们自定义 GridView 字段,以便仅显示产品名称、供应商、类别、价格和已停用状态。 此外,可以随意进行任何字段级格式更改,例如调整 HeaderText 属性或将价格的格式设置为货币。 这些更改后,GridView 的声明性标记应如下所示:

<asp:GridView ID="Products" AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False" runat="server">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            SortExpression="CategoryName" ReadOnly="True" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            SortExpression="SupplierName" ReadOnly="True" />
        <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指示分页接口的样式信息;可以指定设置,例如BackColorForeColorCssClassHorizontalAlign等等。

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

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

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

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

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

接下来,打开GridView.skin文件夹中的DataWebControls文件夹中的App_Themes文件。 如我们在母版页和网站导航教程中讨论过的,Skin 文件可以用来指定 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 文件进行了 PagerSettingsGridView.skin 配置后的效果。 请注意,仅显示十条记录,分页接口指示我们正在查看数据的第一页。

启用分页后,一次仅显示一部分记录

图 8:启用分页后,一次仅显示一部分记录(单击以查看全尺寸图像

当用户在分页界面中单击其中一个页码时,服务器会进行回发,页面会重新加载,显示请求页面的记录。 图 9 显示了选择查看最后一页数据后的结果。 请注意,最后一页只有一条记录;这是因为总共有81条记录,导致每页10条记录的8页加上一页上只有一条记录。

单击页码会导致页面回传并显示适当的记录子集

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

分页过程 Server-Side 工作流

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

  1. GridView s (或 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 Sub Products_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Products.DataBound
    PagingInformation.Text = String.Format("You are viewing page {0} of {1}...", _
        Products.PageIndex + 1, Products.PageCount)
End Sub

此事件处理程序将 PagingInformation 标签的 Text 属性设置为一条消息,告知用户他们当前正在访问的页面是 Products.PageIndex + 1 页中的第几页 Products.PageCount (因为索引是从 0 开始的,所以我们需要在 Products.PageIndex 属性上加 1)。 我选择在Text事件处理程序中分配此 Label 的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 i As Integer = 0 To Products.PageCount - 1
    ' Add the new ListItem
    Dim pageListItem As New ListItem(String.Concat("Page ", i + 1), i.ToString())
    PageList.Items.Add(pageListItem)
    ' select the current item, if needed
    If i = Products.PageIndex Then
        pageListItem.Selected = True
    End If
Next

此代码首先清除 DropDownList 中的 PageList 项。 这似乎是多余的,因为人们不会期望页面数发生变化,但其他用户可能同时使用系统,添加或删除表中的 Products 记录。 此类插入或删除可能会更改数据页数。

接下来,我们需要重新创建页码,并默认选择与当前 GridView PageIndex 对应的页码。 通过从 0 到 PageCount - 1 的循环实现此目的,在每次迭代中添加一个新的 ListItem,并在当前迭代索引等于 GridView 的 Selected 属性时,将其 PageIndex 属性设置为 true。

最后,我们需要为 DropDownList 事件 SelectedIndexChanged 创建事件处理程序,每次用户从列表中选取不同的项时都会触发该事件处理程序。 若要创建此事件处理程序,只需双击设计器中的 DropDownList,然后添加以下代码:

Protected Sub PageList_SelectedIndexChanged(sender As Object, e As System.EventArgs) _
    Handles PageList.SelectedIndexChanged
        ' Jump to the specified page
        Products.PageIndex = Convert.ToInt32(PageList.SelectedValue)
End Sub

如图 11 所示,仅更改 GridView 属性 PageIndex 会导致数据反弹到 GridView。 在 GridView 事件处理程序 DataBound 中,选择了相应的 DropDownList ListItem

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

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

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

添加双向排序支持就像添加分页支持一样简单,只需检查 GridView 智能标记中的“启用排序”选项(将 GridView s AllowSorting 属性 设置为 true)。 这会将 GridView 字段的每个标头呈现为链接按钮。单击这些按钮时,会导致页面回发,并返回按所单击列升序排序的数据。 再次单击同一标头的“LinkButton”将数据重新排序为降序。

注释

如果使用自定义数据访问层而不是类型化数据集,则可能没有 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 中对数据进行排序时发生的步骤顺序:

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

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

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

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

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

“字段”窗口的屏幕截图,其中选择了“价格”字段,并突出显示了“SortExpression”属性。

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

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

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

图 14:通过删除 SortExpression 属性,用户无法再按价格对产品进行排序(单击以查看全尺寸图像

以编程方式对 GridView 进行排序

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

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

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

Protected Sub SortPriceDescending_Click(sender As Object, e As System.EventArgs) _
    Handles SortPriceDescending.Click
        'Sort by UnitPrice in descending order
        Products.Sort("UnitPrice", SortDirection.Descending)
End Sub

单击此按钮会将用户返回到按价格排序的产品的第一页,从最昂贵到最贵的产品(见图 15)。

单击“按钮”将产品从最贵到最不贵的产品排序

图 15:单击按钮对产品从最贵到最不贵的产品排序(单击以查看全尺寸图像

概要

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

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

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

快乐编程!

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是《Sams教你自己学习ASP.NET 2.0在24小时内》。 他可以通过 mitchell@4GuysFromRolla.com 联系。