簡介
顯示大量已經按類別(不是很多)排序的資料但沒有類別分界線,使用者很難找到所需要的類別。例如,資料庫中只有9個類別(8個不同的類別和1個null),共81種產品.現在用一個GridView列出所有產品,假設有使用者對類別Seafood的產品感興趣,她一定會按類別排序,把Seafood產品排列在一起.排序後,使用者便尋找Seafood產品開始和結束的地方。雖然是按英文字母排列類別不難找到Seafood,但仍要花些時間在GridView尋找。為了進一步的區分類別,許多網站使用類別分界線這種排序使用者介面來區別不同的類別。例如像圖1中的分界線可以使使用者很快地找到需要的類別。
圖1:不同組明顯的區分開來
在這篇文章中我們將講解如何建立這種排序使用者介面.
步驟1:建立一個普通的,能夠排序的GridView
在我們學習如何建立增強型排序使用者介面之前,先建立一個普通的列出所有產品GridView並且能夠排序.現在打PagingAndSorting檔案夾下的CustomSortingUI.aspx,添加一個GridView,設定ID="ProductList",以一個ObjectDataSource為資料來源,ObjectDataSource的資料從ProductsBLL類的GetProducts()取得。
接下來設定GridView的列,包括ProductName, CategoryName, SupplierName, UnitPrice繫結資料行和Discontinued複選框列,再設定GridView允許排序。設定完這些以後你應該可以在代碼編輯看到下面這些代碼:
<asp:GridView ID="ProductList" runat="server" AllowSorting="True" 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" DataFormatString="{0:C}" HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" /> <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" /> </Columns></asp:GridView><asp:ObjectDataSource ID="ObjectDataSource1" runat="server" OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts" TypeName="ProductsBLL"></asp:ObjectDataSource>
這時你在瀏覽器中預覽你將看到類似圖2的介面,資料按類別的字母順序排序.
圖2:可排序GridView按Category按序
步驟2:如何添加分界行
完成建立建立一個普通的,能夠排序的GridView後,就要在每一個類別的第一行之前添加分界行。怎麼把這些分界行添加進GridView呢?一開始我們會想到遍曆GridView的所有行,遇到排序列中的值不同就插入分界行。按照這種想法我們自然倒想利用GridView的DataRowBound事件來實現,DataRowBound事件我們已經在基於資料的自訂格式化一章中講過,DataRowBound事件通常應用于格式化資料行。但是,DataRowBound事件不能解決這個問題,因為不能用這個事件動態地添加行到GridView,GridView的Rows集合是唯讀.
要添加行到GridView有以下3個方法:
添加分界行到GridView繫結資料源中
GridView綁定資料後,添加額外的TableRow對象到GridView的控制項集
建立一個自訂伺服器控制項,擴充GridView控制項,重寫它的方法以重新構造GridView的結構
如果自訂排序使用者介面廣泛應用多個頁面或多個網站,建立一個自訂伺服器控制項是最好的方法.但採用這種方法要寫太多的代碼並且要深入理解GridView控制項的的內部原理。因此,我們在這篇文章中不考慮使用這種方法。另外兩種不同的方法-添加分界行到GridView繫結資料源中和在GridView綁定資料後操作它的子控制項,值得討論.
添加分界行到綁定GridView的資料來源中
當GridView被綁定到資料來源,GridView從資料來源中的每一條記錄建立一條GridViewRow.因此我們可以在資料繫結(binding)之前添加一條"分界記錄",圖3描述了這個過程.
圖3:添加分界行到資料來源原理圖
"分界記錄"這個術語之所以加引號是因為實際沒有這條特殊的記錄,我們只是在資料來源中添加一些標記行。打個比方,綁定ProductsDataTable對象到GridView,ProductsDataTable由ProductRow組成。我們可以標記一條記錄作為"分界記錄"並且設定CategoryID為-1(因為-1不存在普通記錄中).
使用這種方法要以下的步驟實現:
1.得到綁定到GirdView的資料(一個ProductsDataTable對象)
2.基於GridView的SortExpression和SortDirection屬性排列資料
3.遍曆ProductsDataTable中的ProductsRows,尋找排序列的分界。
4.在每組的分界處插入"分界行"ProductsRow到DataTable中,CategoryID列值為-1(或其它可以標記"分界行"的值).
5.插入"分界行"後動態地綁定資料到GridView.
完成以上5個步驟,還要在RowDataBound事件中判斷哪些行是"分界行"(CategoryID=-1的行),格式化"分界行"的顯示樣式.此外還要再做一些工作,在Sorting事件中保留SortExpressiont和SortDirection的值.
GridView綁定資料後,添加額外的TableRow對象到GridView的控制項集
相比在GridView綁定資料之前,在GridView綁定資料之後添加"分界行"更勝一籌.GridView是由一個Table構成,一個Table由Rows集構成,一個Row由Cells集構成,這就是GridView的控制項層次.GridView根部是Table對象,再由資料來源的每條記錄產生GridViewRow (由TableRow派生出來),資料來源的每一格的值又在GridViewRow產生TableCell.
我們可以利用GridView的控制項層次構成以後在每組之間的添加分界行.因為GridView的控制項階層是在頁面呈現的時候建立的,所以重寫Page類的Render方法,在需要的地方加上分界行,圖4描述了這個過程.
圖4:更改GridView控制項階層添加界行原理圖
這篇文章將用最後一個方法建立自訂排序使用者介面.
注意:這裡使用的代碼是基於Teemu Keiski Blog中的Playing a Bit with GridView Sort Grouping一文.
步驟3:添加分界行到GridView的控制項階層中
我們要在GridView的控制項層次已經構造完畢後,以及在頁面呈現之前添加分界行,必須在頁面生命週期的最後階段但又必須在GridView產生HTML語言之前進行,因此要重寫Page類的Render方法,比如在下面的代碼:
protected override void Render(HtmlTextWriter writer){ // Add code to manipulate the GridView control hierarchy base.Render(writer);}
當頁面的原來的Render方法(base.Render(writer))被調用,頁面的每個控制項將被顯示出來,產生出來的HTML標記基於控制項層次.因此我們勢必要在base.Render(writer)被調用之前更改GridView的控制項階層.要添加分界行必須確保使用者已經排好序,但剛開始GridView是沒有按類別排好序的,不必要添加分界行.
注意:如果要使GridView預設是按某一列排序的,就要在Page_Load中調用GridView的Sort方法,注意要在if(!IsPostBack)中.請參考分頁和排序資料(Paging and Sorting Report Data)一章獲得關Sort方法的更多相關知識.
假設已經完成排序,下面要做的是判斷是按哪一列排序並尋找該列不同組的分界處,下面的代碼判斷是否排序,按哪一列排序:
protected override void Render(HtmlTextWriter writer){ // Only add the sorting UI if the GridView is sorted if (!string.IsNullOrEmpty(ProductList.SortExpression)) { // Determine the index and HeaderText of the column that //the data is sorted by int sortColumnIndex = -1; string sortColumnHeaderText = string.Empty; for (int i = 0; i < ProductList.Columns.Count; i++) { if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression) == 0) { sortColumnIndex = i; sortColumnHeaderText = ProductList.Columns[i].HeaderText; break; } } // TODO: Scan the rows for differences in the sorted column's values}
假如GridView沒有排序,SortExpression就沒有設定值.我們要做的是對已經排序的GridView添加分界行.完成排序後,下一步要判斷是按第幾列排序,遍曆每一列,比較SortExpression和哪一列的HeaderText相同,用兩個變數sortColumnIndext和sortColumnHeaderText分別儲存該列的索引和標題.
知道是按第幾列排序後,最後一步是添加分界行到GridView中,遍曆排序列的每一行,比較每上一行的值和它的當前行的值相不相同,不相同就要在中間插入分界行,代碼如下:
protected override void Render(HtmlTextWriter writer){ // Only add the sorting UI if the GridView is sorted if (!string.IsNullOrEmpty(ProductList.SortExpression)) { // ... Code for finding the sorted column index removed for brevity ... // Reference the Table the GridView has been rendered into Table gridTable = (Table)ProductList.Controls[0]; // Enumerate each TableRow, adding a sorting UI header if // the sorted value has changed string lastValue = string.Empty; foreach (GridViewRow gvr in ProductList.Rows) { string currentValue = gvr.Cells[sortColumnIndex].Text; if (lastValue.CompareTo(currentValue) != 0) { // there's been a change in value in the sorted column int rowIndex = gridTable.Rows.GetRowIndex(gvr); // Add a new sort header row GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex, DataControlRowType.DataRow, DataControlRowState.Normal); TableCell sortCell = new TableCell(); sortCell.ColumnSpan = ProductList.Columns.Count; sortCell.Text = string.Format("{0}: {1}", sortColumnHeaderText, currentValue); sortCell.CssClass = "SortHeaderRowStyle"; // Add sortCell to sortRow, and sortRow to gridTable sortRow.Cells.Add(sortCell); gridTable.Controls.AddAt(rowIndex, sortRow); // Update lastValue lastValue = currentValue; } } } base.Render(writer);}
首先獲得GridView控制項階層的根控制項,一個Table,用gridTable表示.另外用字串變數lastValue表示上一行的值,currentValue表示當前行的值.
注意:我獲得通過儲存格(cell)的Text屬性賦值給lastValue和currentValue,這隻能應用的繫結資料行(BoundFields),對於其它種類的列如模板列(TemplateField),複選框列(CheckBoxField)等等是不可行的,我將會在後面說明如何解決非繫結資料行的取值問題.
比較lastValue和currentValue,如果不同就要添加分界行到GridView的控制項階層中.取得當前行的索引,建立一個GridViewRow對象(它由TableCell組成)插入到GridView的控制項階層中.另外我們要把分界行格式化成單獨的一個儲存格,寬度與GridView相同,樣式與應用SortHeaderRowStyle樣式(css檔案在下面給出),顯示的文字是排序列名(如Category)和組名(如SeaFood),最後把currentValue賦值給lastValue.下面給出css代碼:
.SortHeaderRowStyle{ background-color: #c00; text-align: left; font-weight: bold; color: White;}
完成上面的代碼後,就可以對按繫結資料行排序後產生有分界行的排序介面(圖5是按supplier排序後給出的截圖).但是我現在還不能對其它種類的列(如模板列或者複選框列)產生使用者定義排序介面,如圖6所示(按Discontinue排序).
圖5:繫結資料行排序
圖6:按ChectBox列排序沒有出現分界行
按複選框列排序沒有出現分界行是的原因是代碼通過擷取TableCell的Text屬性判斷按哪一列排序的,但複選框列TableCell的Text是空的,我們要從TableCell包含的控制項取得CheckBox控制項.遇到這種類型的列,要判斷CheckBox有沒有打勾,把currentValue = gvr.Cells[sortColumnIndex].Text替換成以下代碼:
string currentValue = string.Empty;if (gvr.Cells[sortColumnIndex].Controls.Count > 0){ if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox) { if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked) currentValue = "Yes"; else currentValue = "No"; } // ... Add other checks here if using columns with other // Web controls in them (Calendars, DropDownLists, etc.) ...}else currentValue = gvr.Cells[sortColumnIndex].Text;
這段代碼判斷有沒有控制項存在排序列的TableCell中,如果有並且第一個控制項是CheckBox,按照CheckBox的Checked屬性給currentValue賦值為"Yes"或者"NO",要是綁定就把TableCell的Text屬性值賦給currentValue.同樣道理,模板列使用此方法.圖7是完善代碼按Discontinue排序後的截圖.
圖7:按CheckBox列排序出現分界行
注意:如果資料庫中的CategoryID,SupplierID或UnitPrice 為NULL,那麼在GridView將顯示為空白字串,這意味著分界行是這樣顯示"Category:",你可以設定繫結資料行的 NullDisplayText屬性更這種顯示樣式,或者在Render方法中利用currentValue設定分界行顯示的文字.
總結
GridView沒有內建自訂排序介面的功能,但我們通過一些代碼改變GridView的控制項階層就可以建立自訂排序介面,這篇文章講解如何添加分界行到可排序的GridView中,讓使用者容易的區分不同的組的分界,更多自訂排序介面例子請訪問Scott Guthrie的Blog中A Few ASP.NET 2.0 GridView Sorting Tips and Tricks一文.
祝你編程愉快!
作者簡介
Scott Mitchell,著有六本ASP/ASP.NET方面的書,是4GuysFromRolla.com的創始人,自1998年以來一直應用 微軟Web技術。Scott是個獨立的技術諮詢顧問,培訓師,作家,最近完成了將由Sams出版社出版的新作,24小時內精通ASP.NET 2.0。他的聯絡電郵為mitchell@4guysfromrolla.com,也可以通過他的部落格http://scottonwriting.net/與他聯絡。