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

Delphi 7 DBGrid的DrawColumnCell事件机制及访问冲突问题排查

问题成因与解决方案

DBGrid DrawColumnCell事件运行机制

DrawColumnCell是DBGrid每次需要重绘单元格时都会触发的事件,触发场景包括:

  • 窗口状态变化、焦点切换、单元格选中状态改变
  • 绑定的DataSet状态变化(打开、关闭、滚动、数据更新)
  • 代码主动调用Refresh、Invalidate等重绘方法
    注意:该事件的触发没有提前校验DataSet是否处于活跃状态,只要重绘触发就会执行你写的事件代码,这就是你报错的核心原因。

报错触发逻辑

你取消勾选复选框时的执行顺序是:

  1. 执行ckCasosAltaClick事件,第一步就调用cDataSet.Close关闭数据源
  2. DataSet关闭时,DBGrid自动触发重绘,执行DrawColumnCell事件
  3. 此时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 01:30:04