asp.net FindControl Рекурсивно

Это действительно странный вопрос - я сделаю все возможное, чтобы объяснить.

У меня есть основная мастер-страница:

<%@ Master Language="VB" CodeFile="MasterPage.master.vb" Inherits="master_MasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
        </asp:ContentPlaceHolder>
        <asp:PlaceHolder ID="PH1" runat="server" />
        <asp:PlaceHolder ID="PH2" runat="server" />
    </div>
    </form>
</body>
</html>

И стандартная дочерняя страница:

  <%@ Page Title="" Language="VB" MasterPageFile="~/master/MasterPage.master" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="master_Default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
</asp:Content>

У меня есть следующие методы расширения для нахождения элементов управления рекурсивно:

Option Strict On
Option Explicit On

Imports System.Runtime.CompilerServices
Imports System.Web.UI

Public Module ExtensionMethods

    <Extension()> _
    Public Function FindControlRecursively(ByVal parentControl As System.Web.UI.Control, ByVal controlID As String) As System.Web.UI.Control

        If parentControl.ID = controlID Then
            Return parentControl
        End If

        For Each c As System.Web.UI.Control In parentControl.Controls
            Dim child As System.Web.UI.Control = FindControlRecursively(c, controlID)
            If child IsNot Nothing Then
                Return child
            End If
        Next

        Return Nothing

    End Function

    <Extension()> _
    Public Function FindControlIterative(ByVal rootControl As Control, ByVal controlId As String) As Control

        Dim rc As Control = rootControl
        Dim ll As LinkedList(Of Control) = New LinkedList(Of Control)

        Do While (rc IsNot Nothing)
            If rc.ID = controlId Then
                Return rc
            End If
            For Each child As Control In rc.Controls
                If child.ID = controlId Then
                    Return child
                End If
                If child.HasControls() Then
                    ll.AddLast(child)
                End If
            Next
            rc = ll.First.Value
            ll.Remove(rc)
        Loop

        Return Nothing

    End Function

End Module

У меня есть элемент управления со списком:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="control-1.ascx.vb" Inherits="controls_control_1" %>
<p>
    Control 1</p>
<asp:ListView ID="lv" runat="server">
    <ItemTemplate>
        <div>
            <asp:Literal ID="Name" runat="server" Text='<%#Eval("Name") %>' />
            <asp:LinkButton ID="TestButton" runat="server">Test</asp:LinkButton>
        </div>
    </ItemTemplate>
</asp:ListView>

Это связано с данными:

Partial Class controls_control_1
    Inherits System.Web.UI.UserControl

    Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        If Not Page.IsPostBack Then

            Dim l As New List(Of Person)
            Dim j As New Person
            j.Name = "John"
            l.Add(j)

            lv.DataSource = l
            lv.DataBind()

        End If

    End Sub

End Class

Public Class Person
    Public Property Name As String
End Class

У меня есть второй элемент управления, который является очень простым:

<%@ Control Language="VB" AutoEventWireup="false" CodeFile="control-2.ascx.vb" Inherits="controls_control_2" %>
<p>Control 2</p>

На моей дочерней странице у меня есть следующий код для загрузки элементов управления:

Option Strict On
Option Explicit On

Partial Class master_Default
    Inherits System.Web.UI.Page

    Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init

        Dim controlInstance1 As System.Web.UI.Control = LoadControl("~/controls/control-1.ascx")
        controlInstance1.ID = "control_1"

        Dim zone As System.Web.UI.Control = Me.Master.FindControlRecursively("PH1")

        zone.Controls.Add(controlInstance1)

        Dim controlInstance2 As System.Web.UI.Control = LoadControl("~/controls/control-2.ascx")
        controlInstance2.ID = "control_2"

        Dim zone2 As System.Web.UI.Control = Me.Master.FindControlRecursively("PH2")

        zone2.Controls.Add(controlInstance2)

    End Sub

End Class

Это загружает элементы управления, но если я нажимаю кнопку "Тест" в просмотре списка, страница теряет данные в представлении списка после обратной передачи.

Если я изменяю вызовы FindControlRecursively на FindControlIterative, когда я нажимаю кнопку тестирования, данные в виде списка сохраняются после обратной передачи.

Кто-нибудь имеет представление о том, что может делать вызов FindControlRecursively, чтобы заставить просмотр списка потерять свои данные? Это происходит только в том случае, если control-2 добавлен на страницу - если это не так, а control-1 загружается с использованием FindControlRecursively, данные сохраняются правильно после обратной передачи.

Спасибо заранее... этот сводит меня с ума, и мне потребовалось некоторое время, чтобы выяснить, где именно он ломался.

2 ответа

Почему бы вам просто не выставить свойства, которые возвращают PH1 а также PH2потому что мастер имеет ссылку на них, и вам не нужно перебирать все дочерние элементы управления мастера:

