3

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

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

由于生命周期将进入:页面加载 -> 按钮单击 -> 页面预渲染。在“按钮单击”之前,视图状态不会更新,因此我在 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);
    }        
}
4

4 回答 4

3

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

  • 使用视图状态来跟踪动态创建的控件。
  • LoadViewState在( not Load或中重新创建具有相同 ID 的控件PreRender,因为这样输入字段的值将丢失)。

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

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

要跟踪动态创建的行,请在视图状态下维护行 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处理程序中,删除单击的行并更新视图状态:

    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);
    }

通过创建第一行来挂钩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);
    }

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

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

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

处理零件

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

于 2013-07-29T02:55:03.263 回答
1

未触发按钮单击,因为在 Load 事件之后立即调用控制事件。在 asp.net 生命周期尝试调用您的事件时,您的按钮不在控件层次结构中,因此它被删除了。请记住,这是一个往返,并且控件必须存在于回发中,然后才能触发 LoadComplete 事件以调用其事件处理程序。

在 PreLoad 或 Load 事件中创建您的动态控件,您应该没问题(此时您将有权访问完整的视图状态,以决定是否需要为该行动态创建删除按钮)。

ASP.net 页面生命周期文档: http: //msdn.microsoft.com/en-us/library/ms178472 (v=vs.100).aspx

于 2013-07-19T23:33:49.857 回答
0

同意罗伯特和比尔的观点。

但是要在这里添加,在我看来,实现这一点的唯一方法是创建自定义控件/Web 服务器控件(继承WebControl类),在其中覆盖CreateChildControls方法和RenderContents方法。我认为这就是您所说的意思,在您的一个评论中,您将编写一个网格视图版本。

于 2013-07-24T13:56:33.560 回答
0

我认为罗伯特有正确的答案,但需要更清楚地了解他在谈论哪个 Page.Load。这里有三个页面请求。

  1. 初始页面请求,尚未单击初始按钮。
  2. 回发按钮点击。页面加载中没有处理。PreRender 调用创建新表行和新按钮,并将按钮单击事件链接到新按钮。
  3. 客户端单击新按钮后的回发。您需要在页面加载中重新创建动态按钮,以便动态按钮的 Click 事件不会被丢弃。
于 2013-07-22T16:06:46.700 回答