这真的很奇怪 - 我会尽力解释。
我有一个基本的母版页:
<%@ 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
我有一个带有listview的控件:
<%@ 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调用可能会导致listview丢失数据吗?只有在将control-2添加到页面时才会发生这种情况 - 如果不是,并且使用FindControlRecursively加载control-1,则在回发后数据会被正确保留。
先谢谢...这个让我疯了,我花了一段时间才弄明白它究竟在哪里崩溃。
答案 0 :(得分:4)
为什么不简单地公开返回PH1
和PH2
的属性,因为master具有它们的引用,并且您不需要迭代master的所有子控件:
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的ID,但这不会导致您的问题。
您的主要问题是您将控件添加到Page_Init中的占位符而不是Page_Load。因此UserControls无法加载其ViewState并且ListView为空。在Page_Load中重新创建它们就可以了。
编辑:但我必须承认,我不知道为什么你的迭代扩展会胜过递归。关于占位符的引用是相同的,它们不应该同时工作,很奇怪。
摘要:
在通过FindControlRecursively
找到占位符后,最后添加两个UserControl也可以。
zone.Controls.Add(controlInstance1)
zone2.Controls.Add(controlInstance2)
我在这方面失去了动力,但我相信你会找到答案here。 Controls.Add
将其父级的ViewState加载到所有子级中,因此它取决于添加控件的时间,在回发时其父控件中控件的索引也必须相同才能重新加载ViewState。
您将递归扩展方法在将其添加到PH1后(在搜索PH2时)触及control1的ID,但迭代扩展不会。我认为这会破坏它在Page_Init中的ViewState。
结论使用属性
答案 1 :(得分:3)
我弄清楚为什么我会看到上面描述的行为。我将递归函数更改为以下内容:
<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
检查,我阻止该功能在列表视图中搜索子控件,这允许listview稍后在页面/控件生命周期中加载其视图状态。
另外,我调整了迭代函数,使其更有效,并防止它在未返回控件时发出错误:
<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#viewstate_topic4
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
非常感谢蒂姆指出我正确的方向。