Public ReadOnly Property Container1 As PlaceHolder
    Get
        Return Me.PH1
    End Get
End Property

Public ReadOnly Property Container2 As PlaceHolder
    Get
        Return Me.PH2
    End Get
End Property

Вы можете получить к ним доступ:

Dim ph1 As PlaceHolder = DirectCast(Me.Master, myMaster).Container1
Dim ph2 As PlaceHolder = DirectCast(Me.Master, myMaster).Container2

Другая проблема заключается в этой строке:

controlInstance1.ID = "control_2"

Вы устанавливаете только идентификатор controlInstance1 дважды, но это не вызывает проблем.

Ваша основная проблема заключается в том, что вы добавляете элементы управления к заполнителям в Page_Init вместо Page_Load. Поэтому UserControls не может загрузить свой ViewState, а ListView пуст. Создайте их заново в Page_Load, и это сработает.

Изменить: Но я должен признать, что я не знаю, почему ваше итеративное расширение выигрывает у рекурсивного. Ссылка на заполнители одинакова, они не должны работать, странно.

резюме:

  • это работает с моими свойствами,
  • поместив все в обработчик событий загрузки страницы вместо init
  • с вашим расширением итерации (по любым причинам)

Это также работает, если вы, наконец, добавите оба UserControls после того, как вы нашли заполнители через FindControlRecursively,

zone.Controls.Add(controlInstance1)
zone2.Controls.Add(controlInstance2)

Я теряю мотивацию на это, но я уверен, что вы найдете ответ здесь. Controls.Add загружает ViewState его родителя во все дочерние элементы, и поэтому это зависит от того, когда вы добавляете элементы управления, также индекс элементов управления в их родительских элементах управления должен быть одинаковым при обратной передаче, чтобы перезагрузить ViewState.

Ваш рекурсивный метод расширения касается идентификатора control1 после того, как вы добавили его в PH1(при поиске в PH2), итеративное расширение - нет. Я предполагаю, что это портит его ViewState в Page_Init.

Заключение Используйте свойства вместо

Я понял, почему я вижу поведение, которое я описал выше. Я изменил рекурсивную функцию следующим образом:

<Extension()> _
    Public Function FindControlRecursively(ByVal parentControl As System.Web.UI.Control, ByVal controlId As String) As System.Web.UI.Control

        If String.IsNullOrEmpty(controlId) = True OrElse controlId = String.Empty Then
            Return Nothing
        End If

        If parentControl.ID = controlId Then
            Return parentControl
        End If

        If parentControl.HasControls Then
            For Each c As System.Web.UI.Control In parentControl.Controls
                Dim child As System.Web.UI.Control = FindControlRecursively(c, controlId)
                If child IsNot Nothing Then
                    Return child
                End If
            Next
        End If

        Return Nothing

    End Function

Добавляя parentControl.HasControls проверьте, я запрещаю функции выполнять поиск по списку для дочерних элементов управления, и это позволяет списку просмотра загружать свое представление позже в жизненном цикле страницы / элемента управления.

Кроме того, я настроил свою итеративную функцию, чтобы сделать ее более эффективной и не допустить сбоев, если элемент управления не был возвращен:

<Extension()> _
    Public Function FindControlIteratively(ByVal parentControl As Web.UI.Control, ByVal controlId As String) As Web.UI.Control

        Dim ll As New LinkedList(Of Web.UI.Control)

        While parentControl IsNot Nothing
            If parentControl.ID = controlId Then
                Return parentControl
            End If
            For Each child As Web.UI.Control In parentControl.Controls
                If child.ID = controlId Then
                    Return child
                End If
                If child.HasControls() Then
                    ll.AddLast(child)
                End If
            Next
            If (ll.Count > 0) Then
                parentControl = ll.First.Value
                ll.Remove(parentControl)
            Else
                parentControl = Nothing
            End If
        End While

        Return Nothing

    End Function

Кроме того, чтобы продолжить мое предыдущее описание проблемы - я смог воспроизвести странно странное поведение рекурсивной функции, используя итеративную функцию, если я удалил If child.HasControls() Then проверить из итерационной функции. Надеюсь, что это имеет смысл.

В конце концов, я придерживаюсь итеративной функции, потому что цикл должен быть дешевле, чем рекурсия, хотя в реальных сценариях разница, вероятно, не будет заметна.

Следующие ссылки были чрезвычайно полезны для меня в разработке этого:

http://msdn.microsoft.com/en-us/library/ms972976.aspx

http://www.4guysfromrolla.com/articles/092904-1.aspx

http://scottonwriting.net/sowblog/archive/2004/10/06/162995.aspx

http://scottonwriting.net/sowblog/archive/2004/10/08/162998.aspx

Большое спасибо Тиму за то, что он указал мне правильное направление.

Другие вопросы по тегам