【问题标题】:ASP.NET - Two User Controls - Multiple Instances Added at RuntimeASP.NET - 两个用户控件 - 在运行时添加多个实例
【发布时间】:2013-04-26 19:47:57
【问题描述】:

我有一个带有两个用户控件的 VB ASP.NET Web 应用程序,每个控件都包含一个文本输入。有两个提交按钮,每个按钮对应一个用户控件。

单击按钮会添加其相应用户控件的实例。在大多数情况下,这是可行的,只是在特定情况下,文本框的 ID 会混淆,从而混淆先前输入的值。

问题场景如下:

1) 点击second 按钮(Add Approver 按钮)两次,然后在两个结果文本框中输入一些值(为便于分析,使值不同)。

2) 单击第一个按钮(添加文档按钮)一次。 (这里不需要在结果文本框中添加任何值。)

此时一切看起来都是正确的。查看页面源代码,我看到两个“审批者”文本框的 ID 为 ctl02_txtApprover 和 ctl03_txtApprover,一个“文档”文本框的 ID 为 ctl04_txtDocument。

  1. 再次单击第一个按钮(添加文档按钮)。

此时第一个“批准人”文本框中的值消失了。第二个“批准者”文本框中的值迁移到第一个“批准者”文本框。查看页面源码,两个“Approver”文本框的ID已更改为ctl03_txtApprover和ctl04_txtApprover。考虑到文本框 ID 已更改,迁移的值是有意义的。换句话说,ViewState 看起来正确,但控件 ID 不正确。

我已尽可能简单地编写代码并将其发布在这里。

Default.aspx

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="WebApplicationUserControlTest._Default" %>
<!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>
</head>
<body>
    <form id="form1" runat="server">
        <asp:PlaceHolder ID="phDocument" runat="server" />
        <asp:Button ID="btnAddDocument" runat="server" Text="Add Document" />
        <br /><br />
        <asp:PlaceHolder ID="phApprover" runat="server" />
        <asp:Button ID="btnAddApprover" runat="server" Text="Add Approver" />
    </form>
</body>
</html>

Default.aspx.vb

Public Class _Default
Inherits System.Web.UI.Page
Private Const VIEWSTATE_DOCUMENT_COUNT As String = "DocumentCount"
Private Const VIEWSTATE_APPROVER_COUNT As String = "ApproverCount"

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If Not IsPostBack Then
        ViewState(VIEWSTATE_DOCUMENT_COUNT) = 0
        ViewState(VIEWSTATE_APPROVER_COUNT) = 0
    Else
        're-display any preexisting dynamic sections on postback
        AddAllDocumentInfoSections()
        AddAllApproverSections()
    End If
End Sub

Protected Sub btnAddDocument_Click(sender As Object, e As EventArgs) Handles btnAddDocument.Click
    ViewState(VIEWSTATE_DOCUMENT_COUNT) += 1
    AddDocumentSection()
End Sub

Protected Sub btnAddApprover_Click(sender As Object, e As EventArgs) Handles btnAddApprover.Click
    ViewState(VIEWSTATE_APPROVER_COUNT) += 1
    AddApproverSection()
End Sub

Private Sub AddAllDocumentInfoSections()
    For i As Integer = 0 To ViewState(VIEWSTATE_DOCUMENT_COUNT) - 1
        AddDocumentSection()
    Next
End Sub

Private Sub AddAllApproverSections()
    For i As Integer = 0 To ViewState(VIEWSTATE_APPROVER_COUNT) - 1
        AddApproverSection()
    Next
End Sub

Private Sub AddDocumentSection()
    Dim c As UserControl = LoadControl("~/Document.ascx")
    phDocument.Controls.Add(c)
End Sub

Private Sub AddApproverSection()
    Dim c As UserControl = LoadControl("~/Approver.ascx")
    phApprover.Controls.Add(c)
End Sub
End Class

