[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 属性如下

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值