1. 引言
正如在前兩個任務中所看到的,我們使用視圖(ViewState)儲存自訂控制項屬性,ViewState實際上是一個StateBag對象,開發人員使用鍵/值的方法向視圖中儲存或讀取設定,最終發送給使用者的HTML頁面中會包含一個隱藏欄位,該隱藏欄位中儲存了經過序列化後的值。如果過分使用視圖的話,會導致頁面急劇增大,雖然現在網路頻寬已經不是限制條件,但這仍然是一個不太好的設計,因此開發人員有時會禁用檢視狀態。
對於自訂控制項來說,如果禁用檢視狀態可能導致控制項不能夠正常工作,讀者可以使用第一次任務裡開發的星級控制項,禁用頁面的檢視狀態(設定Page指令EnableViewState屬性為false)並在在頁面的載入(Page_Load)事件裡輸入如下代碼以設定分數:
protected void Page_Load(object sender, EventArgs e){ if (!IsPostBack) star.Score = 4;}
並在ASPX頁面上放置一個伺服器端按鈕以引起回傳事件,預覽該頁面並點擊按鈕,會發生什麼情況?由於提交按鈕引起回傳IsPostBack屬性為true,即不會執行分數設定作業,並且因為禁用了檢視狀態,在頁面生命週期裡運行時無法從檢視狀態中恢複分數,所以會導致沒有分數:
2. 分析
在.NET1.X版本裡,檢視狀態被作為一個整體,要不允許要麼禁止,對於控制項開發人員來說非常不方便,與控制項相關的資料放置在ViewState裡,一旦被禁用後,可能就會出問題。比較幸運的是,對於這種情況,微軟及時做出了反應。在ASP.NET2.0裡,出現了一個新的概念——控制項狀態。
控制項狀態實際上是一種特殊的檢視狀態,它仍然儲存在用戶端的隱藏欄位中,但是它並不會受檢視狀態啟用/禁用的影響,也就是說,即使將ViewState禁用,運行時仍然能正確的恢複在控制項狀態中儲存的資料。
為了使用控制項狀態,僅僅需要做額外的幾個工作:
- 向頁面註冊使用控制項狀態
- 在控制項狀態儲存事件(Control類的SaveControlState方法)中儲存相關資料
- 在控制項狀態讀取事件(Control類的LoadControlState方法)中讀取儲存的資料
需要說明的一點是,正因為控制項狀態始終都會發送到用戶端,所以將大量資料儲存到控制項狀態中顯然不是一件太好的事件,始終應該只儲存影響控制項使用的關鍵的核心的資料。
為了能夠使整頁模式狀態被禁止後控制項仍然能夠正常使用,將星級控制項的得分儲存在控制項狀態中。
3. 實現
為了組織檔案方便,建立一個新的使用控制項狀態的類。
1. 在自訂控制項解決方案的ControlLibrary類庫中添加StateStar類,像第一次任務那樣定義屬性和建立控制項層次並呈現:
using System;using System.ComponentModel;using System.Web.UI;using System.Web.UI.WebControls; namespace ControlLibrary{ public class StateStar : WebControl { private int _score; [DefaultValue(0)] public int Score { get { return _score; } set { _score = value; } } public string Comment { get { object obj = ViewState["Comment"]; return obj == null ? string.Empty : Convert.ToString(obj); } set { ViewState["Comment"] = value; } } 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); } /// <summary> /// 向儲存格中建立注釋標籤 /// </summary> /// <param name="cell">儲存格對象</param> private void CreateComment(TableCell cell) { Label lbl = new Label(); lbl.Text = Comment; cell.Controls.Add(lbl); } /// <summary> /// 向儲存格中建立星形圖案 /// </summary> /// <param name="cell"></param> 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.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 = 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); } protected override void Render(HtmlTextWriter writer) { PrepareControlForReader(); base.Render(writer); } }}
不要被這一大堆沒有注釋的代碼嚇倒,這隻是最簡單的星級控制項的實現,並且為了方便某些讀者不需要查詢以前的資料就可以快速編輯一個控制項。
2. 為了使用控制項狀態,向頁面進行註冊,而且由於在回傳事件的過程中,控制項狀態的註冊無法在請求之間進行傳遞,因此使用控制項狀態的自訂伺服器控制項必須對每個請求進行註冊,所以重寫OnInit方法:
protected override void OnInit(EventArgs e){ base.OnInit(e); Page.RegisterRequiresControlState(this);}
3. 重寫SaveControlState方法,該方法儲存自頁回傳到伺服器後發生的任何伺服器控制項狀態更改,該方法的傳回值標識了伺服器控制項的目前狀態,因此只需要將星級控制項的得分儲存到返回的object中即可(為了保持一致,仍然需要調用父類的實現):
protected override object SaveControlState(){ object[] o = new object[2]; o[0]=base.SaveControlState(); o[1] = _score; return o;}
4. 重寫LoadControlState方法,該方法從儲存的控制項狀態中恢複資料,它有一個參數表示儲存的資料,該參數是object類型,需要根據儲存時使用的類型進行正確的轉換:
protected override void LoadControlState(object savedState){ if (savedState != null) { object[] o = (object[])savedState; base.LoadControlState(o[0]); _score = Convert.ToInt32(o[1]); } }
5. 完成以後步驟後在Web網站中建立一個ASPX頁面聲明並定義StateStar控制項,將視圖禁用並添加一個引起回傳的伺服器端按鈕,像一開始那樣在頁面載入事件裡設定得分,再次預覽頁面並點擊按鈕,可以看到,得分仍然能夠正確的顯示出來:
4. 總結
在本次任務裡,我們使用控制項狀態儲存了自訂控制項的得分,使得在整頁模式被禁用時自訂控制項仍能正常用行。為了使用控制項狀態,調用了Page.RegisterRequiresControlState方法,並重調了SaveControlState和LoadControlState方法,最後需要再次說明的是控制項狀態只應該用來儲存關鍵的數字。
到今天為止,我們已經掌握了開發簡單的自訂控制項的方法,並且圍繞一個星級控制項討論了它的組織、呈現、自訂樣式、控制項狀態和如何使用特性進行定義。在下一次任務裡,我們將一起開發一個具有一些挑戰性的複雜控制項。
ASP.NET自訂控制項系列文章
前言
第一天 簡單的星級控制項
第二天 帶有自訂樣式的星級控制項
第三天 使用控制項狀態的星級控制項
第四天 摺疊面板自訂控制項
第五天 可以評分的星級控制項
第六天 可以綁定資料來源的星級控制項
第七天 開發具有豐富特性的清單控制項
第八天 顯示多個條目星級評等的資料繫結控制項
第九天 自訂GridView
第十天 實現分頁功能的DataList
全部源碼下載
本系列文章PDF版本下載