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

TDbCtrlGrid动态加载缩略图时OnPaintPanel调用Edit/Post致索引失步

TDbCtrlGrid OnPaintPanel中写库导致无限重绘问题

问题表现

需要在TDbCtrlGrid中实现缩略图懒加载:预加载全量缩略图耗时过长,因此计划在OnPaintPanel事件中为当前显示的面板生成对应缩略图并存入数据库,后续展示同一条记录时直接读取已存储的缩略图,避免重复生成。
实际在OnPaintPanel事件中执行数据集Edit/Post操作时,会出现RecNo与PanelIndex失去同步的问题:控件滚动条无限循环重定位,注释掉Edit/Post相关代码后控件行为恢复正常。

根本原因

OnPaintPanel属于TDbCtrlGrid内部绘制流程的触发事件,事件执行时控件正处于逐面板移动数据集游标、同步当前绘制面板索引与数据集RecNo对应关系的阶段。此时主动调用Edit/Post会触发数据集的状态变更、索引重算、数据感知控件刷新通知,直接打断控件原有的绘制同步流程,造成面板索引与数据集记录号错位;错位后控件会自动触发重绘尝试修正状态,重绘过程中又会触发Edit/Post逻辑再次造成错位,最终形成无限重绘循环。
如果数据集设置了活动索引(如复现代码中按Caption字段建立的索引),Post操作引发的记录排序位置变动会进一步放大游标错位的问题。

可行解决方案

  • 延迟执行写库逻辑,禁止在绘制事件上下文中直接操作数据集编辑
    OnPaintPanel中仅做读取判断:如果当前记录的缩略图字段为空,记录下该条记录的唯一标识(主键/RecNo),通过PostMessage向窗体发送自定义消息,等当前完整绘制流程结束、控件退出绘制状态后,再在自定义消息的处理函数中定位到对应记录,生成缩略图、执行Edit/Post操作,写入完成后触发控件对应区域重绘即可。需要增加重入判断标记,避免同一条记录重复触发缩略图生成流程。
  • 增加独立缓存层,异步生成写入缩略图
    分离缩略图生成、存储与绘制逻辑:OnPaintPanel触发时优先读取内存缓存中的缩略图,无缓存时将生成任务丢给后台线程异步处理,生成完成后先写入内存缓存、异步持久化到数据库,再通知主线程重绘对应面板。该方案完全不会阻塞UI绘制流程,也不会打断TDbCtrlGrid的游标同步逻辑,大数量下性能表现最优。
  • 临时屏蔽数据通知(仅用于快速验证,不推荐生产环境使用)
    执行Edit/Post前临时将关联DataSource的Enabled属性设为False,切断数据集向TDbCtrlGrid发送的状态变更通知,完成Post操作后再将Enabled设回True,强制控件重新同步全量记录位置。该方式属于硬中断通知链,数据量大、索引逻辑复杂时容易出现记录跳位、面板内容错位问题。

复现代码

Unit1.pas

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error,
  FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, Data.DB, Vcl.DBCGrids, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    FDMemTable1: TFDMemTable;
    DBCtrlGrid1: TDBCtrlGrid;
    DataSource1: TDataSource;
    Label1: TLabel;
    Label2: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure DBCtrlGrid1PaintPanel(DBCtrlGrid: TDBCtrlGrid; Index: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.DBCtrlGrid1PaintPanel(DBCtrlGrid: TDBCtrlGrid; Index: Integer);
begin
  Label1.Caption := FDMemTable1.FieldByName('caption').AsString;

//  取消注释以下三行会触发无限重绘
//  FDMemTable1.Edit;
//  FDMemTable1.FieldByName('thumbnail').AsString := FDMemTable1.FieldByName('caption').AsString;
//  FDMemTable1.Post;

  Label2.Caption := FDMemTable1.FieldByName('thumbnail').AsString;
end;



procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin
  with FDMemTable1 do
  begin
    LogChanges := False;
    CreateDataset;
    Open;
  end;

  with FDMemTable1 do
  try
    AddIndex('Caption', 'caption', '', []);
  finally
    IndexName := 'Caption';
  end;

  for i := 1 to 100 do
  begin
    FDMemTable1.Insert;
    FDMemTable1.FieldByName('caption').AsString := i.ToString;
    FDMemTable1.Post;
  end;

  FDMemTable1.First;
end;

end.

Unit1.dfm

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 939
  ClientWidth = 1245
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -12
  Font.Name = 'Segoe UI'
  Font.Style = []
  Position = poDesigned
  OnCreate = FormCreate
  TextHeight = 15
  object DBCtrlGrid1: TDBCtrlGrid
    Left = 245
    Top = 0
    Width = 1000
    Height = 939
    Align = alRight
    ColCount = 5
    DataSource = DataSource1
    PanelHeight = 187
    PanelWidth = 193
    TabOrder = 0
    RowCount = 5
    OnPaintPanel = DBCtrlGrid1PaintPanel
    ExplicitLeft = 474
    ExplicitHeight = 1429
    object Label1: TLabel
      Left = 0
      Top = 172
      Width = 193
      Height = 15
      Align = alBottom
      Alignment = taCenter
      Caption = 'Label1'
      ExplicitTop = 270
      ExplicitWidth = 34
    end
    object Label2: TLabel
      Left = 0
      Top = 0
      Width = 193
      Height = 172
      Align = alClient
      Alignment = taCenter
      Caption = 'Label2'
      Layout = tlCenter
      ExplicitLeft = 64
      ExplicitTop = 64
      ExplicitWidth = 34
      ExplicitHeight = 15
    end
  end
  object FDMemTable1: TFDMemTable
    FieldDefs = <
      item
        Name = 'Caption'
        DataType = ftString
        Size = 20
      end
      item
        Name = 'Thumbnail'
        DataType = ftString
        Size = 20
      end>
    IndexDefs = <>
    FetchOptions.AssignedValues = [evMode]
    FetchOptions.Mode = fmAll
    ResourceOptions.AssignedValues = [rvSilentMode]
    ResourceOptions.SilentMode = True
    UpdateOptions.AssignedValues = [uvCheckRequired, uvAutoCommitUpdates]
    UpdateOptions.CheckRequired = False
    UpdateOptions.AutoCommitUpdates = True
    StoreDefs = True
    Left = 112
    Top = 280
  end
  object DataSource1: TDataSource
    DataSet = FDMemTable1
    Left = 168
    Top = 368
  end
end

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:15:27