ASP.NET組件設計Step by Step(8)

 

控制控件的樣式

 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

控件最終通常要生成HTML代碼在客戶端,這些HTML元素可以採用豐富的CSS樣式。你當然可以直接進行CSS 設定,但是asp.net給控件開發者提供了編程方式控制樣式的途徑。

如果對樣式無特殊要求,直接繼承webControl的樣式功能即可,如果需要修改或者擴充繼承的樣式功能,則需要深入瞭解控件樣式的背景知識

WebControl的樣式功能全部封裝在ControlStyle屬性中(一個名爲Style System.Web.UI.WebControls.Style的屬性)。所有樣式屬性都是ControlStyle屬性的子屬性。WebControlControlStyle的定義爲:

Private Style _contentStyle;

……

public Style ControlStyle

{

       get

              {

                     if(_contentStyle = = null)

                            {

                                   _contentStyle=CreateControlStyle();

                                   if(IsTrackingViewState)

                                          {

                                                 ((IStateManager)_controlStyle).TrackViewState();

}

                            }

                     return  _contentStyle;

              }

}

我們看到,ControlStyle是隻讀屬性,在第一次訪問時被創建(這個思想繼承了.netJIT方案)。

那麼這個CreateControlStyle到底是什麼回事?

Protected virtual Style CreateControlStyle()

{

       return new Style(ViewSatte);

}

原來也是讀取ViewState得到的。這樣,無論是你從.aspx頁面中關於控件聲明中定義的還是通過編程設定的都可以在存取時反映得到。

作爲控件開發者,可以自定義繼承自Style的屬性,例如,MyTable控件定義一個TableControl類型,添加Table支持的cellpadding/CellSpaceing等屬性。

編程控制屬性有3種途徑:

1、  覆蓋受保護的虛函數CreateControlStyle

2、  利用ApplyStyleStyle s)方法將自定義的屬性複製到控件自己的ControlStyle中去

3、  MergeStyle(Style s)合併方法到ControlStyle中去

 

我們看到,控件的ControlStyle屬性和其他子屬性都是公用一個StateBag的。因爲控件style生成時候是調用傳遞ViewState的構造函數。另外,子控件也是採用同一個StateeBag來存儲狀態的。

 

 

複合控件

首先明確複合控件不同於用戶控件,因爲它是編譯後的形式出現的,而用戶控件則以文本形式部署。但共同點都是類複用來複用他們的功能。

複合控件包含多個已存控件,複用子控件提供的功能。譬如,當要編寫的複合控件包含TextBox時候,就不必自己實現IPostBackDataHandler接口。複合控件可以派生子Control類或者WebControl類,複合的要點是:

1、  重載CreateChildControl方法來對子控件進行實例化、初始化,並將子控件添加到控件樹中(加入到page的控件樹從而獲得控件的生命週期)。需要避免的是不可在OnInit事件中執行業務邏輯。

2、  實現System..Web.UI.InamingContainer接口,從而在複合控件下建立一個新的命名範圍。InamingContainer僅僅是一個標記接口,讓框架自動實現子控件的唯一命名。

爲什麼必須在CreateChildControls方法中創建子控件呢?實際上,這樣做是爲了可以在控件生命週期中任何需要的時候來創建子控件,而且可以利用子控件來處理諸如會傳數據等任務。爲了確保子控件在代碼訪問其之前創建好,Controllei定義的EnsureChildControls保護方法來檢查子控件是夠已經創建好,如果沒有創建,,就可以調用CreateChildControls方法來創建。如果子控件沒有在render之前被創建,那麼缺省情況下visibletrue的未被創建的子控件會被PreRender方法的默認實現調用EnsureChildControls

 

複合可以重用,但是也會帶來性能損失(例如子控件實例化等)。所以,需要在性能和易用之間權衡,要麼複合控件,要麼乾脆自己編寫完全生成控件。

 

複合控件視圖狀態如何工作?

Control內建了跟蹤、保存和恢復子控件的狀態。

在開始跟蹤視圖狀態階段中,Control依次調用Controls集合中的控件的TrackVierState方法,跟蹤子控件的狀態。如果子控件是在父控件中打開狀態下加入到Controls中,那麼在添加到集合時候調用TrackViewState方法。

