2011-03-11 82 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指着我的正確的方向。