动态创建的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
相关产品推荐
相关产品推荐

