2011-03-11 71 views
4

这是一个非常奇怪的 - 我会尽我所能解释。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 

我用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,当我单击测试按钮时,回发后保留listview中的数据。

任何人都知道FindControlRecursively调用可能会导致listview丢失数据吗?只有当control-2被添加到页面时才会发生这种情况 - 如果不是,并且使用FindControlRecursive加载control-1,则在回发后数据将被正确保留。

在此先感谢......这一次令我疯狂,我花了一段时间才弄清楚它究竟发生了什么。

+0

我已经复制了母版页的错误代码。我只是纠正它。 – John 2011-03-11 18:36:41

回答

4

你为什么不干脆公开返回PH1PH2性质,因为主机拥有他们的参考,你并不需要遍历主的所有子控件:

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) 

我对这个失去动力,但我相信你会找到答案hereControls.Add将父级的ViewState加载到所有子级中,因此它取决于何时添加控件,并且父级控件中的控件的索引在回发时必须与重新加载ViewState时相同。

递归扩展方法在将其添加到PH1(搜索PH2时)后会触及控件1的ID,迭代扩展不会。我认为这会损坏它在Page_Init中的ViewState。

结论使用属性,而不是

+0

不幸的是,我不认为我可以使用键入的属性,因为主设备也在运行时进行设置(这是家庭酿造的CMS的一部分)。 controlInstance1.ID =“control_2”是一个错字,将在上面修复。我曾经在Page_Load中创建了控件,但是在其他回发场景中viewstate不可用,实际上已经修复Page_Init时遇到了麻烦。你的文章的后半部分给了我一些想法,并且非常合理,但这种行为对我来说仍然很奇怪。一旦我在我的应用中解决这个问题,我会尽快跟进。非常感谢您的建议。 – John 2011-03-12 16:00:51

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检查,我阻止功能从搜索子控件列表视图,这使得列表视图稍后加载其视图状态在页/控制生命周期。

而且,我调整我的迭代函数,使之更有效率,并防止窃听了,如果不返回控制:

<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

额外感谢Tim指着我的正确的方向。