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

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,访问它的属性自然会触发空引用错误。

修正步骤:

  1. 每次页面加载都重建动态列:
    原来的代码只在首次加载时添加列,回发时列会丢失,导致GridView结构异常。需要把添加列的逻辑移到!IsPostBack判断外,同时每次加载前清空已有动态列,避免重复添加。

  2. 保证回发时重新创建复选框并保留一致ID:
    回发时RowDataBound会再次触发,这里要重新创建复选框,并且ID要和首次创建时完全一致,这样ViewState才能正确恢复复选框的选中状态。

  3. 添加空值判断避免报错:
    在访问复选框属性前,先判断控件是否存在,防止意外空引用。

修改后的完整代码:

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:11:01