批量更新数据引起 DataGrid 的绘制错误及解决方法

  1. 背景:
    在我们的应用中,经常使用 Windows.Forms.DataGrid 控件用于显示大批量的数据。
    然而,当我们在批量修改 DataGrid 所绑定数据源时,极大可能引发一个 DataGrid 的绘制异常,异常信息如下所示:

    System.NullReferenceException: Object reference not set to an instance of an object.
       at System.Data.DataColumnPropertyDescriptor.GetValue(Object component)
       at System.Windows.Forms.DataGridColumnStyle.GetColumnValueAtRow(CurrencyManager source, Int32 rowNum)

       at System.Windows.Forms.DataGridTextBoxColumn.Paint(Graphics g, Rectangle bounds, CurrencyManager source, Int32 rowNum, Brush backBrush, Brush foreBrush, Boolean alignToRight)
       at System.Windows.Forms.DataGridRelationshipRow.PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column, Brush backBr, Brush foreBrush, Boolean alignToRight)
       at System.Windows.Forms.DataGridRow.PaintData(Graphics g, Rectangle bounds, Int32 firstVisibleColumn, Int32 columnCount, Boolean alignToRight)
       at System.Windows.Forms.DataGridRelationshipRow.Paint(Graphics g, Rectangle bounds, Rectangle trueRowBounds, Int32 firstVisibleColumn, Int32 numVisibleColumns, Boolean alignToRight)
       at System.Windows.Forms.DataGrid.PaintRows(Graphics g, Rectangle& boundingRect)
       at System.Windows.Forms.DataGrid.PaintGrid(Graphics g, Rectangle gridBounds)
       at System.Windows.Forms.DataGrid.OnPaint(PaintEventArgs pe)
       at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)
       at System.Windows.Forms.Control.WmPaint(Message& m)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)


  2. 问题分析:
    经过各种解决方案的测试,最后发现是因为在批量更新 DataGrid 所绑定的数据源时,每次更新都会导致 Grid 发生重绘;而当数据更新速度非常快或者其它原因,导致 UI 线程来不及绘制完毕 DataGrid 时,Grid 就会发生绘制错误。
    请注意异常信息中加亮显示的部分。
  3. 解决方法:根据上面分析推测出来的原因,基本上就是尽量减少因为数据源的变更而导致 DataGrid 重新发生绘制,从而避免因绘制过程中出错。因此,使用下面的步骤,从而使问题得以解决。
    1. 从原来的数据源中Clone一个数据源出来,用来做 Save 操作的数据源,这样就使在Save 操作时,对数据源的更新与 DataGrid 所绑定的数据源分离开来,而不会导致 Grid 被刷新;
    2. 在Save 完毕后,断开 DataGrid 与数据源之间的绑定;
    3. 把 Clone 出来保存了 Save 的结果的数据源 Merge 回原来的数据源中,并更新相应的状态;
    4. 重新将 DataGrid 绑定到数据源,这将使 Grid 只发生一次绘制行为。
  4. 总结:
    我们在大量刷新控件绑定的数据源时,可能通过临时断开与数据源之间的绑定,抑制控件发生频繁重绘。在更新数据源完成后,再恢复控件与数据之间的绑定,从而使控件仅需绘制一次即可显示数据。
    由于 DataGrid 不提供与数据源断开的功能,例如 C1FlexGrid 可以使用 BeginInit() / EndInit() 方法对来提供暂时与数据源断开绑定/恢复绑定的功能,因此,需要手工处理其与数据源的断开/恢复功能,那就是移除  DataSource 属性的置,在数据源更新完毕后,再重新设置DataSource的值。
    事实上,Control基类提供了一个 public virtual 的方法对 BeginInit/EndInit,来提供这方面的支持,仅管 MSDN 文档中并没有如此说明,但在 DataGrid 中完全可以重写这两个方法提供这个功能。但遗憾的是,微软并没有这样做,这又增加了一个  DataGrid 被人诟病的理由,因此,在 .NET 2.0 中,微软新提供了一个替代控件 DataGridView,不过,本人还未仔细地去研究这个控件所提供的功能与改变。
posted on 2006-07-15 18:13  流浪狗  阅读(2081)  评论(2编辑  收藏  举报