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

DataGridView排序后CellValueChanged触发NullReferenceException问题排查

问题原因分析

你遇到的NullReferenceException问题,根源在于每次排序时对DataTable结构的频繁修改(添加后立即移除sortMe列)。当执行m_dt.Columns.Remove("sortMe")时,DataTable会触发结构变更事件,干扰DataGridView与DataView的绑定状态——虽然表面排序功能正常,但在单元格值变更的时机,DataTable或DataRow的内部引用可能因为结构变更处于不稳定状态,导致写入操作时出现空引用。

而读取操作正常,是因为读取的是原始DataTable中已存在的有效行数据,未触发需要同步结构的逻辑,潜在问题没有暴露。

解决方案

核心思路是避免频繁修改DataTable的列结构:我们可以提前在DataTable中添加sortMe列,并将其设置为DataGridView的隐藏列,每次排序时只更新该列的值,无需重复添加/移除列,从而避免结构变更引发的绑定异常。

修改后的代码示例

1. 初始化阶段创建并隐藏sortMe列

在初始化DataTable时就添加sortMe列,绑定数据源后将该列隐藏:

private void Form1_Shown(object sender, EventArgs e)
{
    tlp1.RowStyles.Clear();
    for (var i = 0; i < 1; i++)
    {
        tlp1.RowStyles.Add(new RowStyle(SizeType.Absolute, 200));
        var gbNew = new GroupBox();
        gbNew.Text = "gb" + i;
        gbNew.Size = new Size(tlp1.Size.Width - 2, 185);
        gbNew.Anchor = AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Left;
        m_dgv = new DataGridView();
        m_dgv.Parent = gbNew;
        m_dgv.AutoSize = true;
        m_dgv.Location = new Point(5, 25);
        m_dgv.Size = new Size(gbNew.Size.Width - 10, gbNew.Size.Height - 70);
        m_dgv.Anchor = AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Left;
        
        m_dt = new DataTable();
        for (var j = 0; j < 2; j++)
        {
            var NewColumn = new DataColumn("Column" + j, typeof(Double));
            m_dt.Columns.Add(NewColumn);
        }
        // 提前添加排序专用列
        m_dt.Columns.Add("sortMe", typeof(Int32));
        
        var k = 5;
        for (var j = 0; j < 5; j++)
        {
            var NewRow = m_dt.NewRow();
            NewRow[0] = Convert.ToDouble(k--);
            NewRow["sortMe"] = 0; // 初始化排序标记
            m_dt.Rows.Add(NewRow);
        }
        m_dt.AcceptChanges();
        
        m_dgv.ColumnHeaderMouseClick += dgv_ColumnHeaderMouseClick;
        m_dgv.CellValueChanged += CellValueChangedHandler;
        m_dgv.DataSource = m_dt.DefaultView;
        // 隐藏排序专用列,不显示给用户
        m_dgv.Columns["sortMe"].Visible = false;
        
        tlp1.Controls.Add(gbNew);
    }
}

2. 修改sort_dgv方法,移除列添加/移除逻辑

现在排序时只需要更新sortMe的值并执行排序即可:

private void sort_dgv(String ColumnName, ListSortDirection Direction)
{
    var totalRow = m_dt.Rows[m_dt.Rows.Count - 1];
    // 重置所有行的排序标记
    foreach (DataRow row in m_dt.Rows)
    {
        row["sortMe"] = 0;
    }
    // 合计行设置最大排序值,确保始终处于末尾
    totalRow["sortMe"] = int.MaxValue;
    
    // 执行排序
    if (Direction == ListSortDirection.Descending)
    {
        m_dt.DefaultView.Sort = $"sortMe, {ColumnName} DESC";
    }
    else
    {
        m_dt.DefaultView.Sort = $"sortMe, {ColumnName}";
    }
}

3. 保留CellValueChangedHandler逻辑(无需修改)

现在DataTable结构稳定,单元格值变更逻辑可以正常执行:

private void CellValueChangedHandler(Object sender, DataGridViewCellEventArgs e)
{
    var temp = m_dt.Rows[0].Field<Double>(0);
    var totalRow = m_dt.Rows[m_dt.Rows.Count - 1];
    totalRow.SetField<Double>(0, totalRow.Field<Double>(0) + 1);
}
额外优化建议
  • 可以在初始化时就保存合计行的引用,避免每次通过m_dt.Rows[m_dt.Rows.Count-1]获取,提升代码效率和可读性。
  • 在排序前可以判断当前排序列和方向,避免重复执行相同的排序逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 05:38:56