You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态创建的TextBox开启AutoPostBack后TextChanged事件未触发

动态创建的TextBox TextChanged事件不触发的解决方案

嘿,我看你卡在动态控件事件触发的问题上了,这在ASP.NET开发里是个挺典型的坑,核心原因是动态控件的生命周期管理没做好,咱们一步步来解决:

问题根源分析

你现在是在按钮点击事件里创建动态控件,但ASP.NET的页面生命周期中,动态控件不会自动被持久化到ViewState里。当txtMFVal触发回发时,页面重建过程中找不到这个控件,自然就没法执行绑定的txtManFeesVal_TextChange方法。另外还有个小问题:你注释掉了txtMFVal的ID设置,ASP.NET需要控件有唯一ID才能在回发时识别它,这也会影响事件触发。

具体解决步骤

1. 把动态控件的创建逻辑抽成独立方法,并在每次回发时重建

把创建服务行的代码单独拎出来,这样每次页面回发都能重新生成所有动态控件,而且要在Page_Load或者更早的Page_Init阶段执行重建,确保控件能参与到事件触发流程中。

2. 把serCount存到ViewState里

不然回发后serCount会被重置为0,导致之前创建的行全部丢失。

3. 恢复txtMFVal的ID设置

每个动态控件必须有唯一ID,ASP.NET靠这个来关联回发时的控件和事件。

4. 修正事件处理方法里的类型错误

你在事件方法里把DropDownList当成TextBox来转换了,会导致空引用,这个也得改。

修改后的代码示例

第一步:保存serCount到ViewState

// 用ViewState持久化serCount,避免回发重置
private int serCount
{
    get { return ViewState["serCount"] != null ? (int)ViewState["serCount"] : 0; }
    set { ViewState["serCount"] = value; }
}

第二步:抽离创建行的方法

private void CreateServiceRow(int rowIndex)
{
    TableRow r = new TableRow();
    TableCell c0 = new TableCell();
    TableCell c1 = new TableCell();
    TableCell c2 = new TableCell();
    TableCell c3 = new TableCell();
    TableCell c4 = new TableCell();
    TableCell c5 = new TableCell();
    TableCell c6 = new TableCell();
    TableCell c7 = new TableCell();

    // --- 服务下拉框创建 ---
    DropDownList ddlS = new DropDownList();
    ddlS.ID = "ddlServices" + rowIndex;
    ddlS.Items.Add("Management");
    ddlS.Items.Add("MEP");
    ddlS.Items.Add("Landscaping");
    ddlS.Items.Add("Pest Control");
    ddlServices.Add(ddlS);
    ddlS.CssClass = "form-control";
    ddlS.Width = Unit.Percentage(100);
    c0.Controls.Add(ddlS);
    c0.Width = Unit.Percentage(15);

    // --- 分包复选框创建 ---
    CheckBox chkSubCont = new CheckBox();
    chkSubCont.ID = "chkSubCont" + rowIndex;
    chkSubContracted.Add(chkSubCont);
    c1.Controls.Add(chkSubCont);
    c1.Width = Unit.Percentage(5);
    c1.HorizontalAlign = HorizontalAlign.Center;

    // --- 分包值输入框 ---
    TextBox txtSubContVal = new TextBox();
    txtSubContVal.ID = "txtSubcontractVal" + rowIndex;
    txtSubContVal.CssClass = "form-control";
    txtSubContVal.Attributes.Add("placeholder", "0.000");
    txtSubContVal.Style.Add("text-align", "right");
    txtSubcontValue.Add(txtSubContVal);
    c2.Controls.Add(txtSubContVal);
    c2.Width = Unit.Percentage(10);

    // --- 管理费类型下拉框 ---
    DropDownList ddlMFT = new DropDownList();
    ddlMFT.Items.Add("%");
    ddlMFT.Items.Add("Value");
    ddlMFT.CssClass = "form-control";
    ddlMFT.Width = Unit.Percentage(100);
    ddlMFT.ID = "ddlManFeesType" + rowIndex;
    ddlManFeesType.Add(ddlMFT);
    c5.Controls.Add(ddlMFT);
    c5.Width = Unit.Percentage(5);

    // --- 管理费值输入框(关键修复)---
    TextBox txtMFVal = new TextBox();
    txtMFVal.CssClass = "form-control";
    txtMFVal.Attributes.Add("placeholder", "0.000");
    txtMFVal.Style.Add("text-align", "right");
    txtMFVal.Width = Unit.Percentage(100);
    // 恢复ID设置,必须唯一
    txtMFVal.ID = "txtManFeesValue" + rowIndex;
    txtMFVal.AutoPostBack = true;
    // 每次创建都要绑定事件
    txtMFVal.TextChanged += txtManFeesVal_TextChange;
    txtManFeesValue.Add(txtMFVal);
    c6.Controls.Add(txtMFVal);
    c6.Width = Unit.Percentage(10);

    // --- 人力值输入框 ---
    TextBox txtManp = new TextBox();
    txtManp.ID = "txtManpower" + rowIndex;
    txtManp.CssClass = "form-control";
    txtManp.Attributes.Add("placeholder", "0");
    txtManp.Style.Add("text-align", "center");
    txtManPower.Add(txtManp);
    c3.Controls.Add(txtManp);
    c3.Width = Unit.Percentage(10);

    // --- 报价值输入框 ---
    TextBox txtQuotVal = new TextBox();
    txtQuotVal.ID = "txtQuotedValue" + rowIndex;
    txtQuotVal.CssClass += "form-control";
    txtQuotVal.Attributes.Add("placeholder", "0.000");
    txtQuotVal.Style.Add("text-align", "right");
    txtQuotVal.AutoPostBack = true;
    txtQuotVal.TextChanged += (o, ep) => {
        TextBox t = o as TextBox;
        if (string.IsNullOrEmpty(t.Text)) {
            t.Text = "0.000";
        }
        // 这里注意类型转换,int可能会丢失小数,建议用double
        double prjVal = double.Parse(txtPrjValue.Text);
        double quotVal = double.Parse(t.Text);
        txtPrjValue.Text = (prjVal + quotVal).ToString("0.000");
    };
    txtQuotValue.Add(txtQuotVal);
    c4.Controls.Add(txtQuotVal);
    c4.Width = Unit.Percentage(10);

    // --- 备注输入框 ---
    TextBox txtRem = new TextBox();
    txtRem.ID = "txtRemarks" + rowIndex;
    txtRem.CssClass += "form-control";
    txtRem.Width = Unit.Percentage(100);
    txtRemarks.Add(txtRem);
    c7.Controls.Add(txtRem);
    c7.Width = Unit.Percentage(35);

    // 添加单元格到行,再添加行到表格
    r.Cells.Add(c0);
    r.Cells.Add(c1);
    r.Cells.Add(c2);
    r.Cells.Add(c5);
    r.Cells.Add(c6);
    r.Cells.Add(c3);
    r.Cells.Add(c4);
    r.Cells.Add(c7);
    tblServices.Rows.Add(r);
}

