分页 DataList 或 Repeater 控件中的报表数据 (C#)
虽然 DataList 和 Repeater 都不提供自动分页或排序支持,但本教程演示如何向 DataList 或 Repeater 添加分页支持,从而允许更灵活的分页和数据显示接口。
简介
在联机应用程序中显示数据时,分页和排序是两个非常常见的功能。 例如,在联机书店搜索 ASP.NET 书籍时,可能有数百种此类书籍,但列出搜索结果的报告每页仅列出十个匹配项。 此外,结果可以按标题、价格、页计数、作者姓名等进行排序。 正如我们在 分页和排序报表数据 教程中所述,GridView、DetailsView 和 FormView 控件都提供内置分页支持,可在复选框的勾选时启用。 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
并在使用 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 控件中分页浏览数据。 这三个控件提供了一种称为 默认分页 的简单分页形式,只需选中控件智能标记中的“启用分页”选项即可实现。 使用默认分页时,每次在访问第一页时或当用户导航到其他数据页时,GridView、DetailsView 或 FormView 控件都会重新请求 ObjectDataSource 中的所有 数据。 然后,它根据请求的页面索引以及每页要显示的记录数,截取要显示的特定记录集。 我们在分页 和排序报表数据 教程中详细讨论了默认分页。
由于默认分页会重新请求每个页面的所有记录,因此在对足够大的数据进行分页时不切实际。 例如,假设分页浏览页面大小为 10 的 50,000 条记录。 每次用户移动到新页面时,都必须从数据库中检索所有 50,000 条记录,即使只显示其中 10 条记录。
自定义分页 通过仅获取要显示在请求页面上的精确记录子集来解决默认分页的性能问题。 实现自定义分页时,必须编写 SQL 查询,以便有效地仅返回正确的记录集。 在高效分页大量数据教程中,我们了解了如何使用 SQL Server 2005 年的新ROW_NUMBER()
关键字 (keyword) 创建此类查询。
若要在 DataList 或 Repeater 控件中实现默认分页,可以使用 PagedDataSource
类 作为对其内容进行分页的 周围的 ProductsDataTable
包装器。 类 PagedDataSource
具有一个 DataSource
属性,该属性可分配给任何可枚举对象,以及 PageSize
CurrentPageIndex
指示每页显示多少条记录和当前页索引的 和 属性。 设置这些属性后, PagedDataSource
即可将 用作任何数据 Web 控件的数据源。 PagedDataSource
枚举时, 将仅基于 PageSize
和 CurrentPageIndex
属性返回其内部DataSource
的相应记录子集。 图 4 描述了 类的功能 PagedDataSource
。
图 4:使用 PagedDataSource
可分页接口包装可枚举对象
PagedDataSource
可以直接从业务逻辑层创建和配置对象,并通过 ObjectDataSource 绑定到 DataList 或 Repeater,也可以直接在 ASP.NET 页的代码隐藏类中创建和配置对象。 如果使用后一种方法,则必须放弃使用 ObjectDataSource,而是以编程方式将分页数据绑定到 DataList 或 Repeater。
对象 PagedDataSource
还具有支持自定义分页的属性。 但是,我们可以绕过对自定义分页使用 PagedDataSource
,因为我们的 类中 ProductsBLL
已有 BLL 方法,这些方法专为返回要显示的精确记录的自定义分页而设计。
在本教程中,我们将了解如何通过向返回适当配置PagedDataSource
对象的类添加新方法ProductsBLL
,在 DataList 中实现默认分页。 在下一教程中,我们将了解如何使用自定义分页。
步骤 2:在业务逻辑层中添加默认分页方法
类 ProductsBLL
当前有一个方法用于返回所有产品信息 GetProducts()
,一个方法用于返回起始索引 GetProductsPaged(startRowIndex, maximumRows)
处的产品的特定子集。 使用默认分页时,GridView、DetailsView 和 FormView 控件都使用 GetProducts()
方法检索所有产品,但随后在内部使用 PagedDataSource
仅显示正确的记录子集。 若要使用 DataList 和 Repeater 控件复制此功能,可以在 BLL 中创建一个模拟此行为的新方法。
将方法添加到名为 GetProductsAsPagedDataSource
的ProductsBLL
类,该方法采用两个整数输入参数:
pageIndex
要显示的页面的索引,索引为零,以及pageSize
每页显示的记录数。
GetProductsAsPagedDataSource
首先从 GetProducts()
中检索所有记录。 然后,它创建一个 PagedDataSource
对象,将其 CurrentPageIndex
和 PageSize
属性设置为传入 pageIndex
的 和 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。 首先打开 文件夹中的页面Paging.aspx
PagingSortingDataListRepeater
,并将“工具箱”中的 DataList 拖到Designer,并将 DataList 的 ID
属性设置为 ProductsDefaultPaging
。 在 DataList 的智能标记中,创建名为 ProductsDefaultPagingDataSource
的新 ObjectDataSource 并将其配置为使用 GetProductsAsPagedDataSource
方法检索数据。
图 5:创建 ObjectDataSource 并将其配置为使用 GetProductsAsPagedDataSource
()
方法 (单击以查看全尺寸图像)
将“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 。
图 6:将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“ (无”) (单击以查看全尺寸图像)
GetProductsAsPagedDataSource
由于 方法需要两个输入参数,因此向导会提示我们输入这些参数值的源。
必须跨回发记住页面索引和页面大小值。 它们可以存储在视图状态中、持久保存到查询字符串中、存储在会话变量中,或者使用某种其他技术进行记住。 在本教程中,我们将使用 querystring,它的优点是允许为特定的数据页添加书签。
具体而言,请将查询字符串字段 pageIndex 和 pageSize 分别用于 pageIndex
和 pageSize
参数, (请参阅图 7) 。 请花点时间设置这些参数的默认值,因为当用户首次访问此页面时,查询字符串值将不存在。 对于 pageIndex
,将默认值设置为 0 (这将显示数据) 的第一页, pageSize
将默认值设置为 4。
图 7:使用 QueryString 作为 和 pageSize
参数的源 pageIndex
(单击以查看全尺寸图像)
配置 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
querystring 参数。 因此,使用默认值 0 和 4。 如图 8 所示,这会生成一个显示前四个产品的 DataList。
图 8:列出前四个产品 (单击以查看全尺寸图像)
如果没有分页界面,用户当前无法直接导航到第二页数据。 我们将在步骤 4 中创建分页接口。 不过,目前只能通过在查询字符串中直接指定分页条件来实现分页。 例如,若要查看第二页,请将浏览器地址栏中的 URL 从 Paging.aspx
Paging.aspx?pageIndex=2
更改为 ,然后按 Enter。 这会导致显示第二页数据 (见图 9) 。
图 9:显示第二页数据 (单击以查看全尺寸图像)
步骤 4:创建分页接口
可以实现各种不同的分页接口。 GridView、DetailsView 和 FormView 控件提供四种不同的接口供你选择:
- 接下来,上 一个用户可以一次将一个页面移动到下一页或上一页。
- 除了“下 一页”和“上一页”按钮外,此界面还包括用于移动到第一页或最末页的“第一页”和“最后一页”按钮。
- 数值 列出分页界面中的页码,使用户能够快速跳转到特定页面。
- 除数字页码外,数字页码、第一页、最后一页还包括用于移动到第一页或最末页的按钮。
对于 DataList 和 Repeater,我们负责决定并实现分页接口。 这涉及到在页面中创建所需的 Web 控件,并在单击特定分页界面按钮时显示请求的页面。 此外,可能需要禁用某些分页接口控件。 例如,使用“下一个”、“上一个”、“第一个”、“最后一个”界面查看数据的第一页时,将禁用“第一个”和“上一个”按钮。
在本教程中,我们来使用下一个、上一个、第一个、最后一个接口。 将四个按钮 Web 控件添加到页面,并将其ID
FirstPage
设置为 、PrevPage
、 NextPage
和 LastPage
。 将 Text
属性设置为 << 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
。 稍后,我们将添加显示所请求页面所需的代码。
记住正在分页的记录总数
无论选择哪种分页接口,我们都需要计算并记住正在分页的记录总数。 (的总行计数与页面大小) 确定正在分页的数据的总页数,从而确定添加或启用哪些分页接口控件。 在我们正在生成的“下一步”、“上一个”、“第一个”、“最后一个”界面中,页面计数以两种方式使用:
- 确定我们是否正在查看最后一页,在这种情况下禁用“下一步”和“最后一页”按钮。
- 如果用户单击“最后一个”按钮,我们需要将他们移动到最后一页,其索引比页计数少一个。
页面计数计算为总行计数的上限除以页面大小。 例如,如果要分页 79 条记录,每页 4 条记录,则页计数为 20 (上限为 79/4) 。 如果使用数字分页界面,此信息将告知我们要显示的数字页按钮数:如果分页界面包含“下一步”或“最后一个”按钮,则页面计数用于确定何时禁用“下一步”或“最后一个”按钮。
如果分页界面包含“最后一个”按钮,则必须跨回发记住正在分页的记录总数,以便在单击“最后一个”按钮时,我们可以确定最后一页索引。 为了方便执行此操作,请在 ASP.NET 页代码隐藏类中创建一个 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;
}
}
确定正在分页的记录总数
PagedDataSource
从 ObjectDataSource 方法返回的对象Select()
包含所有产品记录,即使只有一部分产品记录显示在 DataList 中。 PagedDataSource
属性Count
仅返回将在 DataList 中显示的项数;DataSourceCount
属性返回 中的PagedDataSource
项总数。 因此,我们需要为 ASP.NET 页的 TotalRowCount
属性分配 s DataSourceCount
属性的值PagedDataSource
。
为此,请为 ObjectDataSource 事件 Selected
创建事件处理程序。 在事件处理程序中 Selected
,我们有权访问 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;
}
显示请求的数据页
当用户单击分页界面中的按钮之一时,我们需要显示请求的数据页。 由于分页参数是通过 querystring 指定的,若要显示请求的数据页,请使用 Response.Redirect(url)
让用户浏览器使用相应的分页参数重新请求 Paging.aspx
页面。 例如,若要显示第二页数据,我们会将用户重定向到 Paging.aspx?pageIndex=1
。
为便于执行此操作,请创建一个 RedirectUser(sendUserToPageIndex)
将用户重定向到 Paging.aspx?pageIndex=sendUserToPageIndex
的方法。 然后,从四个 Button Click
事件处理程序调用此方法。 在事件处理程序中 FirstPage
Click
,调用 RedirectUser(0)
以将它们发送到第一页;在事件处理程序中 PrevPage
Click
,使用 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
事件处理程序完成后,可以通过单击按钮对 DataList 的记录进行分页。 花点时间试用一下!
禁用分页接口控件
目前,无论查看的页面如何,所有四个按钮都已启用。 但是,我们希望在显示数据的第一页时禁用“第一个”和“上一个”按钮,在显示最后一页时禁用“下一步”和“最后一个”按钮。 PagedDataSource
ObjectDataSource 方法返回的对象Select()
具有属性,IsLastPage
我们可以检查这些属性IsFirstPage
以确定是查看数据的第一页还是最后一页。
将以下内容添加到 ObjectDataSource 的 Selected
事件处理程序:
// 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;
通过此添加,查看第一页时将禁用“第一个”和“上一个”按钮,而在查看最后一页时将禁用“下一步”和“最后一个”按钮。
让我们通过通知用户他们当前正在查看的页面以及存在的总页数来完成分页界面。 将标签 Web 控件添加到页面,并将其 ID
属性设置为 CurrentPageNumber
。 在 ObjectDataSource 的 Selected 事件处理程序中设置其 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
首次访问时。 由于 querystring 为空,DataList 默认显示前四个产品:禁用“第一个”和“上一个”按钮。 单击“下一步”将显示接下来的四条记录 (请参阅图 11) ;现在已启用“第一个”和“上一个”按钮。
图 10:显示数据的第一页 (单击以查看全尺寸图像)
图 11:显示第二页数据 (单击以查看全尺寸图像)
注意
通过允许用户指定每页查看的页面数,可以进一步增强分页界面。 例如,可以添加 DropDownList 列出页面大小选项,如 5、10、25、50 和全部。 选择页面大小后,需要将用户重定向回 Paging.aspx?pageIndex=0&pageSize=selectedPageSize
。 我将实现此增强作为读者的练习。
使用自定义分页
DataList 使用低效的默认分页技术在其数据中分页。 分页浏览足够大量的数据时,必须使用自定义分页。 尽管实现细节略有不同,但在 DataList 中实现自定义分页背后的概念与默认分页相同。 使用自定义分页时,使用 ProductBLL
类方法 GetProductsPaged
(而不是 GetProductsAsPagedDataSource
) 。 如 高效分页大量数据 教程中所述, GetProductsPaged
必须传递起始行索引和要返回的最大行数。 这些参数可以通过查询字符串进行维护,就像默认分页中使用的 和 pageSize
参数一样pageIndex
。
由于没有 PagedDataSource
自定义分页,因此必须使用替代技术来确定正在分页的记录总数,以及是显示第一页还是最后一页数据。 TotalNumberOfProducts()
类中的 ProductsBLL
方法返回正在分页的产品总数。 若要确定是否正在查看第一页数据,请检查起始行索引是否为零,然后正在查看第一页。 如果开始行索引加上要返回的最大行数大于或等于正在分页的记录总数,则查看最后一页。
我们将在下一教程中更详细地探讨如何实现自定义分页。
总结
虽然 DataList 和 Repeater 都不提供 GridView、DetailsView 和 FormView 控件中现成的分页支持,但只需最少的工作量即可添加此类功能。 实现默认分页的最简单方法是将整个产品集包装在 内, PagedDataSource
然后将 绑定到 PagedDataSource
DataList 或 Repeater。 在本教程中, GetProductsAsPagedDataSource
我们向 ProductsBLL
类添加了 方法以返回 PagedDataSource
。 类 ProductsBLL
已包含自定义分页 GetProductsPaged
和 TotalNumberOfProducts
所需的方法。
除了检索要为自定义分页显示的精确记录集,或者检索 中用于默认分页的所有记录 PagedDataSource
,我们还需要手动添加分页接口。 在本教程中,我们创建了一个包含四个 Button Web 控件的 Next、Previous、First、Last 界面。 此外,还添加了显示当前页码和总页数的 Label 控件。
在下一教程中,我们将了解如何向 DataList 和 Repeater 添加排序支持。 我们还将介绍如何使用默认和自定义分页) 创建可分页和排序 (的 DataList。
编程快乐!
关于作者
斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Liz Shulok、Ken Pespisa 和 Bernadette Leigh。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