在 DataList 或 Repeater 控件中分页报表数据44

简介

在在线应用程序中显示数据时,分页和排序是两个非常常见的功能。例如,在一个在线书店搜寻 ASP.NET 书籍时,可能会有数以百计种这样的图书,但在报表的每页只列出十个匹配的搜索结果。而且,搜索结果可以按标题、价格、页数、作者名称等等进行排序。正如我们在 报表数据的分页及排序 教程中所探讨的那样, GridView 、 DetailsView 和 FormView 控件都提供了内置的分页支持,选中 CheckBox 标记就可以使用。GridView 控件也包含了排序支持。

不幸的是DataList 控件和 Repeater 控件都不提供自动分页或排序支持。在本教程中,我们将详细探讨如何为 DataList 控件和 Repeater 控件添加分页支持。我们必须手动创建分页界面,显示适当的记录页面,然后通过回传记住用户访问过的页面。然而这需要的时间和代码要比 GridView 、 DetailsView 或 FormView 控件多,因为 DataList 控件和 Repeater 控件需要考虑更加灵活的分页和数据显示界面。

注意:本教程只关注分页功能。在接下来的教程中,我们会将注意力转移到排序功能上。

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

开始本教程之前,先花点时间添加一些 ASP.NET 页面,本教程和下一篇教程中会用到这些页面。首先在项目中创建一个新文件夹,命名为 PagingSortingDataListRepeater 。接下来,将下面五个 ASP.NET 页面添加到该文件夹,并将其全部进行配置使用母版页 Site.master :

  • Default.aspx
  • Paging.aspx
  • Sorting.aspx
  • SortingWithDefaultPaging.aspx
  • SortingWithCustomPaging.aspx

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

随后打开 Default.aspx 页面,把 SectionLevelTutorialListing.ascx 用户控件 从 UserControls 文件夹中拖拉到 设计界面。我们在母版页与网站导航 教程中创建的用户控件枚举站点地图,并将这些教程以项目符号列表的形式显示在当前区域。

图2 :将SectionLevelTutorialListing.ascx 用户控件添加至 Default.aspx

要让项目符号列表显示我们将要创建的分页和排序教程,我们需要将这些教程添加到站点地图中。打开 Web.sitemap 文件,在“ Editing and Deleting with the DataList ”站点地图节点标记后面添加以下代码:

<siteMapNode 
    url="~/PagingSortingDataListRepeater/Default.aspx" 
    title="Paging and Sorting with the DataList and Repeater" 
    description="Paging and Sorting the Data in the DataList and Repeater Controls"> 
    <siteMapNode 
        url="~/PagingSortingDataListRepeater/Paging.aspx" 
        title="Paging" 
        description="Learn how to page through the data shown 
                     in the DataList and Repeater controls." /> 
    <siteMapNode 
        url="~/PagingSortingDataListRepeater/Sorting.aspx" 
        title="Sorting" 
        description="Sort the data displayed in a DataList or 
                     Repeater control." /> 
    <siteMapNode 
        url="~/PagingSortingDataListRepeater/SortingWithDefaultPaging.aspx" 
        title="Sorting with Default Paging" 
        description="Create a DataList or Repeater control that is paged using 
                     default paging and can be sorted." /> 
    <siteMapNode 
        url="~/PagingSortingDataListRepeater/SortingWithCustomPaging.aspx" 
        title="Sorting with Custom Paging" 
        description="Learn how to sort the data displayed in a DataList or 
                     Repeater control that uses custom paging." /> 
</siteMapNode>

图3 :更新站点地图,以便包含新的 ASP.NET 页面

回顾分页

在前面的教程中,我们学习了如何将 GridView 控件、DetailsView 控件和 FormView 控件中的数据进行分页。这三种控件都提供了简单的分页方式,称为默认分页 ( default paging) ,只需在控件的智能标记选中“ Enable Paging ”就可以实现。使用默认分页时,每次请求一页数据(不管是第一次页面访问还是在用户进入到另一个数据页面时),而 GridView 控件、 DetailsView 控件或 FormView 控件重新请求来自 ObjectDataSource 的所有数据。然后剪切出特定的记录集,显示指定的请求页面索引和每页要显示的记录数目。我们在 报表数据的分页及排序 教程中已经详细探讨了默认分页技术。

