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
相关产品推荐
相关产品推荐

