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

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

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

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

 

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

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

CompositeControl.Controls 屬性如下


    Get
        Me.EnsureChildControls
        Return MyBase.Controls
    End Get
End Property

Control.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 中以維持狀態。


        ''' 由 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

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


        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 方法,強制重建子控制項。


        ''' 覆寫 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

ASP.NET 魔法學院