因为默认分页为每一个页面重新请求所有的数据,所以当需要为大量数据分页时它是不实用的。例如,假设需要为五万条记录进行分页,每页显示十条记录。用户每次进入一个新页面,所有的五万条数据都必须从数据库中重新获得,尽管只显示其中的十条记录。

定制分页技术 解决了默认分页技术在性能方面的问题,这种分页方法只会精确抓取要在请求页面上显示的记录子集。执行定制分页时,我们必须编写 SQL 查询语句,该查询会返回正确的记录集。我们在 对大量数据进行高效分页 教程的后面部分了解了如何使用 SQL Server 2005 的新增 ROW_NUMBER() 关键词 来创建此类查询。

要在DataList 控件或 Repeater 控件中执行默认分页,我们可以使用 PagedDataSource 类,将其作为 ProductsDataTable (其内容被分页)的一个封装。 PagedDataSource 类有 DataSource 属性(该属性可以指派给任何枚举对象)以及 PageSize 和CurrentPageIndex 属性(这两个属性指示说明每页要显示的记录数目和当前页面的索引)。设置好这些属性后,PagedDataSource 就可以用于任何 Web 控件的数据源。枚举 PagedDataSource 时,将只根据 PageSize 和 CurrentPageIndex 属性返回内部 DataSource 的恰当的记录子集。图 4 描述了 PagedDataSource 类的功能。

图4 :PagedDataSource 使用可分页界面封装一个可枚举对象

可以直接从业务逻辑层创建和配置 PagedDataSource 对象,然后利用 ObjectDataSource 将其绑定到 DataList 控件或 Repeater 控件上,也可以直接在 ASP.NET 页面的代码文件类中创建和配置 PagedDataSource 对象。如果使用后一种方法,则必须放弃使用 ObjectDataSource ,而是通过编程将分页数据绑定到 DataList 控件或 Repeater 控件上。

PagedDataSource 对象也有支持定制分页的属性,尽管如此,我们可以不使用PagedDataSource 来进行定制分页,因为在 ProductsBLL 类中有一些 BLL 方法,ProductsBLL 类就是设计用于定制分页,而这些 BLL 方法返回要 显示的准确记录。

在本篇教程中,我们将介绍通过向 ProductsBLL 类(该类返回一个正确配置的 PagedDataSource 对象)添加一个新方法来实施 DataList 控件中的默认分页。下一篇教程中,我们将了解如何使用定制分页。

步骤2 :在业务逻辑层中添加默认分页方法

ProductsBLL 类目前拥有一个用于返回所有产品信息的方法GetProducts() ,和一个用于从起始索引返回产品特定子集的方法GetProductsPaged(startRowIndexmaximumRows) 。使用默认分页时,GridView 控件、 DetailsView 控件和 FormView 控件都使用 GetProducts() 方法检索所有产品,但随后它们会使用固有的 PagedDataSource 仅显示那些正确的记录子集。要将该功能复制到 DataList 控件和 Repeater 控件中,可以在仿效该行为的业务逻辑层中创建一个新方法。

在 ProductsBLL 类中添加名为 GetProductsAsPagedDataSource 的方法,该方法有两个整数输入参数 :

  • pageIndex – 要显示的页面索引,从 0 开始索引。
  • pageSize – 每页要显示的记录数。

GetProductsAsPagedDataSource 首先会从 GetProducts() 中检索所有记录。然后创建一个PagedDataSource 对象,用传递的 pageIndex 和 pageSize 参数值为其 CurrentPageIndex 和PageSize 属性赋值。该方法返回这个已配置的 PagedDataSource 后就结束了:

[System.ComponentModel.DataObjectMethodAttribute 
    (System.ComponentModel.DataObjectMethodType.Select, false)] 
