在 DataList 或Repeater 控件中对数据进行排序

本文档是 Visual Basic 教程 (切换到 Visual C# 教程)

在本教程中,我们将探讨如何在 DataList 和 Repeater 中支持数据排序,以及如何构建一个可进行数据分页和排序的 DataList 或 Repeater。

« 前一篇教程  |  下一篇教程 »

简介

前一篇教程 中,我们介绍了如何将分页支持添加至 DataList 。我们在 ProductsBLL 类中创建了一个新方法 (GetProductsAsPagedDataSource) ,该方法返回 PagedDataSource 对象。当与 DataList 或 Repeater 绑定时,DataList 或 Repeater 会显示所要求的数据页面。该技术与 GridView 、 DetailsView 以及 FormView 控件固有的技术相似,它提供内置的默认分页功能。

除了提供分页支持,GridView 还包括现成的排序支持。DataList 和 Repeater 都不提供内置排序功能,不过,可以通过编码添加排序功能。在本教程中,我们将探讨如何在 DataList 和 Repeater 中支持数据排序,以及如何构建一个可进行数据分页和排序的 DataList 或 Repeater 。

排序功能回顾

正如我们在报表数据的分页及排序 教程中所看到的 ,GridView 控件提供有现成的排序支持。每个 GridView 字段可以有一个关联的 SortExpression ,它标明根据哪个数据字段对数据进行排序。当为 GridView 的 AllowSorting 属性赋值为“true”时,每个有 SortExpression 属性值的 GridView 字段的页眉都呈现为 LinkButton 。当用户单击一个特定 GridView 字段的页眉时,将发生回传,并根据所单击字段的 SortExpression 对数据进行排序。

GridView 控件也具有 SortExpression 属性,其中存储了数据排序所根据的 GridView 字段的 SortExpression 。另外,SortDirection 属性标明数据是按照升序还是降序进行排序(用户连续两次单击一个特定 GridView 字段的页眉链接,设定排序顺序) 。

当将 GridView 绑定到其数据源控件时,就将其 SortExpression 和 SortDirection 属性传递到数据源控件中。数据字段控件将检索数据,然后根据所提供的 SortExpression 以及 SortDirection 属性对数据进行排序。在完成数据的排序后,数据字段控件将其返回至 GridView 。

要复制该功能到 DataList 或者Repeater 控件中,我们必须:

  • 创建一个排序界面
  • 记住排序所依据的数据字段以及排序是按升序还是降序
  • 告知 ObjectDataSource 根据特定的数据字段进行数据排序。

我们将在步骤 3 和步骤 4 中解决这三个任务。随后,我们将讨论如何在 DataList 或 Repeater 中包含分页和排序支持。

步骤2:在 Repeater 中显示产品

在我们进行与排序相关的功能之前,先在一个 Repeater 控件中列出产品。首先,打开 PagingSortingDataListRepeater 文件夹中的 Sorting.aspx 页面。将 Repeater 控件添加至网页中,将其 ID 属性设置为 SortableProducts 。在 Repeater 的智能标记中创建一个新的名为 ProductsDataSource 的 ObjectDataSource ,并将其配置为从 ProductsBLL 类的 GetProducts () 方法检索数据。从 INSERT 、UPDATE 和 DELETE 选项卡的下拉列表中选择“(None) ” 选项。

图1:创建一个 ObjectDataSource 并将其配置为使用GetProductsAsPagedDataSource() 方法

图2:将 UPDATE 、INSERT 和 DELETE 选项卡中 的下拉列表设置为 (None)

和在 DataList 中的情况不同,在将 Repeater 控件绑定到数据源后,Visual Studio 不会自动为其创建 ItemTemplate 。此外,我们必须采用声明方式添加该ItemTemplate ,因为 Repeater 控件的智能标记没有 DataList 所具备的“Edit Templates”选项。让我们使用与上一篇教程相同的ItemTemplate ,用其显示产品名称、供应商和类别。

添加 ItemTemplate 后,Repeater 和 ObjectDataSource 的声明式标记应该和如下标记相类似:

<asp:Repeater ID="SortableProducts" DataSourceID="ProductsDataSource"
    EnableViewState="False" runat="server">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProducts">
</asp:ObjectDataSource>

图3 显示了从浏览器浏览时的页面。

图3:显示每个产品的名称、供应商和类别

步骤3 :告知ObjectDataSource 对数据进行排序

要对 Repeater 中显示的数据进行排序,我们需要将数据排序所依据的排序表达式通知给ObjectDataSource 。在 ObjectDataSource 对其数据进行检索之前,必须首先激发其 Selecting 事件,该事件为我们提供了指定排序表达式的机会。Selecting 事件的 event handler 将传递 ObjectDataSourceSelectingEventArgs 类型的对象,该对象拥有一个DataSourceSelectArguments 类型的Arguments 属性。DataSourceSelectArguments 类是设计用于将来自数据用户的与数据相关的请求传递给数据源控件,它包含有SortExpression 属性

要将来自 ASP.NET 页面的排序信息传递给ObjectDataSource ,需要为 Selecting 事件创建 event handler ,使用如下代码:

Protected Sub ProductsDataSource_Selecting(ByVal sender As Object, _
    ByVal e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    e.Arguments.SortExpression = sortExpression
End Sub

应将 sortExpression 赋值为要按其排序的数据字段名称,(如 “ProductName”)。没有与方向有关的排序属性,所以如果想按降序排序数据,将字符串 "DESC" 追加到 sortExpression 值中(如"ProductName DESC").

接下来,为sortExpression 尝试一些不同的赋值,然后在浏览器中对结果进行检验。如图 4 所示,当使用“ProductName DESC”作为 sortExpression 时,产品会按名称以逆向字母顺序排序。

图4:产品按名称以逆向字母顺序排序

步骤4 :创建排序界面 并记住 排序表达式和方向

在 GridView 中开启排序支持会将每个可排序字段的页眉文本转化为LinkButton ,当单击时,相应地排序数据。这样的排序界面对 GridView 来说是有意义的,界面中的 GridView 数据是整齐地以列排列。然而对于 DataLis 和 Repeater 控件来说,需要不同的排序界面。通用的数据列表(相对数据栅格)排序界面是一个下拉列表,提供了排序数据所依据的字段。让我们为本教实现这一界面。

在SortableProducts Repeater 之上添加一个 Web DropDownlist 控件,将其 ID 属性设置为 SortBy 。从属性窗口中,单击 Items 属性中的省略号,调出 ListItem Collection Editor。添加 ListItem,根据 ProductName 、 CategoryName 和 SupplierName 字段对数据进行排序。同时添加一个 ListItem ,根据名称以逆字母顺序对产品进行排序。

可以将 ListItem 文本属性赋值任何值(如 “Name”),但是Value 属性必须赋值为数据字段的名称(如 “ProductName”)。要想以降序对结果进行排序,将字符串 “DESC ” 追加至数据字段名称中,如 “ ProductName DESC ” 。

图5 :为每一个可排序数据字段添加一个 ListItem

最后,在 DropDownList 的右侧添加一个 Web 按钮控件。将其ID 设置为 RefreshRepeater ,文本属性设置为 “Refresh” 。

创建了 ListItem 并添加 Refresh 按钮后,DropDownList 和Button 的声明式语句应该与以下语句相似:

<asp:DropDownList ID="SortBy" runat="server">
    <asp:ListItem Value="ProductName">Name</asp:ListItem>
    <asp:ListItem Value="ProductName DESC">Name (Reverse Order)
        </asp:ListItem>
    <asp:ListItem Value="CategoryName">Category</asp:ListItem>
    <asp:ListItem Value="SupplierName">Supplier</asp:ListItem>
</asp:DropDownList>
<asp:Button runat="server" ID="RefreshRepeater" Text="Refresh" />

完成 DropDownList 的排序后,接下来我们需要更新ObjectDataSource 的Selecting event handler ,以便其使用选中的SortBy ListItem Value 属性,而不是固定的排序表达式。

Protected Sub ProductsDataSource_Selecting _
    (ByVal sender As Object, ByVal e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    ' Have the ObjectDataSource sort the results by the
    ' selected sort expression
    e.Arguments.SortExpression = SortBy.SelectedValue
End Sub

此时,当首次访问该页面时,最初按 ProductName 数据字段对产品排序,因为默认情况下选中该字段的 SortBy ListItem (见图 6 )。选择一个不同的排序选项(如“Category”),然后单击“Refresh”,将引起回传,并根据类别名称对数据进行重新排序,如图 7 所示。

图6 :产品最初会按名称进行排序

图7 :现在产品按类别进行排序

注意:单击 Refresh 按钮将引起数据自动重新排序,这是因为禁用了 Repeater 的视图状态,因此导致 Repeater 在每个回传中都与其数据源重新进行绑定。如果启用 Repeater 的视图状态,则改变排序下拉列表就不会对排序顺序造成任何影响。要弥补这种状况,可以为Refresh 按钮的 Click 事件创建一个 event handler,并将 Repeater 与其数据源进行绑定(通过调用 Repeater 的DataBind () 方法)。

记忆排序表达式与方向

当在一个可能发生非排序相关回传的页面中创建可排序DataList 或 Repeater 时,必须在整个回传过程中记住排序表达式和方向。例如,假设我们在本教程中更新了 Repeater 使每种产品都包含一个 Delete 按钮。当用户单击 Delete 按钮时,我们将会运行一些代码删除选中的产品,然后将数据与 Repeater 再次绑定。如果排序细节在回传中不持续,那么屏幕中显示的数据就会恢复为原来的排序顺序。

在本教程中,DropDownList 一定要在其视图状态下保存排序表达式和方向。如果我们正在使用一个不同的排序界面 —— 比如,一个带有可以提供多种排序选项的 LinkButtons 排序界面—— 我们需要注意要记住回传过程中的排序顺序。这一步骤可以通过在页面视图状态中对排序参数进行排序,或者通过将查询字符串中的排序参数包括在内,或者通过一些其他状态持续技术实现。

本教程随后的示例中,我们将介绍如何在页面的视图状态中保持排序细节。

步骤5 :向使用默认分页的 DataList 中添加排序支持

前一篇教程 中,我们介绍了如何使用 DataList 进行默认分页。我们对前面的示例进行扩展,使其包含有对分页数据进行排序的能力。首先在 PagingSortingDataListRepeater 文件夹中打开 SortingWithDefaultPaging.aspx  和 Paging.aspx 页面。从 Paging.aspx 页面,单击资源按钮,浏览页面的声明式标记。复制已选中的文本(见图 8 ),将其粘贴至位于 <asp:Content> 标签之间的 SortingWithDefaultPaging.aspx  声明式标记中。

图8 :将 <asp:Content> 标签中的声明式标记从 Paging.aspx 复制到 SortingWithDefaultPaging.aspx 中

在复制完声明式标记后,将 Paging.aspx 页面中代码文件类内的方法和属性复制到SortingWithDefaultPaging.aspx 的代码文件类内。然后,花些时间在浏览器中浏览 SortingWithDefaultPaging.aspx 页面。它的功能和外观应当和 Paging.aspx 的功能和外观相同。

对 ProductsBLL 进行增强,以包含默认分页和排序方法

在前一篇教程中,我们在一个返回 PagedDataSource 对象的ProductsBLL 类中创建了一个 GetProductsAsPagedDataSource(pageIndex, pageSize) 方法。该 PagedDataSource 对象获得有所有的产品(通过 BLL 的 GetProducts() 方法),但是当与 DataList 绑定时,只有那些与指定的 pageIndexpageSize 输入参数相对应的记录才会显示出来。

在本教程较早的部分,我们曾经通过指定排序表达式的方法,从 ObjectDataSource 的 Selecting event handler 内添加了排序支持。当可以进行排序的对象(比如由 GetProducts() 方法所返回的 ProductsDataTable )可以返回至 ObjectDataSource 时,该排序运行良好。然而,由 GetProductsAsPagedDataSource 方法返回的 PagedDataSource 对象,不支持其内部数据源进行排序。因此,我们不得不在将其放入 PagedDataSource 之前,对从 GetProducts() 方法返回的结果进行排序。

要实现这一点,需要在 ProductsBLL 类中创建一个新方法 GetProductsSortedAsPagedDataSource(sortExpression, pageIndex, pageSize) 。要对由 GetProducts() 方法返回的 ProductsDataTable 进行排序,需要指定其默认DataTableView 的Sort 属性 :

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Select, False)> _
Public Function GetProductsSortedAsPagedDataSource _
    (sortExpression As String, pageIndex As Integer, pageSize As Integer) _
    As PagedDataSource
    ' Get ALL of the products
    Dim products As Northwind.ProductsDataTable = GetProducts()
    'Sort the products
    products.DefaultView.Sort = sortExpression
    ' Limit the results through a PagedDataSource
    Dim pagedData As New PagedDataSource()
    pagedData.DataSource = products.DefaultView
    pagedData.AllowPaging = True
    pagedData.CurrentPageIndex = pageIndex
    pagedData.PageSize = pageSize
    Return pagedData
End Function

GetProductsSortedAsPagedDataSource 方法与在前一篇教程中所创建的 GetProductsAsPagedDataSource 方法稍微有所不同。具体来说,GetProductsSortedAsPagedDataSource 可以接受额外的输入参数 sortExpression ,并且可以将该值赋值到 ProductDataTable 的 DefaultView 的 Sort 属性中。PagedDataSource 对象的 DataSource ,可以通过稍后的几行代码,分配到 ProductDataTable 的 DefaultView 中。

调用GetProductsSortedAsPagedDataSource 方法,并为SortExpression 输入参数 赋 值

完成 GetProductsSortedAsPagedDataSource 方法后,接下来是为该参数赋值。SortingWithDefaultPaging.aspx 中的 ObjectDataSource 目前配置为调用 GetProductsAsPagedDataSource 方法,并通过自身两个 QueryStringParameters (在SelectParameters 集合中指定)传递两个输入参数。这两个 QueryStringParameters 表明,GetProductsAsPagedDataSource 方法的 pageIndexpageSize 参数源都来自查询字符串字段 pageIndex 和 pageSize 。

更新 ObjectDataSource 的 SelectMethod 属性,这样它就可以调用新的GetProductsSortedAsPagedDataSource 方法。然后,添加新的QueryStringParameter ,这样,sortExpression 输入参数就可以从查询字符串字段 sortExpression 中访问。将 QueryStringParameter 的 DefaultValue 设置为“ ProductName ”。

在完成这些改变后,ObjectDataSource 的声明式语句应该如下:

<asp:ObjectDataSource ID="ProductsDefaultPagingDataSource"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsSortedAsPagedDataSource"
    OnSelected="ProductsDefaultPagingDataSource_Selected" runat="server">
    <SelectParameters>
        <asp:QueryStringParameter DefaultValue="ProductName"
            Name="sortExpression" QueryStringField="sortExpression"
            Type="String" />
        <asp:QueryStringParameter DefaultValue="0" Name="pageIndex"
            QueryStringField="pageIndex" Type="Int32" />
        <asp:QueryStringParameter DefaultValue="4" Name="pageSize"
            QueryStringField="pageSize" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

此时,SortingWithDefaultPaging.aspx 页面将按产品名称以字母顺序对结果进行排序(见图 9 )。这是因为,默认情况下,“ProductName”值会作为 GetProductsSortedAsPagedDataSource 方法的 sortExpression 参数传递。

图9 :默认情况下,按 ProductName 对进行排序

如果您手动添加一个 sortExpression 查询字符串字段(如 SortingWithDefaultPaging.aspx?sortExpression=CategoryName ),结果就会按照指定的 sortExpression 进行排序。然而,当移至一个不同的数据页面中时,该 sortExpression 参数就不会包括在查询字符串内。实际上,单击“Next”或“Last”按钮会返回至 Paging.aspx 。此外,目前还不存在排序界面。用户可以对分页数据进行改变的唯一方法就是直接对查询字符串进行操作。

创建排序界面

首先我们需要更新 RedirectUser 方法,转至SortingWithDefaultPaging.aspx (而不是 Paging.aspx ),并且在查询字符串中包括 sortExpression 值。我们还应该添加一个只读、页面级的名为SortExpression 的属性。该属性与在前一篇教程中所创建的 PageIndex 和 PageSize 属性相似,如果 sortExpression 查询字符串字段值存在,可以返回该值,否则返回默认值( ProductName )。 .

目前, RedirectUser 方法仅可以接受单一输入参数 —— 用于页面显示的索引。然而,有时候我们想使用排序表达式,而不是查询字符串中指定的内容,将用户引导至特别的页面。稍后我们会为这一页面创建排序界面,界面中包括一系列的 web 按钮控件,用于通过指定的项目栏对数据进行排序。当单击这些按钮中的一个时,对在适当的排序表达式值中传递的用户进行重新定向。要提供该功能,我们需要创建两个版本的 RedirectUser 方法。第一个版本应能接受用于显示的页面索引,另外一个版本可以接受页面索引和排序表达式。

Private ReadOnly Property SortExpression() As String
    Get
        If Not String.IsNullOrEmpty(Request.QueryString("sortExpression")) Then
            Return Request.QueryString("sortExpression")
        Else
            Return "ProductName"
        End If
    End Get
End Property
Private Sub RedirectUser(ByVal sendUserToPageIndex As Integer)
    ' Use the SortExpression property to get the sort expression
    ' from the querystring
    RedirectUser(sendUserToPageIndex, SortExpression)
End Sub
Private Sub RedirectUser(ByVal sendUserToPageIndex As Integer,
    ByVal sendUserSortingBy As String)
    ' Send the user to the requested page with the
    ' requested sort expression
    Response.Redirect(String.Format("SortingWithDefaultPaging.aspx?" & _
        "pageIndex={0}&pageSize={1}&sortExpression={2}", _
        sendUserToPageIndex, PageSize, sendUserSortingBy))
End Sub

本教程中的第一个示例中,我们使用 DropDownList 创建了一个排序界面。在本示例中,我们使用三个位于 DataList 之上的 Web 按钮控件 —— 一个用于通过 ProductName 进行排序,一个用于通过 CategoryName 进行排序,另一个用于通过 SupplierName 进行排序。添加这三个 Web 按钮控件,相应地设置其 ID 和文本属性:

<p style="text-align:center;">
    <asp:Button runat="server" id="SortByProductName"
        Text="Sort by Product Name" />
    <asp:Button runat="server" id="SortByCategoryName"
        Text="Sort by Category" />
    <asp:Button runat="server" id="SortBySupplierName"
        Text="Sort by Supplier" />
</p>

接下来,为每个Web 按钮控件创建一个Click event handler 。该event handler应该调用RedirectUser 方法,使用正确的排序表达式将用户返回至首页。

Protected Sub SortByProductName_Click(sender As Object, e As EventArgs) _
    Handles SortByProductName.Click
    'Sort by ProductName
    RedirectUser(0, "ProductName")
End Sub
Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _
    Handles SortByCategoryName.Click
    'Sort by CategoryName
    RedirectUser(0, "CategoryName")
End Sub
Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _
    Handles SortBySupplierName.Click
    'Sort by SupplierName
    RedirectUser(0, "SupplierName")
End Sub

当首次访问页面时,将按产品名称以字母顺序进行排序(参考前面的图 9 )。单击 “Next” 按钮,前进至数据的第二页面,然后单击“Sort by Category” 按钮。该操作将我们返回至数据首页,按类别名称进行排序(见图 10 )。同样的,单击“Sort by Supplier”按钮,就可以从首页开始,按供应商进行数据排序。在进行数据分页的同时,将记住排序选择。图 11 显示了按类别进行排序,然后前进至第三数据页面时的页面情况。

图10 :产品按类别进行排序

图11 :在对数据分页的同时,记住排序表达式

步骤6:在 Repeater 中通过记录定制分页

步骤 5 中所介绍的 DataList 示例,使用效率较低的默认分页技术,对其数据进行分页。但分页大量的数据时,必须使用定制分页。在对大量数据进行高效分页 教程和对自定义分页数据进行排序 教程中,我们已经分析了默认分页和定制分页之间的区别,并且在 BLL 中创建了多种方法,用于使用定制分页以及对定制分页数据进行排序。具体来说,在前面的这两个教程中,我们将以下的三种方法添加到了 ProductsBLL 类中:

  • GetProductsPaged(startRowIndex,maximumRows)  – 返回一个起始于startRowIndex 但不超过 maximumRows的 特殊记录子集。
  • GetProductsPagedAndSorted(sortExpression,startRowIndex, maximumRows) – 返回按指定的sortExpression 输入参数进行排序的特殊记录子集。
  • TotalNumberOfProducts() – 提供 Products 数据库表中的记录总数。

在使用 DataList 或 Repeater 控件对数据进行高效分页和排序时,可使用这些方法。为了演示这些方法,首先我们使用定制分页支持,创建一个 Repeater 控件,然后我们再添加排序功能。

打开 PagingSortingDataListRepeater 文件夹中的 SortingWithCustomPaging.aspx 页面,在页面中添加 Repeater,并将其 ID 属性设置为 “Products” 。从 Repeater 的智能标记创建一个新的名为 ProductsDataSource 的 ObjectDataSource 。将其配置为从 ProductsBLL 类的 GetProductsPaged 方法选择数据。

图12 :将 ObjectDataSource 配置为使用 ProductsBLL 类的 GetProductsPaged 方法

将 UPDATE 、INSERT 以及 DELETE 选项卡中的下拉列表设置为 (None) ,然后单击 Next 按钮。配置数据源向导将提示设置 GetProductsPaged 方法的 startRowIndexmaximumRows 输入参数源。实际上,忽略这些输入参数。通过 ObjectDataSource 的 Selecting event handler 传递 startRowIndexmaximumRows 值,这和我们在本教程的第一个示例中,指定 sortExpressio n 的过程相同。因此,将向导中的参数源下拉列表设置为 “ None ”。

图13:保持参数源设置为“None”

注意:不要 将ObjectDataSource 的EnablePaging 属性设置为true 。这将使得ObjectDataSource 自动将其 startRowIndexmaximumRows 参数包含在 SelectMethod 现有的参数列表中。当将定制分页数据与 GridView 、DetailsView 、或者 FormView 控件绑定时,EnablePaging 属性十分有用,因为这些控件需要某些来自ObjectDataSource 的行为,当 EnablePaging 属性为 true 时,这些行为才可用。我们必须为 DataList 和 Repeater 手动添加分页支持,所以保持该属性为 false(默认),因为我们将在 ASP.NET 页面内直接完成所需的功能。

最后,对 Repeater 的 ItemTemplate 进行定义,以便显示产品的名称、类别和供应商。在完成这些改变后,Repeater 和 ObjectDataSource 的声明式语句类似如下:

<asp:Repeater ID="Products" runat="server" DataSourceID="ProductsDataSource"
    EnableViewState="False">
    <ItemTemplate>
        <h4><asp:Label ID="ProductNameLabel" runat="server"
            Text='<%# Eval("ProductName") %>'></asp:Label></h4>
        Category:
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'></asp:Label><br />
        Supplier:
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'></asp:Label><br />
        <br />
        <br />
    </ItemTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProductsPaged" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

花点时间在浏览器访问该页面,会注意到未返回任何记录。这是因为我们必须指定startRowIndexmaximumRows 参数值;因此,传递给两者的值都为 0 。要指定这些值,需要为 ObjectDataSource 的 Selecting 事件创建 event handler,并通过编码为这些参数赋值 0 和 5 。

Protected Sub ProductsDataSource_Selecting(sender As Object, _
    e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSource.Selecting
    e.InputParameters("startRowIndex") = 0
    e.InputParameters("maximumRows") = 5
End Sub

完成这一改变后,当从浏览器浏览页面时,将显示前五种产品。

图14 :显示前 5 条记录

注意: 图 14 所列出的产品是按产品名称排序的,因为执行高效定制分页查询的 GetProductsPaged 存储程序按 ProductName 来 排序结果。

要使用户逐步进入页面,我们需要追踪第一行索引和最多行数,并将在回传过程中记住这些值。在默认分页示例中,我们使用了查询字符串字段来保持这些值,在本示例中,我们会在页面视图状态中保持该信息。创建如下两个属性:

Private Property StartRowIndex() As Integer
        Get
            Dim o As Object = ViewState("StartRowIndex")
            If o Is Nothing Then
                Return 0
            Else : Return CType(o, Integer)
            End If
        End Get
        Set(ByVal value As Integer)
            ViewState("StartRowIndex") = value
        End Set
    End Property
    Private Property MaximumRows() As Integer
        Get
            Dim o As Object = ViewState("MaximumRows")
            If o Is Nothing Then
                Return 5
            Else
                Return CType(o, Integer)
            End If
        End Get
        Set(ByVal value As Integer)
            ViewState("MaximumRows") = value
        End Set
    End Property

接下来,在 Selecting event handler 中更新代码,这样代码就会使用StartRowIndex 和 MaximumRows 属性而不是固定赋值0 和5 :

e.InputParameters("startRowIndex") = 0
e.InputParameters("maximumRows") = 5

此时,我们的页面依然显示前五个记录。不过,随着这些属性的完成,我们已经准备好了创建分页界面。

添加分页界面

我们将使用与在默认分页示例中相同的First 、Previous 、Next 和 Last 分页界面,包括可以显示正在浏览何种页面以及存在多少页面的Web 标签控件。在Repeater 下面添加四个Web 按钮控件和一个标签。

<p style="text-align:center;">
    <asp:Button runat="server" ID="FirstPage" Text="<< First" />
    <asp:Button runat="server" ID="PrevPage" Text="< Prev" />
    <asp:Button runat="server" ID="NextPage" Text="Next >" />
    <asp:Button runat="server" ID="LastPage" Text="Last >>" />
</p>
<p style="text-align:center;">
    <asp:Label runat="server" ID="CurrentPageNumber"></asp:Label>
</p>

下一步,为四个按钮创建 Click event handler 。当单击这些按钮中的某一个时,我们需要更新 StartRowIndex ,并将数据绑定到 Repeater 。First 、 Previous 和 Next 按钮的编码十分简单,但是对于 Last 按钮,我们该怎么为数据的最后页面确定起始行索引呢?要计算该索引—— 同时也是确定是否需要激活 Next 和 Last 按钮,需要知道正在分页的全部记录数。我们可以通过调用 ProductsBLL 类的 TotalNumberOfProducts() 方法来确定该记录总数。让我们创建一个返回 TotalNumberOfProducts() 方法结果、名为 TotalRowCount 的只读页面级属性。

Private ReadOnly Property TotalRowCount() As Integer
    Get
        'Return the value from the TotalNumberOfProducts() method
        Dim productsAPI As New ProductsBLL()
        Return productsAPI.TotalNumberOfProducts()
    End Get
End Property

有了这个属性,现在可以确定最后一页的起始行索引。具体来说,TotalRowCount 减 1 后再除以 MaximumRows,然后乘以 MaximumRows 的整数结果就是该索引。现在,可以为四个分页界面按钮编写 Click event handler 了。

Protected Sub FirstPage_Click(sender As Object, e As EventArgs) _
    Handles FirstPage.Click
    'Return to StartRowIndex of 0 and rebind data
    StartRowIndex = 0
    Products.DataBind()
End Sub
Protected Sub PrevPage_Click(sender As Object, e As EventArgs) _
    Handles PrevPage.Click
    'Subtract MaximumRows from StartRowIndex and rebind data
    StartRowIndex -= MaximumRows
    Products.DataBind()
End Sub
Protected Sub NextPage_Click(sender As Object, e As EventArgs) _
    Handles NextPage.Click
    'Add MaximumRows to StartRowIndex and rebind data
    StartRowIndex += MaximumRows
    Products.DataBind()
End Sub
Protected Sub LastPage_Click(sender As Object, e As EventArgs) _
    Handles LastPage.Click
    'Set StartRowIndex = to last page's starting row index and rebind data
    StartRowIndex = ((TotalRowCount - 1) \ MaximumRows) * MaximumRows
    Products.DataBind()
End Sub

最后,当浏览数据首页时,我们需要在页面界面中禁用 First 和 Previous 按钮,而浏览最后一页时,禁用 Next  和 Last 按钮。要做到这一点 ,可将如下的代码添加至ObjectDataSource 的 Selecting event handler 。

' Disable the paging interface buttons, if needed
FirstPage.Enabled = StartRowIndex <> 0
PrevPage.Enabled = StartRowIndex <> 0
Dim LastPageStartRowIndex As Integer = _
    ((TotalRowCount - 1) \ MaximumRows) * MaximumRows
NextPage.Enabled = (StartRowIndex < LastPageStartRowIndex)
LastPage.Enabled = (StartRowIndex < LastPageStartRowIndex)

添加这些 Click event handler ,并根据当前起始行索引来启用或禁用分页界面元素,然后在浏览器中测试页面。如图 15 所示,当首次访问页面时, First 和 Previous 按钮已被禁用。单击 Next 显示数据的第二页,单击 Last 显示最后一页(见图 16 和 17 )。当浏览数据的最后一页时,Next 和 Last 按钮都被禁用。

图15 :当浏览产品首页时,Previous 和 Last 按钮被禁用

图16 :显示产品的第二个页面

图17 :单击 Last 按钮显示数据首页

步骤7 :使用定制分页 Repeater 将排序支持包括在内

现在已经实现了定制分页,我们可以添加排序功能了。 ProductsBLL 类的 GetProductsPagedAndSorted 方法与 GetProductsPaged 有相同的 startRowIndexmaximumRows 输入参数,但是它允许额外的 sortExpression 输入参数。要从 SortingWithCustomPaging.aspx 使用 GetProductsPagedAndSorted 方法,我们需要执行如下步骤:

  1. 将 ObjectDataSource 的 SelectMethod 属性从 GetProductsPaged 改为 GetProductsPagedAndSorted 。
  2. sortExpression 参数对象添加至 ObjectDataSource 的 SelectParameters 集合中。
  3. 创建一个专用的、页面级的 SortExpression 属性,该属性通过页面的浏览状态 在回传过程保持 其属性值。
  4. 更新 ObjectDataSource 的 Selecting event handler ,以便将页面类的 SortExpression 属性值赋值给 ObjectDataSource 的sortExpression 参数。
  5. 创建排序页面

首先更新 ObjectDataSource 的 SelectMethod 属性,添加一个sortExpression 参数。确保 sortExpression 参数的 Type 属性设置为 "String" 。完成这两个任务后,ObjectDataSource 的声明式标记应该如下:

<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL"
    SelectMethod="GetProductsPagedAndSorted"
    OnSelecting="ProductsDataSource_Selecting">
    <SelectParameters>
        <asp:Parameter Name="sortExpression" Type="String" />
        <asp:Parameter Name="startRowIndex" Type="Int32" />
        <asp:Parameter Name="maximumRows" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

接下来,我们需要页面级 sortExpression 属性,该属性具有序列化值,用于查看状态。如果还没有设置排序表达式值,则使用 "ProductName" 作为默认值。

Private Property SortExpression() As String
    Get
        Dim o As Object = ViewState("SortExpression")
        If o Is Nothing Then
            Return "ProductName"
        Else
            Return o.ToString()
        End If
    End Get
    Set(ByVal value As String)
        ViewState("SortExpression") = value
    End Set
End Property

在ObjectDataSource 调用 GetProductsPagedAndSorted 方法之前,需要将 sortExpression 参数赋值为 SortExpression 属性值。在 Selecting event handler 中添加如下代码:

e.InputParameters("sortExpression") = SortExpression

后面要做的就是实现排序界面。正如我们在上一个示例中所做的那样,用三个 Web 按钮控件让用户按产品名称、类别或供应商对结果进行排序,实现排序界面。

<asp:Button runat="server" id="SortByProductName"
    Text="Sort by Product Name" />
<asp:Button runat="server" id="SortByCategoryName"
    Text="Sort by Category" />
<asp:Button runat="server" id="SortBySupplierName"
    Text="Sort by Supplier" />

为这三个按钮控件创建 Click event handler 。在 event handler 中,将 StartRowIndex 重置为0,将 SortExpression 赋值为适当的值,并将数据再次绑定至Repeater :

Protected Sub SortByProductName_Click(sender As Object, e As EventArgs) _
    Handles SortByProductName.Click
    StartRowIndex = 0
    SortExpression = "ProductName"
    Products.DataBind()
End Sub
Protected Sub SortByCategoryName_Click(sender As Object, e As EventArgs) _
    Handles SortByCategoryName.Click
    StartRowIndex = 0
    SortExpression = "CategoryName"
    Products.DataBind()
End Sub
Protected Sub SortBySupplierName_Click(sender As Object, e As EventArgs) _
    Handles SortBySupplierName.Click
    StartRowIndex = 0
    SortExpression = "CompanyName"
    Products.DataBind()
End Sub

这样就可以了!尽管实现定制分页和排序使用了很多步骤,但是这些步骤与默认分页的步骤十分相似。图 18 显示了在按类别排序时浏览数据末尾页面时的产品。

图18 :按类别排序时显示的数据末尾页面

注意:在前面的示例中,当按供应商排序时,“SupplierName ” 用作了排序表达式。然而,对于定制分页实现,我们需要使用“ CompanyName ”。这是因为,负责实现定制分页的存储程序 GetProductsPagedAndSorted 将排序表达式传递到 ROW_NUMBER() 关键字中。 ROW_NUMBER() 关键词要求提供实际的列名称,而不是别名。因此,我们必须使用 CompanyName (Suppliers 表中的列的名称),而不是在排序表达式的 SELECT 查询中使用的别名 ( SupplierName )。

小结

DataList 和 Repeater 都不提供内置的排序支持,但是,可以通过编码和定制排序界面的方式添加这一功能。在实现排序(不是分页)功能时,排序表达式可以通过 DataSourceSelectArguments (该对象传递至 ObjectDataSource 的Select 方法)指定。 DataSourceSelectArguments 对象的 SortExpression 属性可以在 ObjectDataSource 的 Selecting event handler 中赋值。

在将排序功能添加到已经可以提供分页支持的DataList 或Repeater 中时,最简单的方法是定制业务逻辑层,使其包含一种可以接受排序表达式的方法。然后通过 ObjectDataSource 的 SelectParameters 中的一个参数传递该信息。

本教程中对如何使用 DataList 和 Repeater 控件进行分页和排序的探讨到此结束。下一篇教程,也是最后一篇教程,将探讨如何在 DataList 和Repeater 模板中添加 Web 按钮控件,基于单个项目提供定制的、用户发起的功能。

快乐编程!

 

 

下一篇教程