1. 引言
以上幾個任務裡,我們建立了一些簡單但很實用的自訂控制項,但是它們只能按照固定的設定進行呈現,缺少一些關鍵的特徵——資料繫結和有時為了更靈活的控制以支援模版設定。在ASP.NET資料繫結控制項分為三種:
- 簡單資料繫結:簡單資料繫結將一個對象與某個控制項的屬性綁定在一起。資料來源只是綁定單個資料項目,而不是綁定一個資料項目列表。簡單資料繫結使用資料繫結運算式完成,資料繫結運算式是用<%#...%>封裝的任何可執行代碼。
- 清單控制項:清單控制項是通過一個固定不變的使用者介面顯示一個資料項目列表的控制項。常見的清單控制項包含RadioButtonList控制項、CheckBoxList控制項和ASP.NET2.0中新引入的BulletedList控制項。
- 複雜資料繫結:複雜資料繫結控制項通常是顯示一組資料項目的群組控制項,它們有著靈活的呈現機制,例如GridView控制項就是一個複雜資料繫結控制項。
為了使星級控制項在使用時能夠通過某個資料來源顯示資料,需要使該控制項擁有資料繫結的能力,使用時資料繫結方法代碼看起來可能如下所示:
private void BindData(){ DataTable table = new DataTable(); DataColumn col = new DataColumn("Comment",typeof(string)); table.Columns.Add(col); col = new DataColumn("Score",typeof(int)); table.Columns.Add(col); DataRow row = table.NewRow(); row[0] = "Vista"; row[1] = 3; table.Rows.Add(row); table.AcceptChanges(); star.DataSource = table; star.DataTextField = "Comment"; star.DataValueField = "Score"; star.DataBind();}
2. 分析
在開始清單控制項之前再來討論一下簡單資料繫結,前幾次開發的星級控制項就是一個簡單資料繫結控制項,我們直接可以為他的某個屬性使用資料繫結運算式賦值,例如在StartTest.aspx中編寫如下代碼使用自訂控制項:
<cc:Star ID="star" runat="server" Score="4" Comment="<%#DateTime.Now%>" Font-Size="12px" />
在頁面中預覽——很不巧,當前日期並沒有顯示在頁面上,這是由於在頁面中定義的任何資料繫結運算式,只有在調用DataBound方法之後才會進行計算。我們有多種選擇,既可以調用頁面對象(Page)的DataBind方法,也可以調用具體控制項上的DataBind方法,實際上,如果調用頁面對象上的DataBind方法,它將遞迴的調用頁面中定義的所有控制項上的DataBind方法。這就意味著,如果頁面上使用了多個資料繫結運算式,最好還是通過調用Page.DataBind方法執行資料繫結。
如果想瞭解在資料繫結時底層究竟執行了什麼操作,可以開啟ASP.NET的調試功能,並改變臨時檔案目錄,修改web.config中complcation配置節如下所示:
<compilation debug="true" tempDirectory="c:\web"> …… ……</complation>
以上配置啟動了調試並將臨時目錄設定為C:\Web(如果不設定tempDirectory屬性臨時檔案將放置在預設%Windows%\Microsoft.NET\Framework\%Version%\Temporary ASP.NET Files目錄下)。在瀏覽器預覽StarTest.aspx頁面後可以在臨時目錄中發現StarTest兩個部分類別定義,其中一個定義了後置代碼,另一個用於產生ASPX頁面。在產生ASPX頁面的類檔案中可以找到@__BuildControlstar方法(實際上還有一系列的@__BuildControlXXX方法,XXX對應於控制項的ID),在該方法中包含如下代碼:
@__ctrl.DataBinding += new System.EventHandler(this.@__DataBindingstar);
相應的,可以在此檔案中找到@__DataBindingstar方法,該方法如下所示:
public void @__DataBindingstar(object sender, System.EventArgs e) { ControlLibrary.Star dataBindingExpressionBuilderTarget; System.Web.UI.Page Container; dataBindingExpressionBuilderTarget = ((ControlLibrary.Star)(sender)); Container = ((System.Web.UI.Page)(dataBindingExpressionBuilderTarget.BindingContainer)); #line 13 "E:\Documents and Settings\holywolf\My Documents\Books\????\??????????\ControlSolution\Web\StarTest.aspx" dataBindingExpressionBuilderTarget.Comment = System.Convert.ToString(DateTime.Now, System.Globalization.CultureInfo.CurrentCulture); #line default #line hidden}
如果資料繫結運算式不匹配期望的類型,則通常會得到一個編譯錯誤,然而,如果期望的類型是string,則解析器通過Convert.ToString方法實現標準轉換。
接下來討論如何使星級控制項擁有資料繫結的能力,首先我們來觀查ASP.NET2.0中資料繫結類階層:
可以看到,所有類都繼承自BaseDataBoundControl類,該類是ASP.NET2.0資料繫結控制項的基類。BasedataBoundControl類上定義了如何進行資料繫結和如何驗證所繫結資料的方式,並且該類上還定義了兩個資料來源屬性:用於可枚舉資料的DataSource屬性和用於資料來源控制項的DataSourceID屬性。
DataSource屬性接受一個實現了IEnumerable介面(例如集合)或IListSource介面(例如DataTable)的對象,設定該屬性後,必須調用該控制項或頁面的DataBind方法才會真正的將資料填充的控制項中。
DataBoundControl類繼承自BaseDataBoundControl類,該類中提供了一個重要的方法PerformDataBinding方法,在編寫派生自DataBoundControl類的自訂控制項時,需要編寫此方法實現以載入資料,該方法定義如下:
protected virtual void PerformDataBinding(IEnumerable data){ …… ……}
在建立自訂資料繫結控制項時,一般需要處理以下內容:
- 定義相關的DataXXXField指定綁定到資料來源映射
- 添加資料項目屬性並手動管理檢視狀態以獲得更高的效率
- 重載PerformDataBinding方法從資料來源中讀取資料並緩衝到資料項目中
- 根據資料項目中儲存的值正確的呈現控制項
根據以上分析,可以在修改原有星級控制項,使之繼承自DataBoundControl以獲得從資料來源讀取資料的能力。
3. 實現
3.1 向解決方案中ControlLibrary類庫中添加StarDataItem類作為儲存資料的資料項目,為了能夠管理檢視狀態,該類實現了IStateManager介面:
using System;using System.Web.UI; namespace ControlLibrary{ public class StarDataItem:IStateManager { }}
3.2 在該類中添加得分和注釋屬性並添加建構函式:
public string Comment{ get; set;} public int Score{ get; set;} public StarDataItem(){ Comment = string.Empty; Score = 0;} public StarDataItem(string comment, int score){ Comment = comment; Score = score;}
3.3 實現IStateManager介面相關方法以管理檢視狀態:
private bool _mark; public bool IsTrackingViewState{ get { return _mark; }} public object SaveViewState(){ Pair p = new Pair(Comment, Score); return p;} public void LoadViewState(object savedState){ if (savedState != null) { Pair p = (Pair)savedState; Comment = Convert.ToString(p.First); Score = Convert.ToInt32(p.Second); }} public void TrackViewState(){ _mark = true;}
3.4 添加DataBoundStar類,該類繼承了DataBoundControl類:
using System;using System.Collections;using System.Web.UI;using System.Web.UI.WebControls; namespace ControlLibrary{ public class DataBoundStar : DataBoundControl { }}
3.5 在該類中定義資料項目屬性(StarDataItem類型):
private StarDataItem _data; public StarDataItem DataItem{ get { if (_data == null) _data = new StarDataItem(); if (IsTrackingViewState) _data.TrackViewState(); return _data; }}
3.6 為類增加DataTextField和DataValueField屬性定義與資料來源的映射:
public virtual string DataValueField{ get { object o = ViewState["DataValueField"]; return o == null ? string.Empty : (string)o; } set { ViewState["DataValueField"] = value; }} public virtual string DataTextField{ get { object o = ViewState["DataTextField"]; return o == null ? string.Empty : (string)o; } set { ViewState["DataTextField"] = value; }}
3.7 重寫PerformDataBinding方法,根據映射從資料來源中讀取資料填充到資料項目中:
protected override void PerformDataBinding(IEnumerable data){ if (data == null) return; IEnumerator e = data.GetEnumerator(); e.MoveNext(); if (!string.IsNullOrEmpty(DataTextField)) DataItem.Comment = Convert.ToString(DataBinder.GetPropertyValue(e.Current, DataTextField)); if (!string.IsNullOrEmpty(DataValueField)) DataItem.Score = Convert.ToInt32(DataBinder.GetPropertyValue(e.Current, DataValueField));}
該方法接收IEnumerable類型的參數用於迭帶訪問資料來源中的資料,在讀取資料來源中的資料時使用了DataBinder類,該類上有一個實用的GetPropertyValue方法,用於根據屬性反射的讀取資料來源中的值,此處我們使用該方法並傳遞了DataTextField和DataValueField以讀取注釋和評分。
3.8 重寫CreateChildControls方法,調用CreateControlHierarchy方法以建立子控制項層次:
protected override void CreateChildControls(){ base.CreateChildControls(); CreateControlHierarchy();} protected virtual void CreateControlHierarchy(){ Table table = new Table(); TableRow row = new TableRow(); table.Rows.Add(row); TableCell comment = new TableCell(); CreateComment(comment); row.Cells.Add(comment); TableCell stars = new TableCell(); CreateStars(stars); row.Cells.Add(stars); this.Controls.Add(table);}
3.9 如上所示在建立子控制項時調用了CreateComment和CreateStars方法,用於建立注釋和星形圖案(仍然使用了資源檔),並且相關資料均由DataItem屬性讀取,以下是這兩個方法的實現:
private void CreateComment(TableCell cell){ Label lbl = new Label(); lbl.Text = DataItem.Comment; cell.Controls.Add(lbl);} private void CreateStars(TableCell cell){ string starPath = Page.ClientScript.GetWebResourceUrl(this.GetType(), "ControlLibrary.Image.stars.gif"); Panel panBg = new Panel(); panBg.Style.Add(HtmlTextWriterStyle.Width, "80px"); panBg.Style.Add(HtmlTextWriterStyle.Height, "16px"); panBg.Style.Add(HtmlTextWriterStyle.TextAlign, "left"); panBg.Style.Add(HtmlTextWriterStyle.Overflow, "hidden"); panBg.Style.Add(HtmlTextWriterStyle.Position, "relative"); panBg.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath); panBg.Style.Add("background-position", "0px -32px"); panBg.Style.Add("background-repeat", "repeat-x"); cell.Controls.Add(panBg); Panel panCur = new Panel(); string width = DataItem.Score * 16 + "px"; panCur.Style.Add(HtmlTextWriterStyle.Width, width); panCur.Style.Add(HtmlTextWriterStyle.Height, "16px"); panCur.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath); panCur.Style.Add("background-position", "0px 0px"); panCur.Style.Add("background-repeat", "repeat-x"); panBg.Controls.Add(panCur);}
3.10 重寫SaveViewState和LoadViewState方法將DataItem資料項目儲存到檢視狀態中並能夠正確的恢複:
protected override object SaveViewState(){ object o= base.SaveViewState(); Pair p = new Pair(); p.First = o; p.Second = DataItem.SaveViewState(); return p;} protected override void LoadViewState(object savedState){ if (savedState != null) { Pair p = (Pair)savedState; base.LoadViewState(p.First); DataItem.LoadViewState(p.Second); }}
3.11 最後重寫Render方法呈現控制項:
protected override void Render(HtmlTextWriter writer){ PrepareControlForReader(); base.Render(writer);} private void PrepareControlForReader(){ if (this.Controls.Count < 1) return; Table table = (Table)this.Controls[0]; table.CellSpacing = 0; table.CellPadding = 0;}
3.12 在Web網站中添加頁面,聲明並定義自訂控制項:
<cc:DataBoundStar ID="star" runat="server" />
3.13 編寫類似於本次任務開始的代碼以執行資料繫結,並在瀏覽器預覽結果。
4. 總結
本次任務裡我們再次為星級控制項增加了新的特性——資料繫結,為了實現這個目的,自訂控制項繼承了DataBoundControl類,實現了其中的一個重要方法PerformDataBinding,通過該方法的IEnumerable參數迭代訪問資料來源,並調用DataBinder.GetPropertyValue方法擷取資料來源映射的值,最後為了更合理的儲存資料,定義了StarDataItem類作為自訂控制項的屬性。在下次任務裡,我們將一起開發一個清單控制項。
附本文中使用到的背景圖片:
ASP.NET自訂控制項系列文章
前言
第一天 簡單的星級控制項
第二天 帶有自訂樣式的星級控制項
第三天 使用控制項狀態的星級控制項
第四天 摺疊面板自訂控制項
第五天 可以評分的星級控制項
第六天 可以綁定資料來源的星級控制項
第七天 開發具有豐富特性的清單控制項
第八天 顯示多個條目星級評等的資料繫結控制項
第九天 自訂GridView
第十天 實現分頁功能的DataList
全部源碼下載
本系列文章PDF版本下載