public PagedDataSource GetProductsAsPagedDataSource(int pageIndex, int pageSize) 

    // Get ALL of the products 
    Northwind.ProductsDataTable products = GetProducts(); 
 
    // Limit the results through a PagedDataSource 
    PagedDataSource pagedData = new PagedDataSource(); 
    pagedData.DataSource = products.Rows; 
    pagedData.AllowPaging = true; 
    pagedData.CurrentPageIndex = pageIndex; 
    pagedData.PageSize = pageSize; 
 
    return pagedData; 
}

步骤3:在 DataList 控件中使用默认分页显示产品信息

将GetProductsAsPagedDataSource 方法添加到 ProductsBLL 类中后,现在我们就可以创建一个提供默认分页功能的DataList 控件和 Repeater 控件。首先打开 PagingSortingDataListRepeater 文件夹中的 Paging.aspx 页面,然后从工具箱中拖拉一个 DataList 控件到设计器中,将 DataList 控件的 ID 属性设置为 ProductsDefaultPaging 。从 DataList 控件的智能标记中创建一个新的 ObjectDataSource ,命名为 ProductsDefaultPagingDataSource ,然后对它进行配置以便其能够使用 GetProductsAsPagedDataSource 方法检索数据。

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

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

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

因为 GetProductsAsPagedDataSource 方法需要两个输入参数,向导会提示我们输入这两个参数值的来源。

必须在整个回传过程中记住页面索引和页面尺寸值。它们可以存储于视图状态、存在于查询字符串中、存储在会话变量中或使用其它的方法记忆。本教程中,我们使用查询字符串,因为它允许标记特定的数据页面。

具体来说,分别使用查询字符串字段 "pageIndex" 和 "pageSize" 作为 pageIndex 和 pageSize 参数 ( 请参见图 7 )。花点时间将这两个参数赋值为默认值,因为用户首次访问该页面时查询字符串的值并不存在。将 pageIndex 的默认值赋值为 0 (将显示第一个数据页面),pageSize 的默认值赋值为 4 。

图7 :使用查询字符串作为 pageIndex 和 pageSize 的参数源

ObjectDataSource 配置完成后,Visual Studio 会自动为 DataList 控件创建一个 ItemTemplate 。定制该 ItemTemplate ,只显示产品的名称、类别和供应商。另外还要把 DataList 控件的 RepeatColumns 属性赋值为 2 ,其 Width 属性赋值为“ 100% ”,ItemStyle 的 Width 赋值为 “ 50% ”。该宽度设置为两列提供了同等的空间。

完成这些更改后 ,DataList 和 ObjectDataSource 的标记类似如下 :

<asp:DataList ID="ProductsDefaultPaging" runat="server" Width="100%" 
    DataKeyField="ProductID" DataSourceID="ProductsDefaultPagingDataSource" 
    RepeatColumns="2" 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> 
    <ItemStyle Width="50%" /> 
</asp:DataList> 
 
<asp:ObjectDataSource ID="ProductsDefaultPagingDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" 
    SelectMethod="GetProductsAsPagedDataSource"> 
    <SelectParameters> 
        <asp:QueryStringParameter DefaultValue="0" Name="pageIndex" 
             QueryStringField="pageIndex" Type="Int32" /> 
        <asp:QueryStringParameter DefaultValue="4" Name="pageSize" 
             QueryStringField="pageSize" Type="Int32" /> 
    </SelectParameters> 
</asp:ObjectDataSource>

注意:因为在本教程中我们不执行任何的更新和删除功能,所以您可以禁用 DataList 控件的视图状态以减少输出页面的大小。

在首次通过浏览器访问该页面时,没有提供 pageIndex 或 pageSize 查询字符串参数。因此将使用默认的 0 和 4 。如图 8 所示, DataList 控件显示了前四个产品。

图8 :列出了前四个产品

如果没有分页界面,用户不能直接进入第二个数据页面。在步骤4 中我们将创建分页界面。现在,我们只能通过直接在查询字符串中指定分页规则的方法来完成分页操作。例如,要浏览第二页,则在浏览器地址栏中的 URL 地址从 Paging.aspx 更改为 Paging.aspx?pageIndex=2 ,然后按回车键。这样第二个数据页面就显示出来了(请参见图 9 )。

