導言
分頁和排序是在WEB應用程序中展現數據常見的功能。比如,當我們在一個網上書店搜索ASP.NET書籍的時候,可能有幾百本相關書籍,但是我們只希望每頁顯示10條有效記錄。而且,我們還希望結果能根據標題、價格、頁數和作者等等來進行排序。過去的23個教程中我們研究了如何建立各種報表,包括在界面上添加編輯和刪除數據。但是我們沒有研究如何對數據進行排序,對於分頁我們也僅在研究DetailsView和FormView控件的時候看到。
Step 1:添加分頁和排序頁面
在我們開始以前,首先讓我們花些時間來添加包括本篇在內的最近四篇教程需要用到的頁面。我們先在項目中新建一個稱作PagingAndSorting的文件夾,接下來,為目錄新增以下幾個頁面,並配置為使用Site.master母板頁。
Default.aspx
SimplePagingSorting.aspx
EfficientPaging.aspx
SortParameter.aspx
CustomSortingUI.aspx
圖1:創建一個PagingAndSorting文件夾並且添加教程的頁面
下一步,讓我們打開Default.aspx頁面並且從UserControls中拖拽SectionLevelTutorialListing.ascx用戶控件到設計界面。我們在母板頁和站點導航教程中創建的這個用戶控件遍歷站點地圖並且以符號列表形式把它們呈現出來。
圖2:把SectionLevelTutorialListing.ascx用戶控件加入Default.aspx
要讓顯示我們將要創建的分頁和排序教程,我們需要把他們加入站點地圖中。打開Web.sitemap文件並且把下列代碼加在“編輯、插入和刪除”siteMapNode標記之後:
<siteMapNode title="Paging and Sorting" url="~/PagingAndSorting/Default.aspx" description="Samples of Reports that Provide Paging and Sorting Capabilities"> <siteMapNode url="~/PagingAndSorting/SimplePagingSorting.aspx" title="Simple Paging & Sorting Examples" description="Examines how to add simple paging and sorting support." /> <siteMapNode url="~/PagingAndSorting/EfficientPaging.aspx" title="Efficiently Paging Through Large Result Sets" description="Learn how to efficiently page through large result sets." /> <siteMapNode url="~/PagingAndSorting/SortParameter.aspx" title="Sorting Data at the BLL or DAL" description="Illustrates how to perform sorting logic in the Business Logic Layer or Data Access Layer." /> <siteMapNode url="~/PagingAndSorting/CustomSortingUI.aspx" title="Customizing the Sorting User Interface" description="Learn how to customize and improve the sorting user interface." /> </siteMapNode>
圖3:更新站點地圖使之包含新的頁面
Step 2:在GridView中顯示產品信息
在我們真正實現分頁和排序功能以前,讓我們首先創建一個標准的,沒有排序和分頁功能的GridView來顯示產品信息。其實這個工作我們已經做過很多次了,大家也應該很熟悉了。首先打開SimplePagingSorting.aspx頁面並且從工具箱中拖一個GridView控件到設計器,配置它的ID屬性為Products。接著,新建一個ObjectDataSource並使用ProductsBLL類的GetProducts()方法來獲取所有的產品信息。
圖4:使用GetProducts()方法獲取所有產品信息
因為這個報表是只讀的,我們不需要把ObjectDataSource的Insert(), Update(), 和 Delete()方法映射到相應的ProductsBLL方法,因此,對於UPDATE, INSERT, 和 DELETE頁我們從下拉列表中選取(None)。
圖5:對於UPDATE, INSERT, 和DELETE頁,我們從下拉列表中選擇(None)選項
下一步,讓我們調整GridView的字段使之只顯示產品名、供應商、分類、價格和狀態。另外,我們可以盡管進行一些格式上的調整,比如配置價格的HeaderText以符合我們的貨幣形式。經過這些修改之後,我們的GridView代碼應該和下面的差不多:
<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" EnableViewState="False"> <Columns> <asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" /> <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True" SortExpression="CategoryName" /> <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True" SortExpression="SupplierName" /> <asp:BoundField DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" DataFormatString="{0:C}" HtmlEncode="False" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns> </asp:GridView>
圖6顯示了在浏覽器中的效果,但是注意到,我們在一個屏幕上顯示產品。顯示了每個產品的名字、分類、供應商、價格和狀態。
圖6:每個產品都列出來了
Step 3:添加分頁支持
在一個屏幕上列出所有的產品對於用戶查看數據非常不方便。要讓結果更加可以管理,我們應該把數據分幾個頁面來呈現,並提供用戶切換頁面的功能。要實現這個只需要選擇GridView智能標簽前的Paging復選框即可(其實就是把AllowPaging屬性設置為true)。
圖7:點擊Enable Paging復選框來支持分頁
開啟分頁以後就能限制每頁顯示的記錄數量並且在GridView中增加了分頁導航。默認如圖7,是一系列頁面的數字,運行用戶快速從一個頁面切換到另一個。其實我們並不陌生,在為過去的教程中我們已經為DetailsView個FormView控件提供過分頁支持。
DetailsView和FormView控件僅僅支持每一頁顯示一條記錄。但是對於GridView,有一個PageSize 屬性,能讓我們配置每頁顯示的記錄數(默認是設置為10)。
GridView, DetailsView 和 FormView分頁導航能使用下面的屬性來配置:
PagerStyle –指示分頁導航的樣式,能設置BackColor, ForeColor, CssClass, HorizontalAlign等等。
PagerSettings –包含大量屬性來自定義分頁導航的功能;PageButtonCount代表顯示在底部分頁導航的最大頁面數(默認為10);Mode 屬性 代表分頁操作的形式,能設置為:
NextPrevious –顯示下一頁和上一頁按鈕,讓用戶一次朝後或者朝前翻一頁
NextPreviousFirstLast –除了下一頁和上一頁按鈕外,還提供第一頁和最後一頁按鈕,能讓用戶快速定位到首頁或者末頁數據
Numeric –顯示一系列頁面數字,讓用戶直接點擊數字切換到相應頁面
NumericFirstLast –除了頁面數字以外還提供第一頁和最後一頁按鈕,讓用戶能快速定位到首頁或者末頁數據,只有當沒有顯示首頁或者末頁數字時才顯示按鈕
此外,GridView, DetailsView和 FormView還提供了PageIndex 和 PageCount屬性來指示當前呈現的頁面和頁面總數。PageIndex屬性從0開始編號,因此我們浏覽第一頁的時候就為0,而PageCount是從1開始編號的,因此PageIndex的范圍在0和PageCount – 1之間。
讓我們再花一些時間來改進GridView分頁導航的默認外觀。首先,我們把分頁導航居右並且設置為灰色背景色。我們不希望直接設置GridView的PagerStyle屬性來實現,而是在Styles.css中創建一個稱作PagerRowStyle 的CSS類,然後設置主題文件中PagerStyle的CssClass屬性進行關聯。首先打開Styles.css然後把下面CSS類定義加入文件:
.PagerRowStyle { background-color: #ddd; text-align: right; }
接著,打開App_Themes 文件夾中DataWebControls 文件夾下的GridView.skin文件。我們在母板頁和站點導航教程中提到過,Skin文件能為WEB控件指定一個默認的屬性值。因此,我們設置PagerStyle的CssClass屬性為PagerRowStyle。同樣,讓我們配置分頁導航來顯示5個頁面數字(NumericFirstLast模式)。
<asp:GridView runat="server" CssClass="DataWebControlStyle"> <AlternatingRowStyle CssClass="AlternatingRowStyle" /> <RowStyle CssClass="RowStyle" /> <HeaderStyle CssClass="HeaderStyle" /> <FooterStyle CssClass="FooterStyle" /> <SelectedRowStyle CssClass="SelectedRowStyle" /> <PagerStyle CssClass="PagerRowStyle" /> <PagerSettings Mode="NumericFirstLast" PageButtonCount="5" /> </asp:GridView>
分頁用戶體驗
我們為GridView啟用了分頁又在GridView.skin文件中配置了PagerStyle和PagerSettings,圖8顯示了浏覽器中的呈現。注意到,每頁只有10條記錄,從分頁導航上我們可以知道現在浏覽的是第一頁的數據。
圖8:啟用分頁後每次只顯示一部分記錄
當用戶點擊分頁導航中某一個頁面數字,頁面回發並且呈現所請求的頁面的數據。圖9顯示了點擊最後一頁的效果。注意到,最後一頁只有一條記錄,因為總共有81條記錄,每頁顯示10條記錄,8頁80條,最後一頁就剩下一條了。
圖9:點擊一個頁面數字頁面回發顯示相應的一組記錄
分頁服務端工作方式
當用戶點擊了分頁導航中的按鈕後,頁面回發並開始下面服務端工作流:
1.GridView(或者 DetailsView 或者 FormView) PageIndexChanging時間觸發
2.ObjectDataSource從BLL獲取所有數據;GridView的PageIndex和PageSize屬性用來檢測哪些從BLL獲取的數據需要顯示在頁面上
3.GridView的PageIndexChanged事件觸發
在第二步中,ObjectDataSource從數據源獲取所有數據。如果我們僅僅是把AllowPaging屬性設置為true來進行分頁的話,默認方式分頁的WEB控件就會獲取所有數據並從中挑選合適的以HTML呈現在浏覽器上。出為數據庫中的數據被BLL或者ObjectDataSource進行緩存,否則對於大數據量的系統或者大並發的應用程序來說這種工作方式是非常低效的。
在下一個教程中,我們將會研究如何實現自定義分頁。使用自定義分頁我們就能指示ObjectDataSource精確地獲取用戶請求的那些數據。你能想象到,對於大數據的記錄集,自定義分頁能極大增加效率。
注意:默認的分頁方式不適合大數據集合系統和大流量的多並發情況,自定義分頁能改善但是它確實需要很多修改來實現(而不是象默認分頁方式那樣僅僅選擇一個復選框)。因此,默認的分頁方式對於小型的,小流量的網站來說比較合適的,因為它的實現確實非常簡單和快速。
例如,如果我們確信數據庫內不會多余100個產品。如果我們使用自定義分頁的話,多花的那些時間和贏得的效率來說是不值得的。然而,如果我那把有幾千幾萬的產品的話,不實現自定義分頁的話就會極大地降低我們應用程序的性能。
Step 4:自定義分頁體驗
數據Web控件提供了一些屬性來增進分頁體驗。例如,PageCount屬性指示總共有多少頁面,PageIndex屬性指示當前訪問的頁面,並能通過設置它來快速定位到某一頁。為了演示如何使用這些屬性來增進用戶分頁體驗,讓我們在頁面上添加一個Label Web控件來顯示用戶當前訪問的頁面,添加一個DropDownList控件來讓用戶快速切換到某個頁面。
首先,在頁面上添加一個Label Web控件,設置它的ID屬性為PagingInformation,然後把Text清空。接著,為GridView的DataBound事件創建一個事件處理器,然後添加如下代碼:
protected void Products_DataBound(object sender, EventArgs e) { PagingInformation.Text = string.Format("You are viewing page {0} of {1}...", Products.PageIndex + 1, Products.PageCount); }
這個事件處理器指定了PagingInformation標簽的Text屬性為用戶當前訪問的頁面-Products.PageIndex + 1(我們在這裡+1因為Products.PageIndex屬性是從0開始編號的)和頁面總數(Products.PageCount)。我在DataBound事件處理器而不是PageIndexChanged事件處理器中進行這個操作的原因在於,DataBound事件在每次數據綁定到GridView的時候都會觸發,而PageIndexChanged僅僅在頁面切換的時候觸發。當GridView綁定首頁的時候PageIndexChanging還沒有觸發(而DataBound事件能觸發)。
好了,用戶現在能看到他們正在訪問的頁面和頁面總數。
圖10:顯示當前頁和頁面總數
除了Label控件,我們再來添加一個DropDownList控件來顯示所有的頁數並選定當前浏覽的頁面。這樣,用戶就能選擇DropDownList中的某一選項來快速切換到新的頁面索引,我們首先拖一個DropDownList到設計器,然後設置ID屬性為PageList然後選擇啟用AutoPostBack。
接著,在DataBound中加如下代碼:
// Clear out all of the items in the DropDownList PageList.Items.Clear(); // Add a ListItem for each page for (int i = 0; i < Products.PageCount; i++) { // Add the new ListItem ListItem pageListItem = new ListItem(string.Concat("Page ", i + 1), i.ToString()); PageList.Items.Add(pageListItem); // select the current item, if needed if (i == Products.PageIndex) pageListItem.Selected = true; }
這段代碼首先清楚了PageList DropDownList中所有的項。既然我們不能預料到頁面數會不會改變,看上去這個操作可能有些多余。但是其它用戶可能會並發使用系統來從Products表中添加或者移除記錄。這樣的插入或者刪除操作可能會改變數據的頁數。
接著,我們重新創建頁數並選擇GridView PageIndex作為默認。我們循環0到PageCount – 1進行新增每一個ListItem,如果當前循環所以等於GridView的PageIndex屬性的話,我們把這個項的Selected屬性設置為true。
最後,我們需要為DropDownList的SelectedIndexChanged事件創建一個事件處理器。當用戶每次選擇了一個不同頁面的時候觸發,我們只需要雙擊設計器中的DropDownList來創建事件處理器,然後添加下面代碼:
protected void PageList_SelectedIndexChanged(object sender, EventArgs e) { // Jump to the specified page Products.PageIndex = Convert.ToInt32(PageList.SelectedValue); }
如圖11顯示,只不過是改變GridView的PageIndex屬性並重新綁定GridView。在GridView的DataBound事件處理器中,相應的DropDownList ListItem被選擇。
圖11:用戶選擇下拉列表Page 6項後就能切換到第六頁
Step 5:添加雙向排序支持
增加雙向排序的支持和增加分頁支持一樣簡單-只需要選擇GridView 智能標簽的Enable Sorting選項(它會設置GridView的AllowSorting property 屬性為true)。這樣,GridView每一個字段的標題都會顯示為LinkButtons,點擊後頁面就會回發,所點擊列的所有數據就會以升序顯示。再次點擊同一個LinkButton就能以降序顯示。
注意:如果你使用一個自定義的數據訪問層而不是強類型DataSet的話,你可能找不到GridView的Enable Sorting選項。因為ADO.NET DataTable提供了Sort方法使用指定標准對DataTable的DataRow進行排序,因此強類型DataSet提供了排序支持。
如果你的DAL不返回支持排序的對象,我們就需要配置ObjectDataSource來實現對業務邏輯層返回數據的排序。我們將會在將來的教程中研究如何排序業務邏輯或者數據訪問層中的數據。
排序的LinkButton以HTML鏈接的形式呈現,當前的顏色(未訪問過未藍色訪問過為暗紅色)和標題的背景色有了沖突,讓我們設置所有標題中的鏈接在訪問過和未訪問的情況下都為白色。我們通過在Styles.css中添加如下的類來實現:
.HeaderStyle a, .HeaderStyle a:visited { color: White; }
這段代碼表示,使用HeaderStyle類中的所有鏈接都以白色進行顯示。
在定義了CSS後,頁面浏覽器中的效果如圖12,圖1也顯示了Price字段標題上的鏈接被點擊後的效果。
圖12:結果根據UnitPrice以正序形式進行排序
研究排序工作方式
所有的GridView字段-BoundField, CheckBoxField, TemplateField等等-都有SortExpression屬性指示當標題上的排序鏈接點擊後的排序方式。GridView同樣也有一個SortExpression屬性。當排序LinkButton被點擊後,GridView把它的SortExpression設置為該字段的SortExpression,接著,數據就重新按照GridView的SortExpression屬性以一定次序從ObjectDataSource返回。下面列出了用戶排序時GridView具體的流程步驟:
1.GridView的Sorting event 觸發
2.GridView的SortExpression 屬性設置為點擊LinkButton所在字段的SortExpression
3.ObjectDataSource重新從BLL中獲取所有數據並根據GridView的 SortExpression來排序數據
4.GridView的PageIndex被置0,也就是用戶轉到了第一頁數據(假設分頁是開啟的)
5.GridView的Sorted 事件觸發
和默認分頁方式一樣,默認排序方式從BLL中獲取所有數據進行排序。在未分頁或者使用默認方式分頁的情況下,我們沒有辦法改善性能(除了緩存數據)。然而在後續教程中,我們能通過自定義分頁來使排序更有效率。
When binding an ObjectDataSource to the GridView through the drop-down list in the GridView's smart tag, each GridView field automatically has its SortExpression property assigned to the name of the data field in the ProductsRow class. For example, the ProductName BoundField's SortExpression is set to ProductName, as shown in the following declarative markup:
如果我們通過GridView智能標簽的下拉框把ObjectDataSource綁定到GridView的話,GridView的字段就會自動把SortExpression屬性設置為相應的ProductsRow類。比如,ProductName BoundField的SortExpression就設置為ProductName,代碼如下:
<asp:BoundField DataField="ProductName" HeaderText="Product" SortExpression="ProductName" />
一個字段能通過清除SortExpression(設置為空字符串)屬性來禁止排序。想象下如果我們不希望用戶根據價格來排序產品,我們就可以通過在代碼標簽或者在字段對話框(點擊GridView智能標簽的Edit Columns鏈接)中移除UnitPrice BoundField的SortExpression屬性例子實現。
圖13:結果根據UnitPrice以升序進行排序
一旦移除UnitPrice BoundField 的SortExpression屬性,標題上就只是文字而不是鏈接了,防止用戶根據價格進行排序。
圖14:移除了SortExpression屬性,用戶就不能再根據產品價格進行排序了
編程排序GridView
我們還可以使用GridView的Sort 方法編程對GridView的內容進行排序。只需要傳入SortExpression和SortDirection(Ascending或者Descending)即可。
我們關閉了UnitPrice的排序是考慮到不希望用戶只去買最便宜的東西。然而,我們希望鼓勵用戶去買最貴的產品,所以我們希望能按照價格排序但是僅提供從價格最高到價格最低這麼一種排序方式。
要實現這樣的需求,我們首先在頁面上添加一個Button Web控件,設置它的ID屬性為SortPriceDescending,Text屬性為“Sort by Price”。然後在設計器雙擊按鈕控件來創建按鈕的Click事件處理器,加入如下代碼:
protected void SortPriceDescending_Click(object sender, EventArgs e) { // Sort by UnitPrice in descending order Products.Sort("UnitPrice", SortDirection.Descending); }
點擊按鈕頁面轉向第一頁並以價格排序,從最貴的到最便宜的(見圖15)。
圖15:點擊按鈕讓產品從最貴到最便宜進行排序
總結
在這個教程中我們已經說了如何實現默認分頁和排序,僅僅需要選擇一個復選框來實現!當用戶排序或者切換頁面的時候都會有類似的工作方式:
1.頁面回發
2.數據Web控件pre-level的事件觸發(PageIndexChanging 或者 Sorting)
3.所有信息從ObjectDataSource重新獲取
4.數據Web控件post -level的事件觸發(PageIndexChanged或者 Sorted)
我們實現了報表的基本分頁和排序,但是要取得更好的性能我們還需要創建自定義的分頁來或者進一步改善分頁和排序界面。後面的教程會繼續討論相關主題。
編程愉快!
關於作者
Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用微軟Web技術。Scott是個獨立的技 術咨詢顧問,培訓師,作家,最近完成了將由Sams出版社出版的新作,24小時內精通ASP.NET 2.0。他的聯系電郵為[email protected],也可以通過他的博客http://ScottOnWriting.NET與他聯系。