在保存視圖狀態階段,Control首先調用SaveViewSate方法,默認情況下首先調用ViewState字典的SaveViewState。並保存所返回的對象,作爲控件視圖的第一部分;接下來,Control調用每一個子控件的SaveViewState,如果返回的子控件不爲空,那麼由Control在兩個ArrayList中保存子控件的編號和對應狀態,用來進行串行化。

在加載視圖狀態階段,control先調用LoadViewState方法恢復上一次保存的狀態第一部分,接下來Control訪問Controls集合,將剩下的狀態加載入子控件,一般通過編號和保存狀態的ArrayList來組成,這樣就恢復了控件及其子控件的轉檯。如果在此階段還沒有創建子控件,那麼先保存子控件狀態,留做以後使用,直到子控件創建後加載給子控件。

 

事件冒泡

ASP.NET 頁框架提供一種稱爲“事件冒泡”的技術,允許子控件將事件沿其包容層次結構向上傳播。事件冒泡允許在控件層次結構中更方便的位置引發事件,並且允許將事件處理程序附加到原始控件以及公開冒泡的事件的控件上。

例如:數據綁定控件(RepeaterDataList DataGrid)使用事件冒泡將子控件(在項目模板內)引發的命令事件公開爲頂級事件。雖然 .NET 框架中的 ASP.NET 服務器控件將事件冒泡用於命令事件(事件數據類是從 CommandEventArgs 派生的事件),但是,服務器控件上定義的任何事件都可以冒泡。

控件可以通過從基類 System.Web.UI.Control 繼承的兩個方法參與事件冒泡。這兩個方法是:OnBubbleEvent RaiseBubbleEvent。以下代碼片段顯示了這些方法的簽名。

protected virtual bool OnBubbleEvent(

   object source,

   EventArgs args

);

protected void RaiseBubbleEvent(

   object source,

   EventArgs args

);

RaiseBubbleEvent 的實現是由 Control 提供的,並且不能被重寫。RaiseBubbleEvent 沿層次結構向上將事件數據發送到控件的父級。若要處理或引發冒泡的事件,控件必須重寫 OnBubbleEvent 方法。

 

使事件冒泡的控件執行以下三種操作之一。

1、控件不執行任何操作,此時事件自動向上冒泡到其父級。

2、控件進行一些處理並繼續使事件冒泡。若要實現這一點,控件必須重寫 OnBubbleEvent,並從 OnBubbleEvent 調用 RaiseBubbleEvent。以下代碼片段(摘自模板化數據綁定控件示例)在檢查事件參數的類型後使事件冒泡。

protected override bool OnBubbleEvent(object source, EventArgs e) {

            if (e  is CommandEventArgs) {               

                TemplatedListCommandEventArgs args =

                    new TemplatedListCommandEventArgs(this, source, (CommandEventArgs)e);

                RaiseBubbleEvent(this, args);

                return true;

            }

            return false;

        }

3、控件停止事件冒泡並引發和/或處理該事件。引發事件需要調用將事件調度給偵聽器的方法。若要引發冒泡的事件,控件必須重寫 OnBubbleEvent 以調用引發此冒泡的事件的 OnEventName 方法。引發冒泡的事件的控件通常將冒泡的事件公開爲頂級事件。

protected override bool OnBubbleEvent(object source, EventArgs e) {

    bool handled = false;

    if (e is TemplatedListCommandEventArgs) {

        TemplatedListCommandEventArgs ce = (TemplatedListCommandEventArgs)e;

        OnItemCommand(ce);

        handled = true;

    }

    return handled;

}

 

模板化控件

使用模版化控件,控件開發者可以通過template指定生成的全部或者部分UI。模板是頁面語法的一部分,可以包括靜態的HTML(HTML語法表達的控件實際上是子控件,但是屬於LiteralControl控件)以及其他文自文本的服務器控件。模板功能,允許將控制數據與其表示分開。模板控件本身不提供用戶界面 (UI)。該控件的 UI 由頁面開發人員通過內聯模板提供,該模板允許頁面開發人員自定義該控件的 UI。通過使用模板,控件生成不同於樣式的UI,但是這種UI能力主要是產生頁面元素。譬如repeater控件等。

要支持模板化,控件必須實現Itemplate接口。頁面解析器解析模板標籤內的文本,並生成一個解析樹來表示模板的內容,就像解析整個Page一樣。支持模板控件開發需要做到:

