先看一下我們期望做出的效果
在網頁中,如果有使用SuccessTemplate樣板的話,顯示原來網頁上的內容。
<cc1:TheControl ID="TheControl1" runat="server" ><SuccessTemplate runat="server" >This is the success template.</SuccessTemplate></cc1:TheControl >
呈現出來的效果如下:
在網頁中,如果"沒有"使用SuccessTemplate樣板的話,使用內定的DefaultSuccessTemplate樣板內容。
呈現出來的效果如下:<cc1:TheControl ID="TheControl2" runat="server" />
控制項間的結構如下:
控制項間的結構與網頁上標籤結構上有一些不同,在於我們並沒有在網頁上看到SuccessContainer這個容器,而是直接看到SuccessTemplate。
MSDN上說『宣告其容器控制項的基底型別』就是這個意思。
核心的步驟可以簡化如下:
CreateChildControls =>
CreateSuccessViewControls =>
建立空樣板 =>
建立樣板容器 =>
決定使用哪一種樣板 =>
將樣板放入容器內,並建立子控制項 =>
將容器放入控制項中
程式碼已改善MSDN範例不易擴充的問題,重新改寫如下:
[ToolboxData("<{0}:TheControl runat=server></{0}:TheControl>")][ParseChildren(true)]public class TheControl : Control, INamingContainer{private ITemplate _successTemplate;//InnerProperty:指定該屬性保存在 ASP.NET 伺服器控制項中做為巢狀標記。//TemplateContainer:宣告其容器控制項的基底型別//注意:預設的樣板行為由TemplateBuilder來決定[PersistenceMode(PersistenceMode.InnerProperty)][TemplateContainer(typeof(TheControl))]public virtual ITemplate SuccessTemplate{get { return this._successTemplate; }set{this._successTemplate = value;base.ChildControlsCreated = false;}}private SuccessContainer _successContainer;//注意:樣板容器(SuccessContainer)只有定義樣板使用的"資料",樣板行為由樣板(DefaultSuccessTemplate)決定internal sealed class SuccessContainer : Control,INamingContainer{private Literal _title;internal Literal Title{get { return this._title; }set { this._title = value; }}public SuccessContainer(TheControl owner) { }}//如果使用者沒有使用樣板的話,使用這個內定的樣板//注意:樣板行為由樣板(DefaultSuccessTemplate)決定,樣板容器(SuccessContainer)只有定義樣板使用的"資料"private sealed class DefaultSuccessTemplate : ITemplate{private TheControl _owner;public DefaultSuccessTemplate(TheControl owner){this._owner = owner;}public void InstantiateIn(Control container){SuccessContainer successContainer = container as SuccessContainer;this.CreatControls(successContainer);this.LayoutControls(successContainer);}private void CreatControls(TheControl.SuccessContainer container){container.Title = new Literal();container.Title.Text = "This is the \"default\" success template.";}private void LayoutControls(TheControl.SuccessContainer container){Table table = new Table();TableRow tr = new TableRow();TableCell tc = new TableCell();tc.Controls.Add(container.Title);tr.Cells.Add(tc);table.Rows.Add(tr);container.Controls.Add(table);}}private void CreateSuccessViewControls(){ITemplate template = null;this._successContainer = new SuccessContainer(this);this._successContainer.ID = "SuccessContainerID";//網頁上有樣板,型態為System.Web.UI.CompiledTemplateBuilderif (this.SuccessTemplate != null){template = this.SuccessTemplate;}else //網頁上沒有樣板,使用內定的模板{template = new DefaultSuccessTemplate(this);}//定義子控制項和樣板所屬的控制項物件template.InstantiateIn(this._successContainer);this.Controls.Add(this._successContainer);}protected override void CreateChildControls(){CreateSuccessViewControls();}}
參考資料:
- System.Web.UI.WebControl.ChangePassword反組譯程式碼(使用ILSPy)
- ITemplate 介面
- TemplateContainerAttribute 類別
- HOW TO:建立樣板化 ASP.NET 使用者控制項
- 樣板集和樣板化控制項
- TemplateBuilder 類別
- CompiledTemplateBuilder 類別
- BuildTemplateMethod 委派