图9 :显示第二个数据页面

步骤4 :创建分页界面

有各种各样的分页界面可以使用。GridView 控件、DetailsView 控件和 FormView 控件提供了四种分页界面,可以从中选择:

  • Next、Previous – 用户一次移动一个页面,可以进入下一页,也可以进入前一页。
  • Next、Previous、First、Last – 除了Next 按钮和 Previous 按钮,该界面还包括 First 按钮和 Last 按钮,使用这两个按钮可以进入第一页或最后一页。
  • Numeric – 在分页界面上列出页面编号,用户可以快速进入一个特定的页面。
  • Numeric、First、Last – 除了有页面编号,还可以快速进入第一页或最后一页。

我们可以为DataList 控件和 Repeater 控件选择一个分页界面并实现。这个过程包括在页面中创建所需的 Web 控件,以及在单击特定分页界面上的按钮时显示所请求的页面。另外,需要关闭某些分页界面控件。例如,使用 Next 、 Previous 、 First 、 Last 界面浏览第一个分页数据时,必须禁用 First 按钮和 Previous 按钮。

本教程使用Next 、Previous 、First 、Last 界面。在页面中添加四个 Web 按钮控件,并将其 ID 设置为 FirstPage 、 PrevPage 、 NextPage 和 LastPage 。将文本属性设置为“ << First ”、“ < Prev ”、“ Next > ”、和“ Last >> ”。

<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 >>" />

接下来为每一个按钮创建一个 Click Event Handler 。稍等片刻,我们会添加一些必要的代码来显示请求的数据页面。

记录分页记录总数

抛开选择的界面不管,我们还需要计算和记录分页记录的总数。总的行数(与页面大小有关)决定了分页数据的页面总数,还决定了添加或启用什么样的分页界面控件。正在构建的 Next 、 Previous 、 First 、 Last 界面中,有两种情况用到页面数:

  • 确定是否正在浏览最后一页。如果是此种情况,禁用 Next 按钮和 Last 按钮。
  • 用户单击 Last 按钮 , 需要快速将页面转到最后一页 , 最后一页的索引比页面数小 1 。

页面数是由总的单元行数除以页面尺寸计算得到的。例如,要对 79 条记录进行分页,每页四条记录,那么页面数就是 20 ( 79 除以 4 得到的)。如果使用 Numeric 分页界面,该信息会告知我们需要显示多少个页面按钮。如果页面中包含了 Next 按钮或 Last 按钮,那么页面数将用来确定何时禁用 Next 按钮或 Last 按钮。

如果分页界面包含Last 按钮,则必须在整个回传过程中记录被分页的记录总数,以便在单击 Last 按钮时能够确定最后页面的索引。要实现这个目的,可以在 ASP.NET 页面的代码文件类(该类将 TotalRowCount 的值保存到视图状态)中创建一个  TotalRowCount 属性:

private int TotalRowCount 

    get 
    { 
        object o = ViewState["TotalRowCount"]; 
        if (o == null) 
            return -1; 
        else 
            return (int)o; 
    } 
    set 
    { 
        ViewState["TotalRowCount"] = value; 
    } 
}

除了TotalRowCount ,为了能够方便的访问页面索引、页面大小和页面数,还要花点时间创建只读的页面级属性,如下所示 :

private int PageIndex 

    get 
    { 
        if (!string.IsNullOrEmpty(Request.QueryString["pageIndex"])) 
            return Convert.ToInt32(Request.QueryString["pageIndex"]); 
        else 
            return 0; 
    } 

 
private int PageSize 

    get 
    { 
        if (!string.IsNullOrEmpty(Request.QueryString["pageSize"])) 
            return Convert.ToInt32(Request.QueryString["pageSize"]); 
        else 
            return 4; 
    } 

 
private int PageCount 

    get 
    { 
        if (TotalRowCount <= 0 || PageSize <= 0) 
            return 1; 
        else 
            return ((TotalRowCount + PageSize) - 1) / PageSize; 
    } 
}

