在使用 EhLib 中的 TDBGridEh 控件时,常遇到“固定左边列后数据错位”的问题:当设置 `FrozenCols` 属性固定左侧若干列后,滚动右侧数据列时,部分单元格数据显示位置偏移,甚至出现空白或重复内容。该问题多出现在列宽自适应、字体缩放或 DPI 设置不一致的场景下,本质是控件重绘逻辑与列布局计算不同步所致。如何在保持列固定功能的同时确保数据正确对齐?这是开发者普遍关注的技术难点。
1条回答 默认 最新
蔡恩泽 2025-10-14 05:30关注一、问题现象与初步定位
在使用
TDBGridEh控件时,开发者常通过设置FrozenCols = N来固定左侧 N 列,实现表头或关键字段的锁定。然而,在实际运行中,当用户水平滚动右侧非冻结列时,经常出现以下异常:- 数据单元格内容错位,显示到错误的列位置
- 部分单元格空白,无数据显示
- 相同数据重复渲染,疑似重绘缓存未清除
- 滚动后视觉“撕裂”,布局短暂失同步
该现象在高 DPI 显示器(如 150% 缩放)、字体动态调整、或列宽设置为
AutoSize = True时尤为明显。初步判断为控件在处理冻结区域与可滚动区域的绘制边界时,列偏移量计算存在误差。二、底层机制分析
TDBGridEh将网格划分为两个逻辑视口:视口类型 功能描述 相关属性 Frozen View 固定列区域,不随水平滚动移动 FrozenCols, FrozenWidth Scrolling View 可滚动列区域,响应水平偏移 HorzScrollBar.Position 控件在
Paint阶段调用DrawDataCell方法绘制每个单元格。关键问题出现在:- 列索引映射错误:
FieldByColumn(VisibleColIndex)获取的字段与实际绘制列不匹配 - DPI 变化导致
Canvas.TextWidth计算偏差,进而影响列宽累积值 - 双缓冲关闭时,
BeginUpdate/EndUpdate期间未正确刷新布局缓存
三、常见触发场景与复现路径
以下是典型可复现该问题的配置组合:
// 示例代码:易出错配置 DBGridEh1.FrozenCols := 2; DBGridEh1.Font.Size := 10; // 高DPI下自动缩放 DBGridEh1.DataSource := DataSource1; DBGridEh1.AutoSizeColumns(True); // 触发宽度重算 DBGridEh1.DoubleBuffered := False; // 默认可能为False当窗体在 125% 或 150% DPI 下启动,并执行
Refresh或Resize操作时,错位概率显著上升。四、解决方案层级递进
根据问题深度,提出四级应对策略:
- 一级:强制刷新布局 — 调用
ResetFieldBounds确保列边界同步 - 二级:启用双缓冲 — 设置
DoubleBuffered := True减少闪烁与重绘错乱 - 三级:重写绘制事件 — 使用
OnDrawDataCell手动控制文本输出位置 - 四级:定制控件派生类 — 继承
TDBGridEh并重载Paint方法
五、核心修复代码示例
procedure TForm1.DBGridEh1DrawDataCell(Sender: TObject; const Rect: TRect; Field: TField; State: TGridDrawState); var ColIndex: Integer; DrawRect: TRect; begin ColIndex := (Sender as TDBGridEh).ColumnFromField(Field); DrawRect := Rect; // 手动校正X偏移,避免因FrozenCols导致的错位 if ColIndex >= (Sender as TDBGridEh).FrozenCols then OffsetRect(DrawRect, -(Sender as TDBGridEh).HorzScrollBar.Position, 0); // 安全校验绘制区域有效性 if DrawRect.Right > DrawRect.Left then (Sender as TDBGridEh).Canvas.TextRect(DrawRect, DrawRect.Left + 2, DrawRect.Top + 2, Field.DisplayText); end;六、流程图:绘制同步机制优化路径
graph TD A[用户滚动水平条] --> B{FrozenCols > 0?} B -->|Yes| C[计算冻结区总宽度] B -->|No| D[正常绘制全部列] C --> E[获取HorzScroll偏移量] E --> F[调整非冻结列绘制起始X坐标] F --> G[逐列调用OnDrawDataCell] G --> H[手动OffsetRect校正位置] H --> I[完成同步绘制]七、高级调试技巧
可通过以下方式深入诊断:
- 启用
EhLib调试日志,监控CalcColumnBounds输出 - 在
WM_PAINT中插入断点,观察ClipRect范围 - 使用 Spy++ 检查实际窗口绘制消息序列
- 对比不同 DPI 下
Screen.PixelsPerInch与Canvas.PenPos的一致性
建议在开发阶段启用
{$DEFINE EH_DEBUG_LAYOUT}宏,输出列布局快照。八、预防性设计规范
为避免此类问题,推荐以下编码实践:
最佳实践 说明 始终设置 DoubleBuffered := True 防止重绘撕裂 避免在 OnDrawCell 中修改 Column.Width 防止递归布局计算 初始化后调用 ResetFieldBounds 确保 DPI 自适应正确 禁用 AutoSizeColumns 在运行时频繁调用 改用定时器节流 使用 BeginUpdate/EndUpdate 包裹批量操作 减少无效重绘 本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报