Document.ascx

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="Document.ascx.vb" Inherits="WebApplicationUserControlTest.Document" %><asp:TextBox ID="txtDocument" runat="server" /><br /><br />

Approver.ascx

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="Approver.ascx.vb" Inherits="WebApplicationUserControlTest.Approver" %><asp:TextBox ID="txtApprover" runat="server" /><br /><br />

我使用的是 Visual Studio 2010。目标框架是 4.0。我曾尝试更改 clientIDMode 但这似乎没有什么不同。我遇到了 .NET 的错误还是我的代码有问题?

【问题讨论】:

    标签: asp.net vb.net


    【解决方案1】:

    你的代码有问题。

    如果您将控件动态添加到控件树中的同一命名容器中,那么您需要在每次回发后以相同的顺序添加它们。

    在你的情况下,你没有这样做。

    在您的第 2 步中,您按此顺序添加了三个控件:

    • 审批人 1 (AddAllApproverSections)
    • 审批人 2 (AddAllApproverSections)
    • DocumentInfo 1 (btnAddDocument_Click)

    但是在回发之后,您按以下顺序重新生成它们:

    • DocumentInfo 1 (AddAllDocumentInfoSections)
    • 审批人 1 (AddAllApproverSections)
    • 审批人 2 (AddAllApproverSections)

    因此控件 id 不一样,你看到的问题也不一样。

    一种解决方案可能是在 ViewState 中存储表示控件添加顺序的附加信息,以便您可以按相同顺序重新创建它们。

    但我可能倾向于采用不同的方法,例如将 DocumentInfo 部分放入 Repeater 的模板中,将 Approver 部分放入第二个 Repeater。每个Repeater都将数据绑定到一个合适的集合,添加一个项目(Approver或DocumentInfo)将通过向相关集合添加一个元素并调用DataBind来实现。

    【讨论】:

    • 谢谢乔。使用两个中继器,每个用户控件一个似乎工作。查看页面源代码,每个文本框的名称都以它的中继器名称为前缀。我之前曾尝试使用两个面板来实现相同的想法,但无济于事。我想我需要更好地理解命名容器。
    • @Cienfuegos,面板不是命名容器(不实现 INamingContainer 标记接口),所以这就是您的尝试没有成功的原因。在这种情况下,我认为中继器是更好的方法。
    【解决方案2】:

    这里的问题是您正在修改 Controls 集合和 ViewState 在它们被初始化之后。 永远不要在页面加载事件中动态添加控件。

    您需要在Page life cycle 的Page_Init 阶段添加控件,并从Page_Load 事件中的else 语句中删除代码。您的新 Page_Init 事件如下所示:

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

    我相信您可能必须更改存储这些控件的“计数”的方式,因为在此阶段尚不提供视图状态信息。在这种情况下,我会将其存储为 Session 变量。您只需要在整个代码示例中使用“Session”更改对“ViewState”的引用,如下所示:

    Private Sub AddAllDocumentInfoSections()
        For i As Integer = 0 To Session(VIEWSTATE_DOCUMENT_COUNT) - 1
            AddDocumentSection()
        Next
    End Sub
    

    【讨论】:

    • 谢谢。我以前尝试过使用页面初始化。此外,虽然您提供的代码不会对任何内容产生不利影响,但生成的功能与以前完全相同。
    • "您永远不应该在页面加载事件中动态添加控件。" - 我会限定这一点:只要您知道自己在做什么,您就可以在页面加载和以后动态添加控件。
    • @Joe 这是一个有效的观点 =) 我赞成您的回答,因为您发现了我对整个问题的遗漏。很好的答案。
    • 仅供参考,只要我使用两个不同的命名容器,我就可以继续使用 Page_Load 事件和 ViewState。也许最近版本的 .Net 发生了一些变化,使得以前对 Page_Init 的限制不再相关?再次感谢。
    猜你喜欢
    • 2013-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多