上一篇我們使用複合控制項(繼承 CompositeControl)的方式來實作 TBToolbar 控制項,本文將針對複合控制項做一些測試,說明在使用複合控制項要注意的一些問題。

程式碼下載:ASP.NET Server Control - Day15.rar

 

一、複合控制項建立子控制項的時機

還記得我們之前介紹複合控制項時有談到 CompositeControl 類別會確保我們存取子控制項時,它的子控制項一定會事先建立;也就是當我們使用 Controls 屬性去存取子控制項時,一定會執行 CreateChildControls 方法,以確保子控制項事先被建立。我們看一下 CompositeControl 類別的 Controls 屬性的寫法就可以了解其中的原由,在存取 CompositeControl.Controls 屬性時,它會先執行 Control.EnsureChildControls 方法;而 EnsureChildControls 方法會去判斷子控制項是否已建立,若未建立會去執行 CreateChildControls 方法,這也就是為什麼 CompositeControl 有辨法確保子控制項事先被建立的原因。

CompositeControl.Controls 屬性如下

Public Overrides ReadOnly Property Controls As ControlCollection
    Get
        Me.EnsureChildControls
        Return MyBase.Controls
    End Get
End Property

Control.EnsureChildControls 方法如下

Protected Overridable Sub EnsureChildControls()
    If (Not Me.ChildControlsCreated AndAlso Not Me.flags.Item(&H100)) Then
        Me.flags.Set(&H100)
        Try 
            Me.ResolveAdapter
            If (Not Me._adapter Is Nothing) Then
                Me._adapter.CreateChildControls
            Else
                Me.CreateChildControls
            End If
            Me.ChildControlsCreated = True
        Finally
            Me.flags.Clear(&H100)
        End Try
    End If
End Sub

 

二、複合控制項隱藏的問題

我們以上篇的 TBToolbar 控制項為例,撰寫一些測試案例來說明複合控制項的問題。在撰寫測試案例之前,我們先修改一下 TBToolbar 控制項,覆寫 LoadViewState 及 SaveViewState 方法,將 Items 屬性儲存於 ViewState 中以維持狀態。

        ''' <summary>
        ''' 由 ViewState 還原控制項的狀態。
        ''' </summary>
        ''' <param name="savedState">要還原的控制項狀態。</param>
        Protected Overrides Sub LoadViewState(ByVal savedState As Object)
            If Not (savedState Is Nothing) Then
                ' Load State from the array of objects that was saved at ;
                ' SavedViewState.
                Dim myState As Object() = CType(savedState, Object())

                If Not (myState(0) Is Nothing) Then
                    MyBase.LoadViewState(myState(0))
                End If

                If Not (myState(1) Is Nothing) Then
                    FItems = CType(myState(1), TBToolbarItemCollection)
                End If
            End If
        End Sub

        ''' <summary>
        ''' 控制項的狀態儲存至 ViewState。
        ''' </summary>
        ''' <returns>含有控制項之目前檢視狀態的物件。</returns>
        Protected Overrides Function SaveViewState() As Object
            Dim baseState As Object = MyBase.SaveViewState()
            Dim myState(1) As Object
            myState(0) = baseState
            myState(1) = Me.Items
            Return myState
        End Function

在測試頁面上放置「測試一」、「測試二」、「PostBack」三個按鈕,這三個按鈕的動作如下。

「測試一」按鈕:在工具列直接新增一個按鈕。
「測試二」按鈕:先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
「PostBack」按鈕:單純執行 PostBack,不撰寫程式碼。

image

三個按鈕的程式碼如下所示。

    Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim oItem As TBToolbarItem

        '加入新按鈕
        oItem = New TBToolbarItem()
        oItem.Text = "新按鈕"
        oItem.Key = "NewButton"
        TBToolbar1.Items.Add(oItem)
        Me.Response.Write("「測試一」按鈕")
    End Sub

    Protected Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim oItem As TBToolbarItem
        Dim oButton As Button

        '先執行 FindControl 去取得 ID="Add" 的按鈕
        oButton = TBToolbar1.FindControl("Add")

        '再加入新按鈕
        oItem = New TBToolbarItem()
        oItem.Text = "新按鈕"
        oItem.Key = "NewButton"
        TBToolbar1.Items.Add(oItem)
        Me.Response.Write("「測試二」按鈕")
    End Sub

    Protected Sub Button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button3.Click
        '單純 PostBack,無程式碼
        Me.Response.Write("「PostBack」按鈕")
    End Sub