确定需要分页的记录总数

从 ObjectDataSource 的Select() 方法中返回的 PagedDataSource 已经包含了所有产品记录,虽然只在 DataList 控件中显示了其中一个子集。 PagedDataSource 的 Count 属性 只返回将要显示在  DataList 控件中的条目数,而 DataSourceCount 属性 返回 PagedDataSource 内的条目总数。所以,需要为 ASP.NET 页面的 TotalRowCount 属性赋值为 PagedDataSource 的 DataSourceCount 属性值。

要实现这一点,需要为 ObjectDataSource 的Selected 事件创建一个Event Handler 。在 Selected Event Handler 中我们可以访问 ObjectDataSource 的 Select() 方法的返回值,这样就可以得到 PagedDataSource :

protected void ProductsDefaultPagingDataSource_Selected 
    (object sender, ObjectDataSourceStatusEventArgs e) 

    // Reference the PagedDataSource bound to the DataList 
    PagedDataSource pagedData = (PagedDataSource)e.ReturnValue; 
 
    // Remember the total number of records being paged through 
    // across postbacks 
    TotalRowCount = pagedData.DataSourceCount; 
}

显示请求的数据页面

当用户单击分页界面的其中一个按钮时,需要显示请求的数据页面。因为分页参数是通过查询字符串指定的,所以要显示请求的数据页面,需要使用 Response.Redirect(url) 方法使用适当的分页参数把用户的浏览器重新请求定向到 Paging.aspx 页面。例如,要显示第二页,可以将用户的浏览器重定向到 Paging.aspx?pageIndex=1 。

要做到这一点,可以创建一个 RedirectUser(sendUserToPageIndex) 方法,该方法将用户重定向到Paging.aspx?pageIndex=sendUserToPageIndex 。接着从四个按钮的 Click Event Handler 中调用该方法。在 FirstPage Click Event Handler中,调用 RedirectUser(0) ,将其发送到第一个数据页面;在 PrevPage Click Event Handler中使用 PageIndex – 1 作为页面索引等等。

protected void FirstPage_Click(object sender, EventArgs e) 

    // Send the user to the first page 
    RedirectUser(0); 

 
protected void PrevPage_Click(object sender, EventArgs e) 

    // Send the user to the previous page 
    RedirectUser(PageIndex - 1); 

 
protected void NextPage_Click(object sender, EventArgs e) 

    // Send the user to the next page 
    RedirectUser(PageIndex + 1); 

 
protected void LastPage_Click(object sender, EventArgs e) 

    // Send the user to the last page 
    RedirectUser(PageCount - 1); 

 
private void RedirectUser(int sendUserToPageIndex) 

    // Send the user to the requested page 
    Response.Redirect(string.Format("Paging.aspx?pageIndex={0}&pageSize={1}", 
        sendUserToPageIndex, PageSize)); 
}

完成这些 Click Event Handler 后,通过单击按钮就可以对 DataList 的记录进行分页了。不妨花点时间尝试一下哦!

禁用分页界面控件

现在,不管是否浏览页面,这四个按钮都是可以使用的。不过我们想在显示第一个数据页面时禁用First 按钮和 Previous 按钮,在显示最后一个数据页面时禁用 Next 按钮和 Last 按钮。ObjectDataSource 的 Select() 方法返回的 PagedDataSource 对象有两个属性 – IsFirstPage 和 IsLastPage – 通过这两个属性我们可以判断我们正在浏览的是第一个数据页面还是最后一个数据页面。

将下面的代码添加到ObjectDataSource 的 Selected Event Handler 中:

// Configure the paging interface based on the data in the PagedDataSource 
FirstPage.Enabled = !pagedData.IsFirstPage; 
PrevPage.Enabled = !pagedData.IsFirstPage; 
 
NextPage.Enabled = !pagedData.IsLastPage; 
LastPage.Enabled = !pagedData.IsLastPage;

添加这些代码后,当浏览第一页时 将禁用 First 按钮和 Previous 按钮,而当浏览最后一页时将禁用 Next 按钮和 Last 按钮。

告知用户他们在浏览什么页面、总共存在多少页面就可以完成分页界面。在页面上添加一个 Web 标签控件并将其 ID 设置为 CurrentPageNumber 。在ObjectDataSource 的 Selected Event Handler 中设置其 Text 属性,这样该标签中就包含了当前正在浏览的页面号 (PageIndex + 1) 和总的页面数 (PageCount) 。

// Display the current page being viewed... 
CurrentPageNumber.Text = string.Format("You are viewing page {0} of {1}...", 
    PageIndex + 1, PageCount);

图10 显示了首次访问 Paging.aspx 页面时的屏幕画面因为查询字符串是空的,DataList 控件默认显示了前四个产品并且禁用 First 和 Previous 按钮。单击 Next 按钮显示接下来的四个记录(请参见图 11 ),现在 First 按钮和 Previous 按钮是启用的了。

图10 :显示第一个数据页面

图11 :显示第二个数据页面

注意:我们还可以进一步增强分页界面,例如允许用户指定每页浏览的页数。例如,添加一个 DropDownList 控件列出页面尺寸选项,像 5 、 10 、 25 、 50 和 All 等。一旦选择了一个页面尺寸,就需要重定向到 Paging.aspx?pageIndex=0&pageSize=selectedPageSize 。我把这个增强功能的实现作为一个练习留给读者去完成。

使用定制分页

DataList 控件使用效率很低的默认分页技术进行分页。但分页大量的数据时,必须使用定制分页。虽然这两种技术在实现细节上有些轻微的差别,但是DataList 控件定制分页技术和默认分页技术的的原理是相同的。在定制分页技术中,使用 ProductBLL 类的 GetProductsPaged 方法(而不是 GetProductsAsPagedDataSource 方法)。如同在 对大量数据进行高效分页 教程中所探讨的那样,必须为 GetProductsPaged 方法传递起始索引并返回最大行数。这些参数可以通过查询字符串来维护保持,就像在默认分页技术中使用 pageIndex 和 pageSize 参数那样。

因为定制分页中没有 PagedDataSource 方法,所以必须使用替代的技术来确定需要分页的记录总数,以及确定当前显示的是第一个数据页面还最后一个数据页面。 ProductsBLL 类中的 TotalNumberOfProducts() 方法可以返回被分页的记录总数。要确定当前浏览的是第一个数据页面还是最后一个数据页面,需要检查起始索引,如果为 0 ,那么正在浏览的就是第一个数据页面。如果起始索引加上返回的最大行数大于或等于被分页的记录总数,那么就可以确定当前浏览的是最后一个数据页面。

在下一篇教程中我们会详细介绍如何实现定制分页。

小结

虽然DataList 控件和 Repeater 控件都不在控件之外提供像 GridView 、DetailsView 和 FormView 控件提供的分页服务,但是只需稍微努力一下就可以实现这样的功能。实现默认分页的最简单的方法就是封装 PagedDataSource 内的整个产品集,然后将 PagedDataSource 绑定到 DataList 控件或 Repeater 控件上。在本教程中,我们在 ProductsBLL 类中添加 GetProductsAsPagedDataSource 方法来返回 PagedDataSource 。 ProductsBLL 已经包含了定制分页技术所需的方法 – GetProductsPaged 和 TotalNumberOfProducts 。

连同返回的用于定制分页显示的记录集和用于默认分页的PagedDataSource 中的所有记录集外,我们还需要手动添加分页界面。在本教程中,我们使用四个 Web 按钮控件创建了 Next 、Previous 、First 、Last 界面。还有一个标签控件用于显示当前页面编号和添加的总页面数。

在下一篇教程中,我们将会学习到如何为 DataList 和 Repeater 控件添加排序支持。以及如何创建一个既可以进行分页也可以进行排序的 DataList 控件(有使用默认分页和定制分页技术的示例)。

快乐编程!

posted @ 2016-05-01 23:31  迅捷之风  阅读(112)  评论(0编辑  收藏  举报