第三步:在Page_Load中重建动态控件

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        serCount = 0;
        tblHeader.Visible = false;
    }
    else
    {
        // 回发时重建所有已创建的动态行
        for (int i = 0; i < serCount; i++)
        {
            CreateServiceRow(i);
        }
    }
}

第四步:修改按钮点击事件

protected void btnAddService_Click(object sender, EventArgs e)
{
    CreateServiceRow(serCount);
    serCount++;
    tblHeader.Visible = serCount > 0;

    // 原来的刷新脚本可以去掉了,现在回发会自动重建控件
    // string scriptRefresh = "document.getElementById('" + btnASPClickMe.ClientID + "').click();";
    // Page.ClientScript.RegisterStartupScript(typeof(Page), "refreshscript", scriptRefresh, true);
}

第五步:修正事件处理方法的类型错误

protected void txtManFeesVal_TextChange(object sender, EventArgs e)
{
    TextBox txtMFV = sender as TextBox;
    if (txtMFV == null) return;

    TableRow tr = txtMFV.Parent.Parent as TableRow;
    if (tr == null) return;

    // 分包值输入框
    TextBox txtSCV = tr.Cells[2].Controls[0] as TextBox;
    // 管理费类型是DropDownList,不是TextBox!
    DropDownList ddlMFT = tr.Cells[5].Controls[0] as DropDownList;

    if (txtSCV == null || ddlMFT == null) return;

    double scv = double.TryParse(txtSCV.Text, out scv) ? scv : 0;
    double mfv = double.TryParse(txtMFV.Text, out mfv) ? mfv : 0;
    double v = 0;

    // 根据管理费类型计算值
    if (ddlMFT.SelectedValue == "%")
    {
        v = scv * (mfv / 100);
    }
    else
    {
        v = mfv;
    }

    // 后续逻辑...比如更新其他控件的值
}

额外注意点

  • 尽量用double.TryParse代替直接double.Parse,避免输入非数字时抛出异常。
  • 报价值计算时,用double代替int,不然会丢失小数精度。
  • 动态控件的集合(比如ddlServices、txtManFeesValue)如果需要在回发后访问,也要确保每次重建时重新添加,或者直接通过FindControl根据ID查找控件,这样更可靠。

内容的提问来源于stack exchange,提问作者Badih Barakat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:58:28