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類做爲自定義控件的屬性。在下次任務裏,咱們將一塊兒開發一個列表控件。