排序 DataList 或 Repeater 控件中的数据 (C#)

作者 :斯科特·米切尔

下载 PDF

本教程介绍如何在 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 属性,用于存储 SortExpression 数据排序依据的 GridView 字段。 此外, SortDirection 属性指示数据是按升序还是降序排序(如果用户连续单击特定 GridView 字段标头链接两次,则会切换排序顺序)。

当 GridView 绑定到其数据源控件时,它会将其和SortExpression属性移交SortDirection给数据源控件。 数据源控件检索数据,然后根据提供 SortExpression 的属性 SortDirection 对其进行排序。 对数据进行排序后,数据源控件会将其返回到 GridView。

若要使用 DataList 或 Repeater 控件复制此功能,必须:

  • 创建排序接口
  • 记住要排序的数据字段,以及是按升序还是降序排序
  • 指示 ObjectDataSource 按特定数据字段对数据进行排序

我们将在步骤 3 和 4 中处理这三个任务。 接下来,我们将探讨如何在 DataList 或 Repeater 中包含分页和排序支持。

步骤 2:在 Repeater 中显示产品

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

创建 ObjectDataSource 并将其配置为使用 GetProductsAsPagedDataSource() 方法

图 1:创建 ObjectDataSource 并将其配置为使用 GetProductsAsPagedDataSource() 方法(单击以查看全尺寸图像

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

图 2:将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为(无)(单击可查看全尺寸图像

与 DataList 不同,Visual Studio 不会在将 Repeater 控件绑定到数据源后自动创建 ItemTemplate 该控件。 此外,我们必须以声明方式添加此 ItemTemplate 内容,因为 Repeater 控件的智能标记缺少 DataList 中发现的“编辑模板”选项。 让我们使用上一教程中的相同 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事件处理程序传递了一个类型ObjectDataSourceSelectingEventArgs的对象,该对象具有一个名为Arguments类型的DataSourceSelectArguments属性。 该 DataSourceSelectArguments 类旨在将来自数据使用者的数据相关请求传递到数据源控件,并包括一个 SortExpression 属性

若要将排序信息从 ASP.NET 页传递到 ObjectDataSource,请为 Selecting 事件创建事件处理程序并使用以下代码:

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.Arguments.SortExpression = sortExpression;
}

应为 sortExpression 值分配数据字段的名称,以便按 (如 ProductName) 对数据进行排序。 没有与方向相关的排序属性,因此,如果要按降序对数据进行排序,请将字符串 DESC 追加到 sortExpression 值(如 ProductName DESC)。

继续尝试对 sortExpression 进行一些不同的硬编码值,并在浏览器中测试结果。 如图 4 所示,使用 ProductName DESC 作为 sortExpression 时,产品按名称按反向字母顺序排序。

产品按名称按反向字母顺序排序

图 4:产品按其名称按反向字母顺序排序(单击以查看全尺寸图像

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

启用 GridView 中的排序支持会将每个可排序字段的标头文本转换为 LinkButton,单击后,会相应地对数据进行排序。 这种排序接口对于 GridView 有意义,其中其数据整齐地布局在列中。 但是,对于 DataList 和 Repeater 控件,需要不同的排序接口。 数据列表(与数据网格相反)的常见排序接口是一个下拉列表,它提供数据可排序依据的字段。 让我们为本教程实现这样的接口。

在 Repeater 上方 SortableProducts 添加 DropDownList Web 控件,并将其 ID 属性设置为 SortBy。 在属性窗口中,单击属性中的Items省略号以显示 ListItem 集合编辑器。 添加 ListItem s 以按ProductNameCategoryNameSupplierName字段对数据进行排序。 此外,还添加一个按其名称按其名称进行排序的 A ListItem 键,按其名称按相反的字母顺序排序。

属性ListItemText可以设置为任何值(如 Name),但Value属性必须设置为数据字段的名称(如 ProductName)。 若要按降序对结果进行排序,请将字符串 DESC 追加到数据字段名称,如 ProductName DESC。

为每个可排序数据字段添加 ListItem

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

最后,在 DropDownList 右侧添加一个 Button Web 控件。 将其 ID 设置为 RefreshRepeater “刷新”和“刷新”属性 Text

创建 ListItem s 并添加“刷新”按钮后,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 事件处理程序,以便它使用所选 SortBy``ListItem 的 s Value 属性而不是硬编码的排序表达式。

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    // Have the ObjectDataSource sort the results by the selected
    // sort expression
    e.Arguments.SortExpression = SortBy.SelectedValue;
}

此时,首次访问页面时,产品最初将按ProductName数据字段进行排序,因为它SortByListItem默认处于选中状态(见图 6)。 选择其他排序选项(如类别并单击“刷新”)将导致按类别名称回发并重新对数据进行排序,如图 7 所示。

产品最初按名称排序

图 6:产品最初按其名称排序(单击以查看全尺寸图像

产品现在按类别排序

图 7:产品现在按类别排序(单击以查看全尺寸图像

注意

单击“刷新”按钮会导致数据自动重新排序,因为 Repeater 的视图状态已禁用,从而使 Repeater 在每个回发时重新绑定到其数据源。 如果已启用 Repeater 视图状态,则更改排序下拉列表不会影响排序顺序。 若要解决此问题,请为刷新按钮 Click 事件创建事件处理程序,并将 Repeater 重新绑定到其数据源(通过调用 Repeater s DataBind() 方法)。

记住排序表达式和方向

在可能发生非排序相关回发的页面上创建可排序的 DataList 或 Repeater 时,必须跨回发记住排序表达式和方向。 例如,假设我们在本教程中更新了 Repeater,以包含每个产品的“删除”按钮。 当用户单击“删除”按钮时,我们将运行一些代码以删除所选产品,然后将数据重新绑定到 Repeater。 如果未在回发中保留排序详细信息,屏幕上显示的数据将还原为原始排序顺序。

对于本教程,DropDownList 将排序表达式和方向隐式保存到其视图状态。 如果我们使用不同的排序接口,例如 LinkButtons,它提供了各种排序选项,我们需要注意记住回发之间的排序顺序。 这可以通过将排序参数存储在页面视图状态、在查询字符串中包含排序参数或通过一些其他状态持久性技术来实现。

本教程中的未来示例探讨如何在页面视图状态中保留排序详细信息。

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

前面的教程中 ,我们研究了如何使用 DataList 实现默认分页。 让我们扩展前面的示例,以包含对分页数据进行排序的功能。 首先打开SortingWithDefaultPaging.aspx文件夹中的页面Paging.aspxPagingSortingDataListRepeater。 在 Paging.aspx 页面中,单击“源”按钮以查看页面的声明性标记。 复制所选文本(请参阅图 8),并将其粘贴到标记之间的SortingWithDefaultPaging.aspx声明性标记<asp:Content>中。

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

图 8:将标记中的<asp:Content>声明性标记从 < Paging.aspx a0/> 复制到 (SortingWithDefaultPaging.aspx

复制声明性标记后,将页面代码隐藏类中的 Paging.aspx 方法和属性复制到代码隐藏类中 SortingWithDefaultPaging.aspx。 接下来,花点时间在浏览器中查看 SortingWithDefaultPaging.aspx 页面。 它应表现出与 Paging.aspx..

增强 ProductsBLL 以包含默认分页和排序方法

在上一教程中,我们在返回对象的类中创建GetProductsAsPagedDataSource(pageIndex, pageSize)一个ProductsBLLPagedDataSource方法。 此 PagedDataSource 对象填充了 所有 产品(通过 BLL s GetProducts() 方法),但当绑定到 DataList 时,只显示与指定 pageIndexpageSize 输入参数对应的记录。

在本教程的前面部分,我们添加了排序支持,方法是指定 ObjectDataSource 事件处理程序中的 Selecting 排序表达式。 当 ObjectDataSource 返回一个可排序的对象(如 ProductsDataTable 方法返回 GetProducts() 的对象)时,这很有效。 但是, PagedDataSource 该方法返回 GetProductsAsPagedDataSource 的对象不支持对其内部数据源进行排序。 相反,我们需要先对方法GetProducts()返回的结果进行排序,然后再将其放入方法中PagedDataSource

为此,请在 ProductsBLLGetProductsSortedAsPagedDataSource(sortExpression, pageIndex, pageSize)中创建一个新方法。 若要对方法返回的排序ProductsDataTable,请指定GetProducts()其默认值SortDataTableView的属性:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Select, false)]
public PagedDataSource GetProductsSortedAsPagedDataSource
    (string sortExpression, int pageIndex, int pageSize)
{
    // Get ALL of the products
    Northwind.ProductsDataTable products = GetProducts();
    // Sort the products
    products.DefaultView.Sort = sortExpression;
    // Limit the results through a PagedDataSource
    PagedDataSource pagedData = new PagedDataSource();
    pagedData.DataSource = products.DefaultView;
    pagedData.AllowPaging = true;
    pagedData.CurrentPageIndex = pageIndex;
    pagedData.PageSize = pageSize;
    return pagedData;
}

该方法 GetProductsSortedAsPagedDataSource 与在上一教程中创建的方法略 GetProductsAsPagedDataSource 有不同。 具体而言,GetProductsSortedAsPagedDataSource接受其他输入参数sortExpression,并将此值Sort分配给 s ProductDataTable的属性DefaultView。 稍后将PagedDataSource分配ProductDataTableDefaultView对象的 DataSource 几行代码。

调用 GetProductsSortedAsPagedDataSource 方法并指定 SortExpression 输入参数的值

完成该方法后 GetProductsSortedAsPagedDataSource ,下一步是提供此参数的值。 ObjectDataSource 当前SortingWithDefaultPaging.aspx配置为调用GetProductsAsPagedDataSource该方法,并通过集合中指定的QueryStringParameters两个输入参数传入这两SelectParameters个输入参数。 这两QueryStringParameters个指示方法 pageIndex GetProductsAsPagedDataSourcepageSize 参数的源来自查询字符串字段pageIndexpageSize

更新 ObjectDataSource s SelectMethod 属性,以便调用新 GetProductsSortedAsPagedDataSource 方法。 然后,添加新项QueryStringParameter,以便 输入参数。 将 QueryStringParameter s 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 参数传入。

默认情况下,结果按 ProductName 排序

图 9:默认情况下,结果排序方式 ProductName单击以查看全尺寸图像

如果手动添加 sortExpression 查询字符串字段(如 SortingWithDefaultPaging.aspx?sortExpression=CategoryName 结果)将按指定的 sortExpression排序。 但是,移动到其他数据页时,查询字符串中不包含此参数 sortExpression 。 事实上,单击“下一页”或“最后一页”按钮将我们返回到 Paging.aspx! 此外,目前没有排序接口。 用户更改分页数据的排序顺序的唯一方法是直接操作查询字符串。

创建排序接口

首先需要更新 RedirectUser 方法以将用户发送到 SortingWithDefaultPaging.aspx (而不是 Paging.aspx)并在 sortExpression querystring 中包含该值。 我们还应添加只读的页级命名 SortExpression 属性。 此属性与PageIndexPageSize上一教程中创建的属性类似,返回查询字符串字段的值sortExpression(如果存在),否则返回默认值(ProductName)。

目前,该方法 RedirectUser 只接受要显示的页索引的单个输入参数。 但是,有时我们希望使用查询字符串中指定的排序表达式将用户重定向到特定数据页。 在片刻中,我们将创建此页面的排序界面,其中包括一系列按钮 Web 控件,用于按指定列对数据进行排序。 单击其中一个按钮时,我们希望重定向传入相应排序表达式值的用户。 若要提供此功能,请创建方法的两个 RedirectUser 版本。 第一个应只接受要显示的页面索引,第二个索引接受页面索引和排序表达式。

private string SortExpression
{
    get
    {
        if (!string.IsNullOrEmpty(Request.QueryString["sortExpression"]))
            return Request.QueryString["sortExpression"];
        else
            return "ProductName";
    }
}
private void RedirectUser(int sendUserToPageIndex)
{
    // Use the SortExpression property to get the sort expression
    // from the querystring
    RedirectUser(sendUserToPageIndex, SortExpression);
}
private void RedirectUser(int sendUserToPageIndex, string sendUserSortingBy)
{
   // 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));
}

在本教程的第一个示例中,我们使用 DropDownList 创建了排序接口。 对于此示例,让我们使用位于 DataList 上方的三个按钮 Web 控件,一个用于排序 ProductName,一个用于 CategoryName,一个用于 SupplierName。 添加三个按钮 Web 控件,并相应地设置其 ID 属性 Text

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

接下来,为每个处理程序创建一个 Click 事件处理程序。 事件处理程序应调用 RedirectUser 该方法,使用适当的排序表达式将用户返回到第一页。

protected void SortByProductName_Click(object sender, EventArgs e)
{
    // Sort by ProductName
    RedirectUser(0, "ProductName");
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    // Sort by CategoryName
    RedirectUser(0, "CategoryName");
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    // Sort by SupplierName
    RedirectUser(0, "SupplierName");
}

首次访问页面时,数据按字母顺序排序(请参阅图 9)。 单击“下一步”按钮,转到数据的第二页,然后单击“按类别排序”按钮。 这会返回数据的第一页,按类别名称排序(请参阅图 10)。 同样,单击“按供应商排序”按钮按供应商从数据的第一页开始对数据进行排序。 排序选项在分页浏览时会记住。 图 11 显示按类别排序后的页面,然后前进到数据的第十三页。

产品按类别排序

图 10:产品按类别排序(单击以查看全尺寸图像

分页浏览数据时会记住排序表达式

图 11:分页浏览数据时会记住排序表达式(单击以查看全尺寸图像

步骤 6:重复器中的自定义分页记录

DataList 示例使用低效的默认分页技术检查了步骤 5 页中的数据。 分页时,必须使用自定义分页。 回到 高效分页浏览大量数据对自定义分页数据 进行排序教程时,我们回顾了默认分页和自定义分页和 BLL 中创建的方法之间的差异,以利用自定义分页和对自定义分页数据进行排序。 具体而言,在前面的两个教程中,我们向类添加了以下三种方法 ProductsBLL

  • GetProductsPaged(startRowIndex, maximumRows)返回从 startRowIndex 开始且不超过 maximumRows 的特定记录子集。
  • GetProductsPagedAndSorted(sortExpression, startRowIndex, maximumRows) 返回由指定的 sortExpression 输入参数排序的特定记录子集。
  • TotalNumberOfProducts() 提供数据库表中的记录 Products 总数。

这些方法可用于使用 DataList 或 Repeater 控件有效地对数据进行分页和排序。 为了说明这一点,让我们首先创建一个具有自定义分页支持的 Repeater 控件;然后,我们将添加排序功能。

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

将 ObjectDataSource 配置为使用 ProductsBLL 类 s GetProductsPaged 方法

图 12:将 ObjectDataSource 配置为使用 ProductsBLL 类 s GetProductsPaged 方法(单击以查看全尺寸图像

将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为“无”,然后单击“下一步”按钮。 “配置数据源”向导现在提示输入方法的 startRowIndexGetProductsPaged 输入参数的源。 实际上,这些输入参数将被忽略。 相反,startRowIndexmaximumRows 值将通过 ObjectDataSource 事件处理程序中的属性传入Arguments,就像我们在本教程的第一个演示中指定 Selecting 的方式一样。 因此,将参数源下拉列表保留在向导集中的 None 。

将参数源设置为“无”

图 13:将参数源设置为“无”(单击可查看全尺寸图像

注意

不要将 ObjectDataSource s EnablePaging 属性设置为 true. 这将导致 ObjectDataSource 自动将自己的 startRowIndexmaximumRows 参数包含在 SelectMethod 现有参数列表中。 将自定义分页数据绑定到 GridView、DetailsView 或 FormView 控件时,该EnablePaging属性非常有用,因为这些控件需要 ObjectDataSource 中仅在属性EnablePaging可用时true的某些行为。 由于我们必须手动添加对 DataList 和 Repeater 的分页支持,因此请将此属性设置为 false (默认值),因为我们将直接在 ASP.NET 页中烘焙所需的功能。

最后,定义 Repeater s 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 创建事件处理程序,并分别将这些参数值设置为 0 和 5 的硬编码值:

protected void ProductsDataSource_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    e.InputParameters["startRowIndex"] = 0;
    e.InputParameters["maximumRows"] = 5;
}

通过浏览器查看后,页面会显示前五个产品。

显示前五条记录

图 14:显示前五条记录(单击以查看全尺寸图像

注意

图 14 中列出的产品恰好按产品名称排序,因为 GetProductsPaged 执行高效自定义分页查询的存储过程按其 ProductName排序结果。

为了允许用户单步执行页面,我们需要跟踪起始行索引和最大行,并在回发中记住这些值。 在默认分页示例中,我们使用 querystring 字段来保留这些值;对于此演示,让我们在页面视图状态中保留此信息。 创建以下两个属性:

private int StartRowIndex
{
    get
    {
        object o = ViewState["StartRowIndex"];
        if (o == null)
            return 0;
        else
            return (int)o;
    }
    set
    {
        ViewState["StartRowIndex"] = value;
    }
}
private int MaximumRows
{
    get
    {
        object o = ViewState["MaximumRows"];
        if (o == null)
            return 5;
        else
            return (int)o;
    }
    set
    {
        ViewState["MaximumRows"] = value;
    }
}

接下来,更新“选择事件处理程序”中的代码,使其使用 StartRowIndexMaximumRows 属性,而不是 0 和 5 的硬编码值:

e.InputParameters["startRowIndex"] = StartRowIndex;
e.InputParameters["maximumRows"] = MaximumRows;

此时,我们的页面仍仅显示前五条记录。 但是,有了这些属性,我们就可以创建分页接口了。

添加分页接口

让我们使用默认分页示例中所用的同一个 First、Previous、Next、Last 分页接口,包括显示正在查看的数据页的标签 Web 控件以及存在的总页数。 在 Repeater 下面添加四个按钮 Web 控件和标签。

<p>
    <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>
    <asp:Label runat="server" ID="CurrentPageNumber"></asp:Label>
</p>

接下来,为四个 Button 创建 Click 事件处理程序。 单击其中一个按钮时,我们需要更新 StartRowIndex 数据并将其重新绑定到 Repeater。 第一个、上一个和下一个按钮的代码足够简单,但对于最后一个按钮,我们如何确定最后一页数据的起始行索引? 若要计算此索引并能够确定是否应启用“下一步”和“最后一个”按钮,我们需要知道正在分页的记录总数。 可以通过调用 ProductsBLL 类的方法 TotalNumberOfProducts() 来确定这一点。 让我们创建一个名为返回方法结果的TotalRowCount只读页面级属性TotalNumberOfProducts()

private int TotalRowCount
{
    get
    {
        // Return the value from the TotalNumberOfProducts() method
        ProductsBLL productsAPI = new ProductsBLL();
        return productsAPI.TotalNumberOfProducts();
    }
}

有了此属性,我们现在可以确定最后一页的起始行索引。 具体而言,它是负 1 除以TotalRowCountMaximumRows乘以的整数结果MaximumRows。 现在可以为四个分页接口按钮编写 Click 事件处理程序:

protected void FirstPage_Click(object sender, EventArgs e)
{
    // Return to StartRowIndex of 0 and rebind data
    StartRowIndex = 0;
    Products.DataBind();
}
protected void PrevPage_Click(object sender, EventArgs e)
{
    // Subtract MaximumRows from StartRowIndex and rebind data
    StartRowIndex -= MaximumRows;
    Products.DataBind();
}
protected void NextPage_Click(object sender, EventArgs e)
{
    // Add MaximumRows to StartRowIndex and rebind data
    StartRowIndex += MaximumRows;
    Products.DataBind();
}
protected void LastPage_Click(object sender, EventArgs e)
{
    // Set StartRowIndex = to last page's starting row index and rebind data
    StartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
    Products.DataBind();
}

最后,在查看数据的第一页时,我们需要禁用分页界面中的“第一个”和“上一个”按钮,并在查看最后一页时禁用“下一页”和“最后一个”按钮。 为此,请将以下代码添加到 ObjectDataSource 事件处理程序 Selecting

// Disable the paging interface buttons, if needed
FirstPage.Enabled = StartRowIndex != 0;
PrevPage.Enabled = StartRowIndex != 0;
int LastPageStartRowIndex = ((TotalRowCount - 1) / MaximumRows) * MaximumRows;
NextPage.Enabled = StartRowIndex < LastPageStartRowIndex;
LastPage.Enabled = StartRowIndex < LastPageStartRowIndex;

添加这些 Click 事件处理程序和代码以基于当前起始行索引启用或禁用分页接口元素后,请在浏览器中测试页面。 如图 15 所示,首次访问页面时,将禁用“第一个”和“上一个”按钮。 单击“下一步”会显示第二页的数据,同时单击“最后”显示最后一页(请参阅图 16 和 17)。 查看数据的最后一页时,将禁用“下一步”和“最后一步”按钮。

查看产品的第一页时禁用上一个和最后一个按钮

图 15:查看产品第一页时禁用上一个和最后一个按钮(单击以查看全尺寸图像

显示第二页的产品

图 16:显示第二页产品(单击以查看全尺寸图像

单击“上次显示数据的最后一页”

图 17:单击“上次显示数据的最后一页”(单击以查看全尺寸图像

步骤 7:包括对自定义分页重复程序进行排序支持

现已实现自定义分页,我们已准备好包括排序支持。 类 ProductsBLL s GetProductsPagedAndSorted 方法的 startRowIndex,但允许其他 sortExpression 输入参数。 若要使用 GetProductsPagedAndSorted 该方法 SortingWithCustomPaging.aspx,需要执行以下步骤:

  1. 将 ObjectDataSource s SelectMethod 属性从 GetProductsPaged .GetProductsPagedAndSorted
  2. Parameter 对象。
  3. 创建一个专用的页面级 SortExpression 属性,该属性通过页面的视图状态在回发中保留其值。
  4. 更新 ObjectDataSource 的 Selecting 事件处理程序,以将 ObjectDataSource s sortExpression 参数分配给页面级 SortExpression 属性的值。
  5. 创建排序接口。

首先更新 ObjectDataSource s SelectMethod 属性并添加 sortExpressionParameter。 确保 sortExpressionParameter s 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 string SortExpression
{
    get
    {
        object o = ViewState["SortExpression"];
        if (o == null)
            return "ProductName";
        else
            return o.ToString();
    }
    set
    {
        ViewState["SortExpression"] = value;
    }
}

在 ObjectDataSource 调用 GetProductsPagedAndSorted 方法之前,我们需要将 sortExpressionParameter 设置为属性的值 SortExpression 。 在 Selecting 事件处理程序中,添加以下代码行:

e.InputParameters["sortExpression"] = SortExpression;

剩下的就是实现排序接口。 正如我们在上一个示例中所做的那样,让我们使用三个 Button 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 事件处理程序。 在事件处理程序中 StartRowIndex ,重置为 0,将值设置为 SortExpression 相应的值,并将数据重新绑定到 Repeater:

protected void SortByProductName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "ProductName";
    Products.DataBind();
}
protected void SortByCategoryName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CategoryName";
    Products.DataBind();
}
protected void SortBySupplierName_Click(object sender, EventArgs e)
{
    StartRowIndex = 0;
    SortExpression = "CompanyName";
    Products.DataBind();
}

这就是它! 虽然有许多步骤可以实现自定义分页和排序,但这些步骤与默认分页所需的步骤非常相似。 图 18 显示按类别排序时查看数据的最后一页时的产品。

显示数据的最后一页(按类别排序)

图 18:显示按类别排序的数据的最后一页(单击以查看全尺寸图像

注意

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

总结

DataList 和 Repeater 都不提供内置排序支持,但可以使用一些代码和自定义排序接口来添加此类功能。 实现排序而不是分页时,可以通过传递到 ObjectDataSource 方法DataSourceSelectArguments的对象指定Select排序表达式。 可以在 ObjectDataSource 事件处理程序中分配此 DataSourceSelectArguments 对象 SortExpression 属性 Selecting

若要向已提供分页支持的 DataList 或 Repeater 添加排序功能,最简单的方法是自定义业务逻辑层以包含接受排序表达式的方法。 然后,可以通过 ObjectDataSource s SelectParameters中的参数传入此信息。

本教程将完成对 DataList 和 Repeater 控件的分页和排序的检查。 下一篇和最后一篇教程将介绍如何将 Button Web 控件添加到 DataList 和 Repeater 模板,以便基于每个项目提供一些自定义的用户启动功能。

快乐编程!

关于作者

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

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 David Suru。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com