1. 引言
在上個任務裡,通過繼承WebControl類建立了一個簡單的星級控制項,並且也可以設定字型邊框等相關樣式,但是需求馬上又來了,如果我們想加入自訂的樣式,例如希望文本可以自由顯示到圖案左邊或下邊,如所示,對於這樣的要求怎麼辦?
2. 分析
看到上方的圖形,很容易想到為第一個任務裡的Star類添加一個屬性,標識是在左面還是下面顯示文本,這的確能夠解決現有的問題,但如果過一陣子希望能夠設定標籤文本的顏色怎麼辦,很容易,再添加一個屬性,具有敏銳眼光的讀者一定想到,這不是解決問題的最好方法,因為可能會陸續提出新的樣式的要求,那麼比較合理的解決方案是單獨添加一個樣式屬性,該屬性持有一個樣式集合,通過修改這個樣式集合可以隨時添加新的外觀特徵,最後再呈現控制項的時候只需要使用第一個儲存格應用該樣式就可以了。
在.NET中,System.Web.UI.WebControls.Style類封裝了Web伺服器外觀的屬性,例如可以設定背景色、前景色彩或邊框等,TableStyle類和TableItemStyle類均擴充了Style類,從它們的名字就可以想到TableStyle類用於設定表格控樣的樣式,而TableItemStyle類用於設定TableRow或TableCell的樣式,它們之間的關係:
對於本次任務來說,我們要設定某個儲存格的樣式,並且添加了自訂特性,因此選擇繼承TableItemStyle類即可。
確定為Star類添加一個擴充了TableItemStyle類的屬性後,樣式的問題解決了,接下來要考慮的就是如何儲存樣式如性,像上次任務裡直接儲存到ViewState中嗎,還是有更好的選擇。
很顯然,ViewState不能直接儲存某個對象,需要進行序列化操作後才可以進行,預設情況下使用的是BinaryFormatter作為對象的序列化器,該類會把對象所有的成員全部序列化,而不管是公有的或私人的,這樣做的話可能導致效率不是很高,為了自己控制屬性的儲存,需要繼承IStateManager介面,該介面定義任何類為支援伺服器控制項的檢視狀態管理而必須實現的屬性和方法,其定義的成員有:
成員 |
描述 |
IsTrackingViewState屬性 |
擷取一個值,批示伺服器控制項是否正在跟蹤其檢視狀態更改 |
LoadViewState方法 |
載入伺服器控制項以前儲存的檢視狀態 |
SaveViewState方法 |
將伺服器控制項的檢視狀態儲存到Object |
TrackViewState方法 |
指示伺服器控跟蹤其檢視狀態更改 |
根據以上分析,為了實現需求需要定義一個繼承自TableItemStyle並實現IStateManager介面的樣式類TextItemStyle,在該類中增加DisplayTextAtBottom屬性定義是否在底部顯示註冊文本,並在上次任務中開發的Star類裡增加類型為TextItemStyle的屬性以設定樣式。
3. 實現
1. 在上次任務解決方案的ControlLibrary類庫中添加TextItemStyle類,該類繼承TableItemStyle類並實現了IStateManager介面:
public class TextItemStyle:TableItemStyle,IStateManager{}
2. 在該類中定義布爾類型的DisplayTextAtBottom屬性,使用一個私人變數儲存該屬性設定;當該屬性修改時需要通知Star類中的樣式屬性以應用新的樣式,所以需要使用NotifyParentProperty屬性修飾:
private bool _displayTextAtBottom; [NotifyParentProperty(true)]public bool DisplayTextAtBottom{ get { return _displayTextAtBottom; } set { _displayTextAtBottom = value; }}
3. 實現IStateManager中的IsTrackingViewState屬性和TrackViewState方法,由於在這兩個成員裡不需要額外的動作,所以只需要簡單的訪問父類成員即可:
bool IStateManager.IsTrackingViewState{ get { return base.IsTrackingViewState; }} void IStateManager.TrackViewState(){ base.TrackViewState();}
4. 實現IStateManager介面SaveViewState方法以儲存自訂屬性:
object IStateManager.SaveViewState(){ Pair p = new Pair(); p.First=base.SaveViewState(); p.Second = _displayTextAtBottom; return p;}
|
這裡使用了System.Web.UI.Pair類用於儲存兩個相關的對象,與此類似的還是System.Web.UI.Triplet類,用於儲存三個相關的對象。需要儲存更多的資料時,可以考慮定義一個數組。 |
5. 實現IStateManager介面LoadViewState方法,從儲存的視圖資料中恢複必要的設定:
void IStateManager.LoadViewState(object savedState){ if (savedState != null) { Pair p = (Pair)savedState; base.LoadViewState(p.First); _displayTextAtBottom = Convert.ToBoolean(p.Second); }}
|
可以看到TextItemStyle中對介面的實現是通過介面名稱.成員名稱實現的,即所謂的顯式繼承,採用這種繼承方向,不能直接通過類調用介面方法,而必須將類轉換為介面類型後方可調用。 |
6. 在Start類中添加樣式屬性,並調用介面方法進行視圖操作:
private TextItemStyle _textStyle; [PersistenceMode(PersistenceMode.InnerProperty)]public TextItemStyle TextStyle{ get { if (_textStyle == null) _textStyle = new TextItemStyle(); if (IsTrackingViewState) ((IStateManager)_textStyle).TrackViewState(); return _textStyle; } set { _textStyle = value; }}
該屬性使用PersitenceMode標識,該類只是通知Visual Studio 2005,使用aspx源檔案中的一個嵌入標記來永久儲存該風格的內容。
7. 修改CreateControlHierarchy方法,根據樣式屬性設定決定是否建立新行:
protected virtual void CreateControlHierarchy(){ Table table = new Table(); TableRow row = new TableRow(); table.Rows.Add(row); TableCell stars = new TableCell(); CreateStars(stars); TableCell comment = new TableCell(); CreateComment(comment); if (TextStyle.DisplayTextAtBottom) { row.Cells.Add(stars); TableRow text = new TableRow(); text.Cells.Add(comment); table.Rows.Add(text); } else { row.Cells.Add(comment); row.Cells.Add(stars); } this.Controls.Add(table);}
8. 最後修改呈現方法PrepareControlForRender,將樣式應用到文字儲存格上:
private void PrepareControlForReader(){ if (this.Controls.Count < 1) return; Table table = (Table)this.Controls[0]; table.CellSpacing = 0; table.CellPadding = 0; TableCell cell = null; if (TextStyle.DisplayTextAtBottom) { cell = table.Rows[1].Cells[0]; } else { cell = table.Rows[0].Cells[0]; } cell.ApplyStyle(TextStyle);}
9. 在網站中聲明並定義控制項:
<cc:StyleStar ID="star" runat="server" Score="4" Comment="Windows XP"> <TextStyle ForeColor="Red" DisplayTextAtBottom="true" /> </cc:StyleStar>
瀏覽運行結果。
雖然我們在TextItemStyle類中定義了儲存和閱讀檢視狀態的方法,但是在回傳時能夠正常工作嗎,嘗試在頁面的PageLoad方法裡設定樣式的背景色為紅色:
if (!IsPostBack) star.TextStyle.BackColor=System.Drawing.Color.Red;
接下來在頁面中添加一個伺服器端按鈕,瀏覽頁面並點擊提交按鈕,會出現怎樣的結果?可以看到紅色背景丟失了,這是由於雖然我們定義了樣式類屬性儲存的方法,但它還沒有真正的參與到整頁模式讀寫過程中,為此,需要重寫Start類的SaveViewState和LoadViewState方法,指定如何將資料儲存到檢視狀態中,以及如何從檢視狀態中恢複。
protected override object SaveViewState(){ Pair p = new Pair(); p.First=base.SaveViewState(); p.Second = ((IStateManager)TextStyle).SaveViewState(); return p;} protected override void LoadViewState(object savedState){ if (savedState != null) { Pair p = (Pair)savedState; base.LoadViewState(p.First); ((IStateManager)TextStyle).LoadViewState(p.Second); }}
編譯解決方案後再次預覽頁面,並點擊提交按鈕,可以看到.NET已經協助我們正確的從檢視狀態中恢複資料了。
4. 總結
本次任務中為星形控制項增加了自訂樣式,並自訂視圖操作狀態以更高效的儲存和讀取相關資料。在定義屬性時候使用了NotifyParentProperty特性和PersistenceMode特性分別用來在屬性發生更改時通知父屬性和將屬性使用嵌入標記來儲存。可能您會突然想到,如果使用者將整頁模式狀態禁止後會產生什麼樣的結果,某些屬性還能正確設定嗎,下一次任務裡我們將討論這個問題。
ASP.NET自訂控制項系列文章
前言
第一天 簡單的星級控制項
第二天 帶有自訂樣式的星級控制項
第三天 使用控制項狀態的星級控制項
第四天 摺疊面板自訂控制項
第五天 可以評分的星級控制項
第六天 可以綁定資料來源的星級控制項
第七天 開發具有豐富特性的清單控制項
第八天 顯示多個條目星級評等的資料繫結控制項
第九天 自訂GridView
第十天 實現分頁功能的DataList
全部源碼下載
本系列文章PDF版本下載