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
Большое спасибо Тиму за то, что он указал мне правильное направление.