소개
페이징과 정렬은 데이터를 표시할 때 자주 사용하는 기능입니다. 예를 들어, 온라인 서점에서 ASP.NET에 관한 책을 검색하면 수백 또는 수천 개의 결과가 있을 수 있지만 각 페이지에는 10개의 항목만 나열됩니다. 그리고 결과는 제목(책 제목), 가격(가격), 페이지 수(페이지 수), 저자 이름(저자) 등으로 정렬할 수 있습니다. 페이지 매김 및 보고서 데이터 정렬에서 논의한 것처럼 GridView, DetailsView 및 FormView에는 모두 체크박스를 선택하여 활성화할 수 있는 페이징 기능이 내장되어 있습니다. GridView는 내장된 정렬도 지원합니다.
안타깝게도 DataList나 Repeater 모두 내장 페이징 및 정렬 기능을 제공하지 않습니다. 이 장에서는 DataList 및 Repeater에 페이징 및 정렬 지원을 추가하는 방법을 알아봅니다. 페이징 인터페이스를 만들고, 올바른 페이지 기록을 표시하고, 포스트백 프로세스 중에 검색된 페이지를 기록해야 합니다. GridView, DetailsView 및 FormView보다 시간이 더 많이 걸리고 더 많은 코드를 작성하지만 더 많은 확장성을 제공합니다.
참고: 이 장에서는 페이징에 중점을 두고 다음 장에서는 정렬에 대해 알아봅니다.
1단계: 페이징 및 정렬 튜토리얼 페이지 추가
먼저 이 장과 다음 장에 필요한 페이지를 추가하세요. PagingSortingDataListRepeater라는 폴더를 만들고 다음 5개 페이지를 추가합니다. 이때 모두 Site.master를 선택해야 합니다.
Default.aspx
Paging.aspx
Sorting.aspx
SortingWithDefaultPaging.aspx
SortingWithCustomPaging.aspx
그림 1 : 페이지 만들기
그런 다음 Default.aspx 페이지를 열고 UserControls 폴더에서 SectionLevelTutorialListing.ascx 사용자 컨트롤을 드래그합니다. 우리는 이 사용자 컨트롤을 여러 번 사용해 왔습니다. 마스터 페이지 및 사이트 탐색을 참조하십시오.
그림 2: 사용자 컨트롤 추가
정렬 및 페이지 매기기 튜토리얼을 나열하려면 사이트 맵에 추가해야 합니다. 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: 사이트 맵 업데이트
페이징 검토
앞서 우리는 GridView, DetailsView 및 FormView를 사용하여 페이지를 매기는 방법을 배웠습니다. 이 세 가지 컨트롤은 모두 기본 페이징이라는 기능을 제공합니다. 스마트 라벨에서 "페이징 활성화"만 선택하면 됩니다. 기본 페이징을 사용하는 경우 첫 번째 페이지이든 다른 페이지이든 데이터가 요청될 때마다 GridView, DetailsView 및 FormView가 모든 데이터를 다시 요청합니다. 그런 다음 요청된 페이지 인덱스와 페이지당 표시되는 레코드 수를 기반으로 특정 페이지에 대한 데이터가 표시되고, 다른 데이터(즉, 요청되었지만 표시되지 않은 데이터)는 무시됩니다. 보고서 데이터 페이징 및 정렬에서 기본 페이징에 대해 자세히 설명했습니다.
기본 페이징은 매번 모든 데이터를 요청하므로 데이터 양이 많은 경우에는 적합하지 않습니다. 예를 들어 페이지당 10개의 데이터 항목을 표시하여 총 50,000개의 항목을 표시한다고 가정해 보겠습니다. 사용자가 페이지를 볼 때마다 데이터베이스에서 50,000개의 데이터 항목이 요청되고 그 중 10개만 표시됩니다.
Custom Paging은 매번 요청한 데이터만 반환하므로 기본 Paging의 성능 문제를 해결합니다. 사용자 정의 페이징을 사용할 때 올바른 레코드를 반환하는 효과적인 SQL 문을 작성해야 합니다. 우리는 여기서 SQL Server2005의 ROW_NUMBER() 키워드를 사용하여 이러한 문을 만드는 방법을 배웠습니다.
DataList 또는 Repeater의 기본 페이징을 사용하면 PagedDataSource 클래스를 사용하여 ProductsDataTable에서 페이지를 매겨야 하는 콘텐츠를 래핑할 수 있습니다. PagedDataSource 클래스에는 모든 열거 유형 개체에 할당할 수 있는 DataSource 속성과 PageSize(페이지당 표시되는 레코드 수) 및 CurrentPageIndex(현재 페이지의 인덱스)가 있습니다. 이러한 속성이 설정되면 PagedDataSource를 모든 데이터 컨트롤의 데이터 소스로 사용할 수 있습니다. PagedDataSource는 PageSize 및 CurrentPageIndex를 기반으로 적절한 레코드를 반환합니다. 그림 4에서는 PagedDataSource 클래스의 기능을 설명합니다.
그림 4: PagedDataSource는 페이징 가능한 인터페이스를 사용하여 열거형 개체를 래핑합니다.
PagedDataSource 개체는 BLL에서 직접 생성 및 구성하고 ObjectDataSource를 통해 DataList 또는 Repeater에 바인딩할 수 있습니다. 또는 ASP.NET 페이지 뒤에 있는 코드에서 직접 이 작업을 수행할 수 있습니다. 후자의 방법을 사용하는 경우 ObjectDataSource를 사용할 수 없으며 프로그래밍 방식으로 페이징 데이터를 DataList 또는 Repeater에 직접 바인딩해야 합니다.
PagedDataSource对象也有支持自定义分页的属性。但是在这里我们将不讨论它,因为我们在ProductsBLL类里已经有一个可以精确的返回需要显示的记录的方法。本章我们将学习如何通过在ProductsBLL类里添加一个返回合适的PagedDataSource对象的方法来实现默认分页。下章我们再讨论自定义分页。
第二步: 在BLL里添加默认的分页方法
ProductsBLL类里现在有一个返回所有product的方法–GetProducts()–和一个返回特定子集的方法–GetProductsPaged(startRowIndex,maximumRows)。当使用默认分页时,GridView, DetailsView, FormView 使用GetProducts()方法获取所有的product,但是在内部使用PagedDataSource来显示正确的记录子集。在DataList和Repeater里实现同样的功能,我们可以在BLL里创建一个模拟这种行为的方法。
在ProductsBLL里添加一个带两个整型参数的方法,名为GetProductsAsPagedDataSource:
pageIndex – 显示的页的索引,从0开始
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; }
第三步: 在DataList里使用默认分页显示Product
完成GetProductsAsPagedDataSource方法后,我们现在来创建一个提供默认分页的DataList或Repeater。打开PagingSortingDataListRepeater文件夹下的Paging.aspx页,拖一个DataList进来,将ID设为ProductsDefaultPaging。通过智能标签创建一个名为ProductsDefaultPagingDataSource的ObjectDataSource并用GetProductsAsPagedDataSource方法配置它。
图 5: 创建并配置ObjectDataSource
在UPDATE, INSERT, DELETE 标签的下拉列表里都选择“(None)”.
图 6: 在UPDATE, INSERT, DELETE 标签的下拉里选择“(None)”
因为GetProductsAsPagedDataSource方法需要两个参数,因此向导会提示我们选择参数源。page index和page size的值必须在postback过程中记下来。它们可以存在view state,querystring,session里或用其它技术来记录。本章我们使用querystring。
分别使用querystring字段“pageIndex” 和“pageSize”来配置pageIndex和pageSize。见图7。由于用户第一次浏览页的时候没有querystring,因此还需要设置这两个参数的默认值。将pageIndex的默认值设为0(表示显示第一页数据),将pageSize的默认值设为4。
图 7: 配置参数
配置完ObjectDataSource后,Visual Studio自动为DataList创建一个ItemTemplate。修改它让它只显示product的name,category和supplier。将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的view state来减少页面的大小。
第一次浏览页的时候,querystring里没有提供pageIndex 和pageSize的值,因此将使用默认的0和4。见图8。DataList将显示4条product记录。
图 8: 显示4条Product
由于还没有分页的界面,因此用户现在还不能直接导航到第二页。我们将在第四步里创建分页界面。现在我们只能直接在querystring里指定分页的参数来进行分页。例如,我们可以将地址从Paging.aspx改为Paging.aspx?pageIndex=2,然后回车,来浏览第二页。这样就可以看到第二页的数据了,见图9。
图 9: 显示第二页数据
第四步: 创建分页界面
有很多不同的完成分页界面的方法。GridView, DetailsView, FormView 提供了4种不同的界面:
Next, Previous(后一页,前一页) – 用户可以浏览上一页或下一页.
Next, Previous, First(第一页), Last (最后一页)– 除了上面的功能,这个还包含第一页和最后一页。
Numeric (数字)–在分页界面上列出页数,用户可以随意的选择一个页 .
Numeric, First, Last – 在上一个功能的基础上增加了第一页和最后一页.
对DataList 和Repeater而言,我们需要决定它的分页界面并实现它。这其中包含了需要创建web控件和当特定页的button被点时显示请求的页。另外某些分页界面的控件可能需要禁用。例如,当使用Next, Previous, First, Last这个模式来显示时,如果浏览第一页数据,那么第一页和前一页的button应该被禁用。
本章我们使用 Next, Previous, First, Last界面。添加4个button,并将ID分别设为FirstPage,PrevPage,NextPage和LastPage。将Text设为“”, “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 >>" />
然后为每个button创建一个Click事件处理。呆会我们将添加代码来显示请求的页。
记下分页的总记录数
不管选择哪种分页界面,我们都需要计算和记下分页的总记录数。总的行数(和page size)来决定总的页数,它决定了那些分页界面的控件需要增加或启用。在我们创建的Next, Previous, First, Last 界面里,page count(页数)在两种情况下需要被用到:
判断我们是否在浏览最后一页,这种情况下Next 和Last buttons 需要禁用。
如果用户点了Last button我们需要将它转到最后一页,它的索引等于page count减1。
page count通过总行数除以page size(页数)来计算得到。例如我们要分页79条记录,每页显示4条,那么page count为20(79/4)。如果我们使用数字分页界面,就可以通过这个信息来决定要显示多少个数字页的button。如果分页界面只包含Next 和Last buttons,可以通过page count来什么时候禁用Next 和Last buttons。
如果分页界面包含Last button(最后一页),我们需要在postback过程中记下分页的总记录数,这样在点Last button的时候我们可以获得最后一页的索引。为了方便实现这个,我们在ASP.NET页的后台代码里创建一个TotalRowCount属性来将这个值保存到view state里。
private int TotalRowCount { get { object o = ViewState["TotalRowCount"]; if (o == null) return -1; else return (int)o; } set { ViewState["TotalRowCount"] = value; } }
除了TotalRowCount外,还需要为page index,page size和page count创建页面级的只读属性来方便读取。
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对象包含所有的product记录,即使只有一部分会在DataList里显示。PagedDataSource的Count property 返回将在DataList里显示的项的数目。DataSourceCount property 返回PagedDataSource里的所有项的的总数目。因此我们需要将ASP.NET页的TotalRowCount属性赋值为PagedDataSource的DataSourceCount。
我们为ObjectDataSource的Selectd事件创建一个event handler来完成这些。在Selectd的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; }
显示请求的页的数据
当用户点分页界面上的button时,我们需要显示请求的页的数据。由于分页的参数在querystring里指定,因此使用Response.Redirect(url)来让用户重新请求带合适分页参数的Paging.aspx页。例如,显示第二页的数据,我们将用户重定向到Paging.aspx?pageIndex=1。
创建一个RedirectUser(sendUserToPageIndex)方法来重定向用户到Paging.aspx?pageIndex=sendUserToPageIndex。然后在四个按钮的Click事件处理里调用这个方法。在FirstPageClick里调用RedirectUser(0),在PrevPageClick里调用RedirectUser(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的记录现在可以通过button来分页了,你可以测试一下。
禁用分页控件
现在无论浏览哪页四个按钮都是可用的。然而我们在浏览第一页时需要禁用 First 和Previous buttons ,在浏览最后一页时需要禁用Next 和Last buttons。通过ObjectDataSource的Select()方法返回的PagedDataSource对象有几个属性– IsFirstPage 和 IsLastPage –通过它们可以判断用户浏览的是否是第一或最后一页数据。添加下面的代码到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;
添加完后,当浏览第一页时,First 和Previous buttons 将被禁用。当浏览最后一页时,Next 和 Last buttons 将被禁用。
我们最后来实现在分页界面里通知用户他们当前是浏览的哪页和一共有多少页。添加一个Label控件并将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默认显示最开始的4条product。First 和Previous buttons 被禁用。点Next 会显示下面的4条记录(见图11),而First 和Previous buttons 同时被启用了。
图 10: 第一页数据
图 11: 第二页数据
注意:分页界面可以进一步改善,比如增加允许用户来指定每页显示多少记录。例如添加一个DropDownList列出page size的选项,比如5, 10, 25, 50, 和ALL。用户选择了page size后会重定向到Paging.aspx?pageIndex=0&pageSize=selectedPageSize。我将这个作为练习留给读者自己完成。
使用自定义分页
DataList使用没有效率的默认分页技术。当大数据量时,我们需要使用自定义分页。虽然实现的细节有所不同,但是分页里的概念和默认分页是一样的。默认分页时,使用ProductsBLL类的GetProductsPaged方法(而不是GetProductsAsPagedDataSource)。正如在大数据量时提高分页的效率 里讨论的那样,GetProductsPaged需要传入开始行的索引和行的最大数目。这些参数可以通过默认分页里使用的querystring里的pageIndex和pageSize参数来保存。
由于自定义分页里没有PagedDataSource,所以需要其它技术来决定总记录数和判断我们是否显示第一或最后一页数据。ProductsBLL类的TotalNumberOfProducts()方法返回roduct的总记录数。而为了判断是否浏览的是第一页数据,我们需要检查开始行的索引–如果是0,则表示在浏览第一页。如果开始行的索引加上最大的行数大于或等于总记录数则表示在最后一页.我们将在下章详细的讨论如何实现自定义分页。
总结
DataList和Repeater都没有提供象GridView, DetailsView, FormView 那样的分页的支持,这样的功能需要我们来实现。最简单的实现方法是使用默认分页,将所有的product都包装到PagedDataSource里,然后绑定PagedDataSource到DataList或Repeater。本章我们在ProductsBLL类里添加GetProductsAsPagedDataSource方法,它返回PagedDataSource。ProductsBLL类已经包含了自定义分页需要的方法– GetProductsPaged和TotalNumberOfProducts。
不管是自定义方法里获取精确的记录还是默认方法里获取所有记录,我们都需要手动添加分页界面。本章我们创建的是包含4个button控件的Next, Previous, First, Last interface 。当然还添加了一个显示当前页和总页数的Label控件。
更多DataList 및 Repeater 데이터 페이징相关文章请关注PHP中文网!