asp.net - 基于viewstate在Page_PreRender中创建动态控件导致按钮OnClick事件不起作用

标签 asp.net

我意识到应该在 Page_Load 和 Page_Init 中创建动态控件,以便将它们注册到控件树中。

我创建了一个自定义控件,需要在按钮 OnClick 事件中使用 ViewState。然后使用此 ViewState 动态创建控件。

由于生命周期将进行:页面加载 -> 按钮点击 -> 页面预渲染。在“单击按钮”之前, View 状态不会更新,因此我在 Page PreRender 中创建动态控件。但是,创建按钮并以编程方式在 Page_PreRender 中分配 OnClick EventHandler 不起作用。

有谁知道我怎样才能让它工作?

btn_DeleteTableRow_Click 不会触发。这是在 CreatePartRows() 中设置的

这是我的例子:

<asp:UpdatePanel ID="up_RMAPart" runat="server" UpdateMode="Conditional" EnableViewState="true" ChildrenAsTriggers="true">
<ContentTemplate>
    <div class="button" style="width: 54px; margin: 0px; float: right;">
        <asp:Button ID="btn_AddPart" runat="server" Text="Add" OnClick="btn_AddPart_Click" />
    </div>
    <asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
    </asp:Table>
    <div class="clear"></div>
</ContentTemplate>
<Triggers>
    <asp:AsyncPostBackTrigger ControlID="btn_AddPart" EventName="Click" />
</Triggers>

代码隐藏:

[Serializable]
public struct Part
{
    public string PartName;
    public int Quantity;
    public int PartID;

    public Part(string sPartName, int iQuantity, int iPartID)
    {
        PartName = sPartName;
        Quantity = iQuantity;
        PartID = iPartID;
    }
}

public partial class RMAPart : System.Web.UI.UserControl
{
    private Dictionary<string,Part> m_RMAParts;
    private int m_RowNumber = 0;

    public Dictionary<string, Part> RMAParts
    {
        get
        {
            if (ViewState["m_RMAParts"] != null)
                return (Dictionary<string, Part>)ViewState["m_RMAParts"];
            else
                return null;
        }
        set
        {
            ViewState["m_RMAParts"] = value;
        }
    }

    public int RowNumber
    {
        get
        {
            if (ViewState["m_RowNumber"] != null)
                return Convert.ToInt32(ViewState["m_RowNumber"]);
            else
                return 0;
        }
        set
        {
            ViewState["m_RowNumber"] = value;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            RMAParts = new Dictionary<string, Part>();
            RowNumber = 0;
            RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
            RowNumber = 1;
            CreatePartRows();
        }
    }



    protected void Page_PreRender(object sender, EventArgs e)
    {
        CreatePartRows();
    }

    private void CreatePartRows()
    {
        Table_Parts.Controls.Clear();

        TableHeaderRow thr = new TableHeaderRow();

        TableHeaderCell thc1 = new TableHeaderCell();
        thc1.Controls.Add(new LiteralControl("Part"));
        thr.Cells.Add(thc1);

        TableHeaderCell thc2 = new TableHeaderCell();
        thc2.Controls.Add(new LiteralControl("Quantity"));
        thr.Cells.Add(thc2);

        TableHeaderCell thc3 = new TableHeaderCell();
        thc3.Controls.Add(new LiteralControl(""));
        thr.Cells.Add(thc3);

        Table_Parts.Rows.Add(thr);

        foreach (KeyValuePair<string, Part> kvp in RMAParts)
        {
            string[] sKey = kvp.Key.Split('_');

            TableRow tr = new TableRow();
            tr.ID = kvp.Key;

            TableCell tc1 = new TableCell();
            TextBox tb_Part = new TextBox();
            tb_Part.ID = "tb_Part_" + sKey[1];
            tb_Part.CssClass = "textbox1";
            tc1.Controls.Add(tb_Part);
            tr.Cells.Add(tc1);

            TableCell tc2 = new TableCell();
            TextBox tb_Quantity = new TextBox();
            tb_Quantity.ID = "tb_Quanitty_" + sKey[1];
            tb_Quantity.CssClass = "textbox1";
            tc2.Controls.Add(tb_Quantity);
            tr.Cells.Add(tc2);

            TableCell tc3 = new TableCell();
            Button btn_Delete = new Button();
            btn_Delete.ID = "btn_Delete_" + sKey[1];
            btn_Delete.CommandArgument = tr.ID;
            btn_Delete.Click += new EventHandler(btn_DeleteTableRow_Click);                
            btn_Delete.Text = "Remove";
            tc3.Controls.Add(btn_Delete);
            tr.Cells.Add(tc3);

            Table_Parts.Rows.Add(tr);               
        }

    }

    public void Reset()
    {
        Table_Parts.Controls.Clear();
        RMAParts.Clear();
        RowNumber = 0;
        RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
        RowNumber = 1;
        CreatePartRows();
    }

    protected void btn_AddPart_Click(object sender, EventArgs e)
    {
        RMAParts.Add("PartRow_" + RowNumber.ToString(), new Part());
        RowNumber++;
    }

    protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
        Table_Parts.Rows.Remove(tr);
        RMAParts.Remove(btn.CommandArgument);
    }        
}

最佳答案

为确保输入字段的值在回传中保持不变并引发服务器事件:

  • 使用 View 状态来跟踪动态创建的控件。
  • LoadViewState 中重新创建具有相同 ID 的控件(不是 LoadPreRender,因为那时输入字段的值将丢失)。

此答案的其余部分详细说明了我如何修改您的代码以使其正常工作。

RMAPart.ascx

为方便起见,您可以在 .ascx 中声明标题行:

<asp:Table ID="Table_Parts" runat="server" CssClass="hor-zebra">
    <asp:TableRow>
        <asp:TableHeaderCell Text="Part" />
        <asp:TableHeaderCell Text="Quantity" />
        <asp:TableHeaderCell />
    </asp:TableRow>
</asp:Table>

RMAPart.ascx.cs

要跟踪动态创建的行,请在 View 状态中维护行 ID 列表:

public partial class RMAPart : System.Web.UI.UserControl
{
    private List<string> RowIDs
    {
        get { return (List<string>)ViewState["m_RowIDs"]; }
        set { ViewState["m_RowIDs"] = value; }
    }

btn_AddPart_Click 处理程序中,生成一个新行 ID 并为新行创建控件:

    protected void btn_AddPart_Click(object sender, EventArgs e)
    {
        string id = GenerateRowID();
        RowIDs.Add(id);
        CreatePartRow(id);
    }

    private string GenerateRowID()
    {
        int id = (int)ViewState["m_NextRowID"];
        ViewState["m_NextRowID"] = id + 1;
        return id.ToString();
    }

    private void CreatePartRow(string id)
    {
        TableRow tr = new TableRow();
        tr.ID = id;

        TableCell tc1 = new TableCell();
        TextBox tb_Part = new TextBox();
        tb_Part.ID = "tb_Part_" + id;
        tb_Part.CssClass = "textbox1";
        tc1.Controls.Add(tb_Part);
        tr.Cells.Add(tc1);

        TableCell tc2 = new TableCell();
        TextBox tb_Quantity = new TextBox();
        tb_Quantity.ID = "tb_Quantity_" + id;
        tb_Quantity.CssClass = "textbox1";
        tc2.Controls.Add(tb_Quantity);
        tr.Cells.Add(tc2);

        TableCell tc3 = new TableCell();
        Button btn_Delete = new Button();
        btn_Delete.ID = "btn_Delete_" + id;
        btn_Delete.CommandArgument = id;
        btn_Delete.Click += btn_DeleteTableRow_Click;
        btn_Delete.Text = "Remove";
        tc3.Controls.Add(btn_Delete);
        tr.Cells.Add(tc3);

        Table_Parts.Rows.Add(tr);
    }

btn_DeleteTableRow_Click 处理程序中,删除被点击的行并更新 View 状态:

    protected void btn_DeleteTableRow_Click(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        TableRow tr = (TableRow)Table_Parts.FindControl(btn.CommandArgument);
        Table_Parts.Rows.Remove(tr);
        RowIDs.Remove(btn.CommandArgument);
    }

Hook Page_Load 并通过创建第一行开始:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            Reset();
        }
    }

    public void Reset()
    {
        while (Table_Parts.Rows.Count > 1)
            Table_Parts.Rows.RemoveAt(Table_Parts.Rows.Count - 1);

        ViewState["m_NextRowID"] = 0;
        string id = GenerateRowID();
        RowIDs = new List<string> { id };
        CreatePartRow(id);
    }

覆盖 LoadViewState 并使用存储在 View 状态中的 ID 重新创建行:

    protected override void LoadViewState(object savedState)
    {
        base.LoadViewState(savedState);

        foreach (string id in RowIDs)
        {
            CreatePartRow(id);
        }
    }
}

处理零件

上面的代码根本没有使用您的 Part 结构。要在您的业务对象和用户控件之间实际移动数据,您可以添加一个采用 Part 集合并使用它来创建行和填充文本框的公共(public)方法,然后添加另一个公共(public)方法读取将文本框的值输出到 Part 集合中。

关于asp.net - 基于viewstate在Page_PreRender中创建动态控件导致按钮OnClick事件不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17755263/

相关文章:

c# - 复选框!指定的转换无效

c# - 为什么我在使用 FileMode.OpenOrCreate 时收到 FileNotFoundException?

c# - 使用 JSON.NET 将 JSON 反序列化为匿名对象

asp.net - 将 Request.ApplicationPath 用于 cookie 路径是否安全

c# - 从 html5 按钮 onclick 事件调用服务器端方法

c# - 获取有关 Ninject 拦截的方法的信息

c# - Web API [FromBody] 参数在发送对象数组时为空

mysql - 为什么数据不插入mysql数据库?

asp.net - SandcaSTLe MREFBuilder在Crystal Reports XI上进行倒推

c# - Web.config 设置无法接受超过 1 GB 的文件大小