1. 引言
在上次任務裡,我們為星級控制項增加了資料繫結的特性,但是在實際運用中還會產生更多的需求,例如使用者可能希望建立一個課程列表(1):
或者在資料項目比較多的時候,能夠手動控制資料的相片順序(圖2)
本次任務中,我們將一起開發這樣的控制項。
2. 分析
以上兩個圖例中顯示的都是清單控制項,在ASP.NET2.0中ListControl類是清單控制項的父類,通過上次任務的分析可以瞭解CheckBoxList、RadioButtonList和DropDownList等控制項均繼承自ListControl類,這些清單控制項都是對於每一個資料項目重複的應用一個樣式,全如CheckBoxList對於每個清單項目顯示一個複選框,而RadioButtonList對於每個清單項目顯示一個單元框。實際上,清單控制項中的每一個清單項目都是ListItem類型的,而且為了顯示一個列表,清單控制項常常擁有每一個元素都是ListItem類型的集合,也就是我們經常用到的Items屬性,該屬性在ListControl類上定義,ListControl類還擁有許多其他非常有用的屬性:
屬性 |
描述 |
AppendDataBoundItems |
擷取或設定一個值,指示是否在綁定資料之前清除清單項目 |
DataTextField |
擷取或設定為清單項目提供常值內容的資料來源欄位 |
DataTextFormatString |
擷取或設定格式化字串,該字串用來控制如何顯示綁定到清單控制項的資料 |
DataValueField |
擷取或設定為各清單項目提供值的資料來源欄位 |
SelectedIndex |
擷取或設定列表中選定項的最低序號索引 |
SelectedItem |
擷取清單控制項中索引最小的選定項 |
SelectedValue |
擷取清單控制項中選定項的值,或挑選清單控制項中包含指定值的項 |
在以上屬性中,AppendDataBoundItems屬性是.NET2.0中新增的,該屬性在列表中已經手動定義某些項目後再執行資料繫結時特別有用。例如下拉式清單中,可能希望為使用者添加“請選擇”項目,那麼就可以使用<asp:ListItem />標記定義該清單項目,並將AppendDataBoundItems屬性設定為true,在該下拉式清單再次執行資料繫結時會保留“請選擇”項目。
對於本次任務的第一個需求,只需要使其繼承自ListControl類,並且在呈現時將每一個資料項目輸出為一個超連結即可。但是該清單控制項有一些局限,它只能採用ListItem類作為資料項目物件類型,如果希望修改或添加清單項目屬性時,此類清單控制項就無能為力了,為了實現這種目的(例如第二個需求),需要做一些額外的工作。
接下來考慮第二個需求,需要在資料繫結的基礎上為自訂控制項增加按照指定的方向呈現資料項目的能力,這有點像DataList控制項,可以通過設定RepeatDirection、RepeatColumns等屬性實現上述功能。對於此類問題,.NET為我們提供了IRepeatInfoUser介面,該介面包含了以下成員的聲明:
屬性 |
描述 |
HasFooter屬性 |
擷取一個值,指示清單控制項是否包含腳註部分 |
HasHeader屬性 |
擷取一個值,指示清單控制項是否包含標題區段 |
HasSeparators屬性 |
擷取一個值,指示清單控制項是否包含清單項目之間的分隔字元 |
RepeatedItemCount屬性 |
擷取清單控制項中的項數 |
GetItemStyle方法 |
檢索清單控制項中指定索引位置的指定項類型的樣式 |
RenderItem |
用指定的資訊呈現列表中的項 |
為了實現新特性,使自訂控制項實現IRepeatInfoUser介面,重要的是該介面中的RenderItem方法,每個繫結資料項都自動調用此方法,此方法簽名如下所示:
void RenderItem ( ListItemType itemType, int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer)
其參數說明如下:
參數 |
描述 |
itemType |
ListItemType枚舉值之一,該枚舉指定清單控制項中項的類型 |
repeatIndex |
指定清單控制項中項的位置的序號索引 |
repeatInfo |
RepeatInfo類型對象,表示用於呈現列表中的項的資訊 |
writer |
System.Web.UI.HtmlTextWriter類執行個體,表示用於在用戶端轉譯HTML內容的輸出資料流 |
最後如果控制項想要支援特殊的布局功能,還需要使用特殊的代碼重寫自訂控制項的Render方法,在該方法中使用RepeatInfo類的RenderPrpeater方法呈現控制項,以下是該方法的定義:
public void RenderRepeater ( HtmlTextWriter writer, IRepeatInfoUser user, Style controlStyle, WebControl baseControl)
其參數說明如下:
參數 |
描述 |
writer |
System.Web.UI.HtmlTextWriter類執行個體,表示用於在用戶端轉譯HTML內容的輸出資料流 |
user |
一個實現了IRepeatInfoUser介面的對象,表示要呈現的控制項 |
controlStyle |
表示顯示項目所採用的樣式 |
baseControl |
複製基屬性的控制項 |
根據以上分析,編寫兩個自訂控制項實現資料項目的輸出。
3. 實現簡單清單控制項
3.1 在解決方案的ControlLibrary類庫中建立SimpleHyperLinkList類,並引入必要命名空間:
using System.Web.UI;using System.Web.UI.WebControls; namespace ControlLibrary{ [ToolboxData("<{0}:SimpleHyperLinkList runat=\"server\"></{0}>")] public class SimpleHyperLinkList : ListControl { }}
3.2 重寫控制項的Render方法,在呈現時迭代訪問資料項目以呈現多個連結控制項,此處調用了RenderControl方法,該方法在Control類上定義,用於將伺服器控制項的內容輸入到所提供的HtmlTextWriter對象中:
protected override void Render(HtmlTextWriter writer){ HyperLink link = new HyperLink(); for (int i = 0; i < Items.Count; i++) { link.ApplyStyle(ControlStyle); link.Text = Items[i].Text; link.NavigateUrl = Items[i].Value; link.RenderControl(writer); writer.Write("<br />"); }}
這個類的代碼很簡單,唯一比較有趣的地方是類使用了ToolboxData特性(對應System.Web.UI.ToolboxDataAttribute類),該特性指定了將控制項從視覺化設計工具的工具箱中拖放到設計頁面上時預設產生的標記,在需要為自訂控制項指定屬性的初始值時該特性很有用。
3.3 在ASPX頁面中聲明並定義控制項,瀏覽運行結果。
4. 實現豐富特性清單控制項
4.1 在ControlLibrary類庫中建立HyperLinkItem實體類,用於儲存最終產生的每一個連結的文本、注釋和地址字串:
namespace ControlLibrary{ public class HyperLinkItem { public HyperLinkItem() { Text = string.Empty; Tooltip = string.Empty; Url = string.Empty; } public HyperLinkItem(string text, string tooltip, string url) { Text = text; Tooltip = tooltip; Url = url; } public string Text { get; set; } public string Tooltip { get; set; } public string Url { get; set; } }}
4.2 在類庫中建立HyperLinkItemCollection集合類,該類繼承了Collection泛型集合類,以確保該集合中只參添加HyperLinkItem類型對象,並且為了手動管理檢視狀態,該類實現了IStateManager介面:
using System.Collections.ObjectModel;using System.Web.UI; namespace ControlLibrary{ public class HyperLinkItemCollection:Collection<HyperLinkItem>,IStateManager { private bool marked; public HyperLinkItemCollection() { marked = false; } public bool IsTrackingViewState { get { return this.marked; } } public void TrackViewState() { marked = true; } public void LoadViewState(object state) { if (state == null) return; Clear(); Triplet trip = (Triplet)state; string[] rgTooltip = (string[])trip.First; string[] rgText = (string[])trip.Second; string[] rgUrl = (string[])trip.Third; for (int i = 0; i < rgUrl.Length; i++) { Add(new HyperLinkItem(rgText[i], rgTooltip[i], rgUrl[i])); } } public object SaveViewState() { int num = Count; object[] rgTooltip = new string[num]; object[] rgText = new string[num]; object[] rgUrl = new string[num]; for (int i = 0; i < num; i++) { rgTooltip[i] = Items[i].Tooltip; rgText[i] = Items[i].Text; rgUrl[i] = Items[i].Url; } return new Triplet(rgTooltip, rgText, rgUrl); } }}
以上代碼稍微有些複雜的就是檢視狀態的儲存了讀取,為了保證將某一個HyperLinkItem的三個屬性全部儲存至檢視狀態中,定義了三個object數組最終儲存到Triplet對象中(讀取時仍遵循此規則)。
4.3 向類庫中添加HyperLinkList類,為了使該類具有更豐富的展示方式,仍然繼承自DataBindControl類並實現IRepeatInfoUser介面:
using System.Collections;using System.Web.UI;using System.Web.UI.WebControls; namespace ControlLibrary{ [ToolboxData("<{0}:HyperLinkList runat=\"server\"></{0}>")] public class HyperLinkList:DataBoundControl,IRepeatInfoUser { }}
4.4 定義控制項內部使用的重複項屬性和暴露出來的集合屬性,在HyperLinkList類中編寫如下代碼:
private HyperLinkItemCollection items;private HyperLink controlToRepeat; private HyperLink ControlToRepeat{ get { if (controlToRepeat == null) { controlToRepeat = new HyperLink(); } return controlToRepeat; }} public virtual HyperLinkItemCollection Items{ get { if (items == null) items = new HyperLinkItemCollection(); if (base.IsTrackingViewState) items.TrackViewState(); return items; }}
4.5 定義資料來源映射屬性DataTextField、DataValueField和DataTooltipField以從資料來源中讀取相應的資料填充重複項:
public virtual string DataTextField{ get { object o = ViewState["DataTextField"]; return o == null ? string.Empty : (string)o; } set { ViewState["DataTextField"] = value; }} public virtual string DataTooltipField{ get { object o = ViewState["DataTooltipField"]; return o == null ? string.Empty : (string)o; } set { ViewState["DataTooltipField"] = value; }} public virtual string DataUrlField{ get { object o = ViewState["DataUrlField"]; return o == null ? string.Empty : (string)o; } set { ViewState["DataUrlField"] = value; }}
4.6 重寫PerformDataBinding方法根據映射從資料來源讀取資料:
protected override void PerformDataBinding(IEnumerable data){ base.PerformDataBinding(data); string urlField = DataUrlField; string textField = DataTextField; string tooltipField = DataTooltipField; if (data != null) { foreach (object o in data) { HyperLinkItem item = new HyperLinkItem(); item.Url = DataBinder.GetPropertyValue(o, DataUrlField,null); item.Text = DataBinder.GetPropertyValue(o, DataTextField,null); item.Tooltip = DataBinder.GetPropertyValue(o, DataTooltipField,null); Items.Add(item); } }}
4.7 重寫SaveViewState和LoadViewState方法儲存或讀取資料項目集合:
protected override object SaveViewState(){ object o= base.SaveViewState(); Pair p = new Pair(o, Items.SaveViewState()); return p;} protected override void LoadViewState(object savedState){ if (savedState==null) return; Pair p = (Pair)savedState; base.LoadViewState(p.First); Items.LoadViewState(p.Second);}
4.8 定義RepeatDirection、RepeatColumns和RepeatLayout屬性供RepeatInfo對象使用以控制重複項的呈現:
public virtual RepeatDirection RepeatDirection{ get { object o = ViewState["RepeatDirection"]; return o == null ? RepeatDirection.Vertical : (RepeatDirection)o; } set { ViewState["RepeatDirection"] = value; }} public virtual int RepeatColumns{ get { object o = ViewState["RepeatColumns"]; return o == null ? 0 : (int)o; } set { ViewState["RepeatColumns"] = value; }} public virtual RepeatLayout RepeatLayout{ get { object o = ViewState["RepeatLayout"]; return o == null ? 0 : (RepeatLayout)o; } set { ViewState["RepeatLayout"] = value; }}
4.9 實現IRepeatInfoUser介面相關成員,此處為了方便,並沒有增加新的特性:
bool IRepeatInfoUser.HasFooter{ get { return false; }} bool IRepeatInfoUser.HasHeader{ get { return false; }} bool IRepeatInfoUser.HasSeparators{ get { return false; }} Style IRepeatInfoUser.GetItemStyle(ListItemType type, int repeatIndex){ return null;}
4.10 實現IRepeatInfoUser介面的RepeatedItemCount屬性,返回當前輸出項的數目:
int IRepeatInfoUser.RepeatedItemCount{ get { return this.Items.Count; }}
4.11 實現IRepeatInfoUser介面的RenderItem方法以呈現資料項目,該方法調用了ControlToRepeat私人屬性以得到一個超連結:
void IRepeatInfoUser.RenderItem(ListItemType type, int repeatIndex, RepeatInfo repeatInfo, HtmlTextWriter writer){ HyperLink link = ControlToRepeat; int i = repeatIndex; link.ID = i.ToString(); link.Text = Items[i].Text; link.NavigateUrl = Items[i].Url; link.ToolTip = Items[i].Tooltip; link.RenderControl(writer);}
4.12 最後重寫Render方法呈現控制項,根據分析,這裡使用RepeatInfo類:
protected override void Render(HtmlTextWriter writer){ if (Items.Count > 0) { RepeatInfo ri = new RepeatInfo(); Style controlStyle = base.ControlStyleCreated ? base.ControlStyle : null; ri.RepeatColumns = RepeatColumns; ri.RepeatDirection = RepeatDirection; ri.RepeatLayout = RepeatLayout; ri.RenderRepeater(writer, this, controlStyle, this); }}
4.13 建立測試ASPX頁面,聲明並定義自訂控制項,預覽運行結果。
5. 總結
本次任務裡,我們編寫了一個繼承自ListControl類的簡單清單控制項,在此基礎上,我們再次開發了一個繼承自DataBindControl類,並且為了實現豐富的布局功能,還實現了IRepeatInfoUser類,在呈現方法中,通過RepeatInfo類按照定義格式輸入資料項目。在下一個任務裡,我們將開始一個稍複雜的看起來和GridView有些相似的組合資料繫結控制項以顯示一系列星級評等的列表。
ASP.NET自訂控制項系列文章
前言
第一天 簡單的星級控制項
第二天 帶有自訂樣式的星級控制項
第三天 使用控制項狀態的星級控制項
第四天 摺疊面板自訂控制項
第五天 可以評分的星級控制項
第六天 可以綁定資料來源的星級控制項
第七天 開發具有豐富特性的清單控制項
第八天 顯示多個條目星級評等的資料繫結控制項
第九天 自訂GridView
第十天 實現分頁功能的DataList
全部源碼下載
本系列文章PDF版本下載