導言
分頁和排序是在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 DropDownListPageList.Items.Clear();// Add a ListItem for each pagefor (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。他的聯絡電郵為mitchell@4guysfromrolla.com,也可以通過他的部落格http://ScottOnWriting.NET與他聯絡。