1、實現 System.Web.UI.INamingContainer 接口。這是沒有任何方法的標記接口。它可以在您的控件下創建新的命名範圍,這樣子控件就在名稱樹中有了唯一的標識符。

public class TemplatedFirstControl : Control,INamingContainer {...}

2、將 ParseChildrenAttribute 應用到控件,並傳遞 true 作爲參數。在 ASP.NET 頁上聲明性地使用控件時,這樣可指示頁分析器如何分析模板屬性標記。步驟 3 說明如何定義一個模板屬性。 注意 如果您的控件是從 WebControl 派生的,則不需要應用 ParseChildrenAttribute,因爲 WebControl 已經用該屬性作了標記。

[ ParseChildren(ChildrenAsProperties = true)]

public class TemplatedFirstControl : Control, INamingContainer {...}

3、定義 System.Web.UI.ITemplate 類型的一個或多個屬性。ITemplate 有一個方法 InstantiateIn,該方法可使用頁上內聯提供的模板創建控件。不必實現 InstantiateIn 方法;ASP.NET 頁框架可提供這種實現。ITemplate 屬性必須有 System.Web.UI.TemplateContainerAttribute 類型的元數據屬性,它指出哪種 INamingContainer 控件將擁有實例化模板。這在步驟 4 中作了說明。如下代碼所示定義了一個模板屬性。

[TemplateContainer(typeof(FirstTemplateContainer))]                 

 public ITemplate FirstTemplate {...}

4TemplateContainerAttribute 的參數是您想在其中實例化模板的容器控件類型。容器控件獨立於正在創作的模板控件。具有邏輯容器的原因是:模板控件通常有一個模板,該模板需要使用不同數據重複實例化。擁有與根模板控件不同的容器控件,使擁有多個此類示例成爲可能。邏輯容器是該模板內子控件的即時 INamingContainer。在開發模板化數據綁定控件中更詳細地介紹了這種關係。

注意 容器控件本身必須實現 INamingContainer,因爲它有需要在頁上唯一命名的子控件。但是容器僅僅是容器,對應需要解釋的模板內容,並非控件。

public class FirstTemplateContainer : Control, INamingContainer {...}

5、重寫 CreateChildControls 方法以便在模板中創建子控件。這是通過三個步驟來完成的。

實例化模板容器。

調用模板屬性的 InstantiateIn 方法並將該容器作爲參數傳遞給它。InstantiateIn 方法(在 ITemplate 接口中聲明)實例化該模板的元素,作爲該模板容器的子控件。不必實現 InstantiateIn 方法;ASP.NET 頁框架可提供這種實現。

將模板容器的示例添加到您的模板控件的 Controls 集合。

以下代碼片段說明了 CreateChildControls 的實現。

private Control myTemplateContainer;

protected override void CreateChildControls ()

{

   if (FirstTemplate != null)

   {

      myTemplateContainer = new FirstTemplateContainer(this);

      FirstTemplate.InstantiateIn(myTemplateContainer);

      Controls.Add(myTemplateContainer);

           }

    else

    {

        Controls.Add(new LiteralControl(Text + " " + DateTime));

    }

 }

5、重寫從 Control 繼承的 OnDataBinding 方法以調用 EnsureChildControls 方法。這樣可保證在頁框架嘗試計算模板內任何數據綁定表達式之前,創建模板中的子控件。您還必須調用基類的 OnDataBinding 方法以確保調用已註冊的事件處理程序。

        protected override void OnDataBinding(EventArgs e) {

            EnsureChildControls();

            base.OnDataBinding(e);

        }

7、在步驟 5 中,在 CreateChildControls 方法內重複該邏輯,以便爲控件的每個模板屬性實例化一個模板。

我們看到,通常情況下我們在模板內指定的Container實際上需要我們控件開發者自行定義。實際上如果不重複生成子控件,InamingContainer也可不實現。但是開始提醒需要實現此接口。如果要在控件中支持數據綁定,那麼模板容器應該由一個或者多個屬性代表綁定的數據。通常模板類作爲控件類的內部私有類實現。

控件可以重複實例化某個模板(只要在不同的容器實例中即可),因此,模板不應該包含或者假定任何控件實例作爲成員變量,因爲在每次模板實例化時候,成員變量都可以用新的值來重寫。

 

發佈了30 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章