ASP.NET GridView动态复选框报‘未将对象引用设置到对象实例’错误
Object reference error when accessing dynamically added CheckBoxes in GridView on postback
我正在实现一个功能:用两张SQL表discipline和program在GridView里构建矩阵——discipline的行作为列标题,program的行作为行标题,并且在GridView1_RowDataBound方法中给对应单元格添加复选框。但点击Submit按钮时,submit_Click里的if(cb.Checked)抛出Object reference not set to an instance of an object错误。
我的代码:
.aspx页面
<div> <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" OnRowDataBound="GridView1_RowDataBound"> <Columns> <asp:BoundField DataField="Data" /> </Columns> </asp:GridView> </div> <asp:Button ID="submit" OnClick="submit_Click" Text="Submit" runat="server" /> <div runat="server" id="result"></div>
.aspx.cs页面
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { DataSet disciplines = GetData("Select * From discipline"); //GetData gets Sql table and returns a DataSet DataSet programs = GetData("Select * from program"); DataTable dt = new DataTable(); dt.Columns.Add("Data"); for (int i = 0; i < programs.Tables[0].Rows.Count; i++) { DataRow dr = dt.NewRow(); dr[0] = programs.Tables[0].Rows[i]["PROG_DESC"]; dt.Rows.Add(dr); } for (int i = 0; i < disciplines.Tables[0].Rows.Count; i++) { TemplateField tf = new TemplateField(); tf.HeaderText = disciplines.Tables[0].Rows[i]["DISCP_DESC"].ToString(); GridView1.Columns.Add(tf); } GridView1.DataSource = dt; GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if(e.Row.RowType == DataControlRowType.DataRow) { for(int i=1; i<GridView1.Columns.Count; i++) { CheckBox cb = new CheckBox(); cb.Checked = false; cb.ID = (e.Row.RowIndex*100+i).ToString(); e.Row.Cells[i].Controls.Add(cb); } } } protected void submit_Click(object sender, EventArgs e) { StringBuilder resultString = new StringBuilder(); //string that will contain the cell numbers of checked checkboxes int num = GridView1.Columns.Count; for (int i = 1; i < num; i++) { foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { CheckBox cb = (CheckBox)row.FindControl((row.RowIndex*100+i).ToString()); if (cb.Checked) { resultString.Append(row.RowIndex.ToString()); resultString.Append("\t"); resultString.Append((i - 1).ToString()); resultString.Append("\n"); } } } } result.InnerText = resultString.ToString(); }
问题原因与解决方案
这个错误的核心是ASP.NET回发时没有重新创建动态生成的控件。在ASP.NET页面生命周期中,动态添加的控件(包括GridView的列和复选框)需要在每次回发时重新构建,否则ViewState无法恢复这些控件的状态,FindControl就会返回null,访问它的属性自然会触发空引用错误。
修正步骤:
每次页面加载都重建动态列:
原来的代码只在首次加载时添加列,回发时列会丢失,导致GridView结构异常。需要把添加列的逻辑移到!IsPostBack判断外,同时每次加载前清空已有动态列,避免重复添加。保证回发时重新创建复选框并保留一致ID:
回发时RowDataBound会再次触发,这里要重新创建复选框,并且ID要和首次创建时完全一致,这样ViewState才能正确恢复复选框的选中状态。添加空值判断避免报错:
在访问复选框属性前,先判断控件是否存在,防止意外空引用。
修改后的完整代码:
protected void Page_Load(object sender, EventArgs e) { // 每次加载都重建动态列,确保回发时GridView结构一致 DataSet disciplines = GetData("Select * From discipline"); // 清空已有动态列(保留第一列固定列) while(GridView1.Columns.Count > 1) { GridView1.Columns.RemoveAt(1); } for (int i = 0; i < disciplines.Tables[0].Rows.Count; i++) { TemplateField tf = new TemplateField(); tf.HeaderText = disciplines.Tables[0].Rows[i]["DISCP_DESC"].ToString(); GridView1.Columns.Add(tf); } if (!IsPostBack) { // 仅首次加载时绑定数据 DataSet programs = GetData("Select * from program"); DataTable dt = new DataTable(); dt.Columns.Add("Data"); for (int i = 0; i < programs.Tables[0].Rows.Count; i++) { DataRow dr = dt.NewRow(); dr[0] = programs.Tables[0].Rows[i]["PROG_DESC"]; dt.Rows.Add(dr); } GridView1.DataSource = dt; GridView1.DataBind(); } } protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) { if(e.Row.RowType == DataControlRowType.DataRow) { for(int i=1; i<GridView1.Columns.Count; i++) { CheckBox cb = new CheckBox(); cb.ID = $"chk_{e.Row.RowIndex}_{i}"; // 更直观的ID命名,降低计算错误 e.Row.Cells[i].Controls.Add(cb); // 回发时ViewState会自动恢复选中状态,无需手动设置为false } } } protected void submit_Click(object sender, EventArgs e) { StringBuilder resultString = new StringBuilder(); int numColumns = GridView1.Columns.Count; foreach (GridViewRow row in GridView1.Rows) { if (row.RowType == DataControlRowType.DataRow) { for (int i = 1; i < numColumns; i++) { // 按ID查找复选框 CheckBox cb = (CheckBox)row.FindControl($"chk_{row.RowIndex}_{i}"); // 或者更可靠的方式:遍历单元格控件查找 // CheckBox cb = row.Cells[i].Controls.OfType<CheckBox>().FirstOrDefault(); if (cb != null && cb.Checked) { resultString.Append(row.RowIndex.ToString()); resultString.Append("\t"); resultString.Append((i - 1).ToString()); resultString.Append("\n"); } } } } result.InnerText = resultString.ToString(); }
关键说明:
- 每次
Page_Load都重建动态列,保证GridView在回发时的结构和首次加载完全一致; - 复选框ID采用更直观的命名规则,避免原代码中
row.RowIndex*100+i可能出现的冲突或计算错误; - 在
submit_Click中先判断cb != null,再访问cb.Checked,彻底避免空引用错误; - 回发时
RowDataBound重新创建的复选框,其选中状态会由ASP.NET自动通过ViewState恢复,无需手动设置。
这样修改后,就能正常获取复选框的选中状态,解决空引用报错问题了。
内容的提问来源于stack exchange,提问作者Chandan Maji
相关产品推荐
相关产品推荐

