Delphi 7 DBGrid的DrawColumnCell事件机制及访问冲突问题排查
问题成因与解决方案
DBGrid DrawColumnCell事件运行机制
DrawColumnCell是DBGrid每次需要重绘单元格时都会触发的事件,触发场景包括:
- 窗口状态变化、焦点切换、单元格选中状态改变
- 绑定的DataSet状态变化(打开、关闭、滚动、数据更新)
- 代码主动调用Refresh、Invalidate等重绘方法
注意:该事件的触发没有提前校验DataSet是否处于活跃状态,只要重绘触发就会执行你写的事件代码,这就是你报错的核心原因。
报错触发逻辑
你取消勾选复选框时的执行顺序是:
- 执行ckCasosAltaClick事件,第一步就调用
cDataSet.Close关闭数据源 - DataSet关闭时,DBGrid自动触发重绘,执行DrawColumnCell事件
- 此时ckCasosAlta已经是未勾选状态,代码进入else分支,尝试访问
Column.Field.FieldName、Column.Field.AsDateTime,但DataSet已经关闭,关联的Field对象已经被销毁,访问空指针直接触发访问冲突。
第一次勾选时未报错属于偶发情况,勾选分支中cDataSet.FieldByName('fechaalta').Value <> null的判断本身也存在空指针访问风险。
修复方案
第一步:在DrawColumnCell最开头加DataSet活跃校验
所有访问Field的操作前,先判断DataSet是否打开、字段对象是否有效:
procedure TfmForm.gdGridDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var Fld: TField; begin // 先校验基础状态,避免空指针 if not cDataSet.Active or (Column.Field = nil) then begin gdGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State); Exit; end; if ckCasosAlta.Checked then begin dtFechaProbableAlta.Visible:=False; Fld := cDataSet.FindField('fechaalta'); if (Fld <> nil) and not Fld.IsNull then begin gdGrid.Canvas.Brush.Color:=clLime; gdGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State); end; end else begin if (gdSelected in State) and (Column.Field.FieldName = 'fechaprobablealta') then begin with dtFechaProbableAlta do begin Left:= Rect.Left + gdGrid.Left+1; Top:= Rect.Top + gdGrid.Top+1; Width:= Rect.Right - Rect.Left+ 2; Height:= Rect.Bottom - Rect.Top +2; if not Column.Field.IsNull then Date:= Column.Field.AsDateTime else Date:= Now; // 给默认值避免空值报错 Visible:=True; end; end else begin // 非目标选中单元格隐藏日期控件,避免控件停留在旧位置 dtFechaProbableAlta.Visible := False; end; end; end;
第二步:优化复选框点击事件的执行顺序
关闭DataSet前先隐藏日期控件,避免重绘过程中控件状态异常:
procedure TfmForm.ckCasosAltaClick(Sender: TObject); begin // 先隐藏控件,再操作DataSet dtFechaProbableAlta.Visible := False; if ckCasosAlta.Checked then begin chkCasosProbableAlta.Checked:=False; cDataSet.Close; qDataSet.CommandText:= '对应SQL查询语句'; end else begin cDataSet.Close; qDataSet.CommandText:= '对应SQL查询语句'; end; cDataSet.Open; // Open操作已经会加载最新数据,不需要额外调用Refresh end;
内容的提问来源于stack exchange,提问作者Lucho
相关产品推荐
相关产品推荐

