[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,不撰寫程式碼。
三個按鈕的程式碼如下所示。
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
案例一:執行「測試一」按鈕,在工具列直接新增一個按鈕。
當按下「測試一」按鈕時,工具列可以正常加入我們新增的按鈕。
案例二:執行「測試二」按鈕,先使用 FindControl 取得工具列的按鈕,然後在在工具列再新增一個按鈕。
重新執行程式,當按下「測試二」按鈕時,你會發現奇怪的現象,工具列竟然沒有加入我們新增的按鈕?
此時再按下「PostBack」按鈕,工具列才會出現我們剛剛加入的按鈕。
為什麼會發生這種怪現象呢?其實原因很簡單,因為 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
再一次執行「測試二」的動作,就會發現執行結果就會正常了。
四、結語
在複合控制項的 Render 前執行 RecreateChildControls 方法可以強制重建子控制項,可是這樣又會引發另一個問題,那就是當直接存取子控制項去修改子控制項的屬性後,一旦在 Render 又重建子控制項,那之前設定子控制項狀態又被全部重建了,所以需特別注意有這樣的情形。另外複合控制項有可能重覆執行建立子控制的動作,在執行效能上也比較不佳。
備註:本文同步發佈於「第一屆iT邦幫忙鐵人賽」,如果你覺得這篇文章對您有幫助,記得連上去推鑒此文增加人氣 ^^
http://ithelp.ithome.com.tw/question/10012425