案例一:執行「測試一」按鈕,在工具列直接新增一個按鈕。

當按下「測試一」按鈕時,工具列可以正常加入我們新增的按鈕。

image

案例二:執行「測試二」按鈕,先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。

重新執行程式,當按下「測試二」按鈕時,你會發現奇怪的現象,工具列竟然沒有加入我們新增的按鈕?

image

此時再按下「PostBack」按鈕,工具列才會出現我們剛剛加入的按鈕。

image

為什麼會發生這種怪現象呢?其實原因很簡單,因為 FindControl 時會去存取 Controls 屬性,而這時子控制項已經被建立了;而之前再用 Items 屬性加入新按鈕,它已經不會在重建子控制項,導致第一時間沒有加入新按鈕。不過 Items 屬性會被存在 ViewState 中,所以當執行「PostBack」按鈕時,就會出現我們剛剛新增的按鈕。

 

三、解決方式

要解決上述「測試二」的問題,只要覆寫 TBToolbar 控制項的 Render 方法,在 Render 前執行 RecreateChildControls 方法,強制重建子控制項。

        ''' <summary>
        ''' 覆寫 Render 方法。
        ''' </summary>
        Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
            Me.RecreateChildControls()
            MyBase.Render(writer)
        End Sub

再一次執行「測試二」的動作,就會發現執行結果就會正常了。

image

 

四、結語

在複合控制項的 Render 前執行 RecreateChildControls 方法可以強制重建子控制項,可是這樣又會引發另一個問題,那就是當直接存取子控制項去修改子控制項的屬性後,一旦在 Render 又重建子控制項,那之前設定子控制項狀態又被全部重建了,所以需特別注意有這樣的情形。另外複合控制項有可能重覆執行建立子控制的動作,在執行效能上也比較不佳。

 

備註:本文同步發佈於「第一屆iT邦幫忙鐵人賽」,如果你覺得這篇文章對您有幫助,記得連上去推鑒此文增加人氣 ^^
http://ithelp.ithome.com.tw/question/10012425


Feedback

  • WizardWu 2008/10/16 下午 12:48 回覆

    # re: [ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題


    現在教 Custom Control 的書,一面倒都是用 C# 撰寫,
    若大大寫一本 vb 版的自訂控件書籍,
    在台灣應該會不錯賣。

    雖然很多年前黃忠成老師的那本 asp.net 1.1/c# 元件開發賣得好像不太好。

  • jeff377 2008/10/16 下午 01:12 回覆

    # re: [ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題

    to WizardWu :
    我是有考慮要寫一本這類的書...不過今年在開發新系統工作超級忙..就一直延延延....沒什麼進度了。 ~"~
    不過就像你說的,控制項開發的書籍是屬於比較進階的技行,有一定難度,相對的會去看的人也就不會那麼多了。

  • allenkuo 2008/10/17 上午 10:48 回覆

    # re: [ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題

    我個人覺得若寫這類進階書,由於市場小,會買的人應該不多
    至於會想買的人, 對於書裡是用vb.net or C#,應該就不太會挑了

  • jeff377 2008/10/17 上午 10:54 回覆

    # re: [ASP.NET 控制項實作 Day15] 複合控制項隱藏的問題

    to allenkuo :
    我也是這麼認為,因為這類進階書籍,一般只是看它的設計方式及概念,程式語言不是重要的考慮因素,我學習伺服器控制項開發時,也大都是看 C# 的資料,VB.NET 的反而少了點。

標題 *
名稱 *
Email (將不會被顯示)
Url
回應
登入後使用進階評論
Please add 7 and 3 and type the answer here: