本文英文原版及代碼下載:
http://aspnet.4guysfromrolla.com/articles/012308-1.aspx
擴展GridView控件以包含一個與排序相關的箭頭標記
導言:
在ASP.NET 2.0發表之前,我針對DataGrid控件寫了一本書以及許多的文章,雖然DataGrid依然存在於ASP.NET 2.0裏,但相較而言GridView控件提供了更多的功能及特性.它可以綁定到象SqlDataSource和ObjectDataSource這樣的數據源控件, 不用手寫代碼我們就可以執行排序、分頁、編輯、刪除等功能.
雖然DataGrid (以及GridView)支持內置的排序功能,但我們無法從感官上判斷是按哪一列來進行排序的.在系列文章的Part 18部分《An Extensive Examination of the DataGrid Web Control》,我們看到了如何對一個支持排序的DataGrid的表頭列進行update,當依據某個列進行升序或降序排序操作時,在該列上顯示一個向上或向下的箭頭標記.實現方法是這樣的,遍歷DataGrid控件的Columns collection,當該列的SortExpression值與DataGrid控件的SortExpression相匹配時,就對該列添加相應的箭頭標記.
圖1
我最近需要在一個GridView控件上實現該功能。與DataGrid示例不同,我不打算在ASP.NET頁面裏添加代碼,而是重新創建一個自定義Web server控件,來對GridView進行擴充,並添加必要的functionality.在本文,我們將探討具體的操作步驟,以及如何在ASP.NET頁面裏使用該控件.相關示例代碼在本文結尾處可下載.
GridView Sorting簡述
GridView控件實現排序很簡單,只要將AllowSorting屬性設置爲True即可.這樣,它的表頭列就呈現爲一個LinkButton.當點擊後,回傳並觸發GridView的Sorting event事件.如果GridView綁定的數據源控件支持排序,那麼GridView將在內部進行排序以及重新綁定數據.如果是通過編程的方式來將數據綁定到GridView的,那麼我們就要爲Sorting event事件創建事件處理器,自己寫代碼實現排序和重新綁定數據.當觸發Sorting event事件並重新綁定數據後,接下來GridView就觸發Sorted event事件,結束排序流程.
GridView的每列都有一個SortExpression屬性,當點擊某個列上的 LinkButton時,就指示數據按該列進行排序,同時將GridView的SortExpression屬性賦值爲該列的SortExpression,再加SortDirection屬性,GridView就有了內置的、雙向的排序功能.
下面的圖解釋了排序的流程,包括對SortExpression 和 SortDirection屬性賦值.記住,正個流程是從用戶點擊某個列的LinkButton開始的,當按要求對數據排完序後就結束了.所以,從終端用戶的角度來看,當他點擊某個表頭列的文字後,數據就按照該列進行排序了.
圖2
創建一個對GridView進行了擴展的Custom Server Control
在關於DataGrid控件系列文件的Part 18部分,我們將與箭頭標記相關的代碼放在頁面的後臺代碼類裏.具體來說,代碼思路是遍歷DataGrid的Columns collection,對每列而言,先將出現在HeaderText裏的任何<img>元素清除掉,這樣就把所有列裏的箭頭圖片都抹去了.然後,代碼檢查當前列的SortExpression是否與DataGrid控件的SortExpression值相匹配,如果是的話,就把相應的向上或向下的箭頭標記添加到該列的HeaderText的尾部.
類似的,GridView也有一個Columns collection;每列也有一個SortExpression;也有SortExpression 和 SortDirection屬性.換句話說,要對GridView添加箭頭標記的話,我們可以象操作DataGrid那樣來進行。不過在此,我打算創建一個對GridView進行了擴展的custom server control,添加必要的代碼來對必要的方法進行重寫.
爲此,我們要創建一個繼承自Web control的public class來進行擴展.我在名爲skmControls的Class Library project裏創建了一個名爲GridView的類.(在文章《Creating a TextBox Word / Character Counter Control》裏我們首先創建並探討了該skmControls2 Class Library project)
public class GridView : System.Web.UI.WebControls.GridView
{
... Override necessary GridView methods here ...
}
GridView有一個虛方法,virtual InitializeRow method.每次向GridView添加一row時都會調用該方法,包括header row.我最先想到的是重寫該方法,對header row而言,遍歷所有的列(field),並每列的HeaderText適當更新 (也就是先移除所有的image標記,再爲那個作爲排序依據的列添加恰當的向上或向下的箭頭標記).然而,在databinding階段,如果你嘗試更新GridView的field的屬性的話,該field將向GridView報告其狀態發生了改動,並要求重新綁定數據.這樣一來我們就陷入了一個死循環:
1.Databinding開始
2.對header row執行InitializeRow method方法
3.我再對header row裏每個單元(cells)的HeaderText進行更新
4.對HeaderText的更新將導致通知GridView重新綁定數據,重新返回到第1步!
因此我們需要在databinding處理過程之前或之後來修改HeaderText屬性.幾經考慮後,我意識到我只需要在完成了對數據的排序後再修改HeaderText屬性.GridView控件的OnSorted method方法將觸發Sorted event事件,當然在數據完成了排序之後纔會發生該事件.因此,我決定重寫該方法,並在該方法裏更新HeaderText屬性.
public class GridView : System.Web.UI.WebControls.GridView
{
protected override void OnSorted(EventArgs e)
{
string imgArrowUp = ...;
string imgArrowDown = ...;
foreach (DataControlField field in this.Columns)
{
// strip off the old ascending/descending icon
int iconPosition = field.HeaderText.IndexOf(@" <img border=""0"" src=""");
if (iconPosition > 0)
field.HeaderText = field.HeaderText.Substring(0, iconPosition);
// See where to add the sort ascending/descending icon
if (field.SortExpression == this.SortExpression)
{
if (this.SortDirection == SortDirection.Ascending)
field.HeaderText += imgArrowUp;
else
field.HeaderText += imgArrowDown;
}
}
base.OnSorted(e);
}
}
經過重寫的該OnSorted method方法首先爲向上和向下的箭頭圖標指定URL,我們將在後面詳細探討.接下來,對GridView的Columns collection遍歷,將每列的<img>元素刪除.再接下來,檢查當前列的SortExpression是否與GridView的SortExpression匹配,如果匹配的話,根據GridView的 SortDirection屬性的值,HTML將把向上或向下的箭頭標記顯示在該列的HeaderText裏.
一個有關HTML Encoding的問題
我在測試的時候發現一個問題,在默認的情況下,BoundField HTML會對其HeaderText的內容進行encode處理.換句話說,在處理BoundField輸出時,OnSorted method方法把<img>標籤添加到BoundField,而HTML又對其進行encode處理,分別把 < 和 > 替換爲< 和 >也就是說,它將一個HeaderText值
Price <img border="0" src="up.gif" />
轉換爲
Price <img border="0" src="up.gif" />
因此瀏覽器呈現的是:Price <img border="0" src="up.gif" />,而不是我們期望的一個圖標.
BoundField有一個HtmlEncode屬性,默認爲True,這樣HTML就把BoundField包含的整個內容進行編碼處理.因此我們想到的是將HtmlEncode屬性設置爲False,但如果我們只是不希望對錶頭進行HTML encode處理,而對data row依然進行HTML encode,哪又怎麼辦呢?
爲此,我在skmControls2 project裏創建了另一個自定義類,該類繼承自BoundField class,重寫了InitializeCell method方法,而HTML encode就是發生在這個方法裏的.只有當BoundField的HtmlEncode 和 SupportsHtmlEncode屬性都爲True時,纔會對其內容進行HTML encode處理.另外,SupportsHtmlEncode屬性是隻讀的,對BoundField而言,總是返回 True.因此我重寫了SupportsHtmlEncode屬性,它根據一個私有成員變量返回一個值.這樣一來,當初始化一個表頭單元(header cell)時,在重寫的這個InitializeCell method方法裏,我將該私有成員變量設置爲false.
public class BoundField : System.Web.UI.WebControls.BoundField
{
bool allowHtmlEncode = true;
protected override bool SupportsHtmlEncode
{
get
{
return allowHtmlEncode;
}
}
public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState,
int rowIndex)
{
if (this.HtmlEncode && cellType == DataControlCellType.Header)
{
allowHtmlEncode = false;
base.InitializeCell(cell, cellType, rowState, rowIndex);
allowHtmlEncode = true;
}
else
base.InitializeCell(cell, cellType, rowState, rowIndex);
}
}
在所有的GridView field裏,只對BoundField的數據進行HTML encode處理,所以上述的處理是專門針對BoundField而言的.對其它的TemplateField, CheckBoxField, ButtonField等而言,我們不用進行額外的處理就可以準確無誤的添加箭頭標記.爲向上或向下的箭頭指定Image URLs
那麼如何爲箭頭圖標指定URL呢?我爲此創建了2個屬性,ArrowUpImageUrl 和 ArrowDownImageUrl;當然如果開發人員嫌麻煩的話,也可以將這2個箭頭圖片鑲嵌在skmControls2裝配件裏.當開發人員沒有爲ArrowUpImageUrl 和 ArrowDownImageUrl指定值時,這是很有用的.
關於如何將資源(resources)鑲入裝配件,以及在一個ASP.NET頁面裏檢索這些資源,請參閱文章《Accessing Embedded Resources through a URL Using WebResource.axd》
在ASP.NET頁面裏使用該自定義GridView控件
在文章結尾處可下載到本文所用代碼,要使用該skmControls2控件,將DLL拷貝到 website的/Bin目錄,再在要使用該控件的.aspx頁面添加如下的@Register聲明:
<%@ Register Assembly="skmControls2" Namespace="skmControls2" TagPrefix="skm" %>
另外,你還可以在Web.config文件裏添加@Register聲明,這樣你就不用在頁面上添加了,具體方法見《Tip/Trick: How to Register User Controls and Custom Controls in Web.config》
象使用普通GridView控件那樣來使用,只不過將<asp:GridView>標籤裏的"asp"替換成"skm", 象這樣<skm:GridView>,就這麼簡單!同樣的,對BoundFields,你替換爲 <skm:BoundField>代碼裏有個頁面從Northwind數據庫的Products數據庫表裏將ProductID, ProductName, CategoryName, UnitPrice,以及Discontinued列顯示在一個支持排序的GridView控件裏,用的了一個TemplateField,一個CheckBoxField,以及多個BoundFields(也就是skmControls2 BoundFields).如下:
<skm:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataSourceID="NorthwindDataSource"
AllowSorting="True">
<Columns>
<skm:BoundField DataField="ProductID" HeaderText="ID" InsertVisible="False"
SortExpression="ProductID" />
<asp:TemplateField HeaderText="Name" SortExpression="ProductName">
<ItemTemplate>
<asp:Label runat="server" Text='<%# Bind("ProductName") %>' id="Label1" Font-Bold="True"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<skm:BoundField DataField="CategoryName" HeaderText="Category Name" SortExpression="CategoryName" />
<skm:BoundField HtmlEncode="False" DataFormatString="{0:c}" DataField="UnitPrice" HeaderText="Price"
SortExpression="UnitPrice" />
<asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
</Columns>
</skm:GridView>
注意到我們沒有在聲明代碼裏設置ArrowUpImageUrl 或 ArrowDownImageUrl屬性的值,因此,方格里使用的是默認提供的向上或向下箭頭標記(見下面的第一個截屏).如果你想使用自己提供的箭頭圖標,那麼就要顯式的指定URL值,或通過編程來指定.可以是絕對URL(比如:http://www.example.com/images/UpArrow.gif) 或相對路徑,比如: ~/Images/UpArrow.png. 在示例頁面裏包含了一個checkbox,允許你使用默認的或自己提供的箭頭圖標.
下面的2個截屏是代碼演示情況,第一個顯示的是按價格的降序排序,當然使用的是內置默認的那個向下箭頭圖標.
圖3
第二個截屏顯示的是按產品名稱來排序的,不過使用的是自己提供的箭頭標記.
圖4
當使用Fields Dialog Box時自定義的BoundField Markup會丟失
如果你使用skmControls2 library裏的自定義BoundField control的話,比如:<skm:BoundField ... />, 有一點你應該知道,當我們使用Fields dialog box來修改GridView的列時,Visual Studio會自動的把我們自定義的BoundField markup替換成默認的<asp:BoundField ... />.當我們在GridView的智能標籤裏點擊"Edit Columns"時就會彈出Fields dialog box對話框.
結語:
本文我們探討了如何構建一個自定義GridView server control(以及一個自定義BoundField control),以便展示一個箭頭標記.該箭頭標記使我們可以清楚的看到是按哪列的升序還是降序來排序的.最好將該功能封裝在一個自定義server control裏,這樣一來我們就不用在ASP.NET頁面裏寫代碼就可以實現該功能了.
祝編程快樂!