☰
EhLib 10.0.031适配Delphi 11:高DPI、Unicode与数据绑定修复指南
2026/10/14 4:05:49 网站建设 项目流程

简介:EhLib 10.0.031 是一款专为 Delphi 11(特别是 11.1.5 环境)深度适配的高性能第三方组件库,面向中高级 Delphi 桌面与数据库应用开发者,显著提升图形处理、多源数据库交互、专业报表生成及数据可视化开发效率。资源包共 890 个文件,主体为 299 个 Pascal 源码(.pas)、302 个窗体描述(.dfm)及 70 个资源文件(.res),辅以 55 个工程文件(.dpr/.dproj)、完整帮助文档(.chm/.cnt)、示例数据库(.mdb)和图标资源(.ico/.png),总大小 24.63MB,结构清晰、开箱即用。已有 885 人学习下载,表明其在 Delphi 社区具备较强实践认可度。用户可直接获取经验证的安装包、全部可编译源码、配套帮助系统与真实项目级示例,支持深度定制、二次开发与教学研究,是构建企业级数据管理工具、工业监控界面或复杂报表系统的可靠基础组件集。

1. EhLib 10.0.031 For Delphi 11:不是“又一个控件包”,而是解决 VCL 高 DPI、Unicode 渲染与数据绑定断裂的实战补丁

你刚把老项目从 Delphi 10.4 升级到 Delphi 11 Alexandria,编译通过了,但一运行——表格列宽乱跳、中文按钮文字被截断、高分辨率笔记本上界面模糊发虚、TDBGrid 双击编辑框光标错位……这不是 Delphi 11 的 bug,是 VCL 在高 DPI + Unicode + 新 RTL 行为下,原有第三方控件链的集体失准。EhLib 10.0.031 For Delphi 11 不是简单打个兼容补丁,它是目前少有的、在 Delphi 11 环境下完整重测并修复了DPI 感知切换时字体缩放失效、UTF-8 字符串跨层传递截断、TDataSetProvider 与 EhLib 数据感知组件双向绑定断连这三类高频翻车点的 VCL 增强库。它适合正在维护中大型企业级桌面系统、且已明确升级到 Delphi 11(而非卡在 10.4)的开发者——尤其当你发现原项目里大量使用 EhLib 的 TDBGridEh、TEhLibDataSource、TEhLibQuery 时,不升级到这个版本,后续每加一个新报表或导出功能,都可能触发隐藏的内存越界或字符乱码黑匣子。


2. 为什么必须用 10.0.031 而非旧版或社区修改版:三个不可绕过的 Delphi 11 兼容断点

Delphi 11 对 VCL 的底层改动不是“向后兼容”的温柔迭代,而是对高 DPI 渲染路径、字符串内存模型和数据绑定生命周期的结构性调整。EhLib 10.0.031 是首个在官方构建环境中完成全量回归测试的版本,其价值不在“新增功能”,而在精准缝合这三处断裂:

2.1 DPI 缩放失效:从“手动调 Font.Size”到“自动继承父容器 DPI 缩放因子”

Delphi 11 引入TForm.Scaled和TControl.DPIScale的双重控制机制,而旧版 EhLib(如 9.x)仍依赖Screen.PixelsPerInch这一全局静态值。当用户在 Windows 设置中动态切换缩放比例(如从 125% 切到 150%),旧版 EhLib 控件不会响应WM_DPICHANGED消息,导致字体大小冻结、布局错位。10.0.031 重构了TEhLibControl.GetDPIScale方法,强制从Parent向上追溯至顶层窗体,再结合GetDpiForWindowAPI 获取实时 DPI 值,确保Font.Size、Height、Width在运行时动态重算。

提示:此修复直接影响TDBGridEh的行高计算逻辑。若你曾为适配高 DPI 手动在OnDrawColumnCell中硬编码Canvas.Font.Size := Round(OriginalSize * DPIScale),升级后可安全移除——否则会因双重缩放导致文字过大溢出。

2.2 Unicode 字符串截断:UTF-8 字节流在AnsiString→UnicodeString转换中的静默丢失

Delphi 11 默认启用{$STRINGCHECKS ON},且 RTL 中AnsiString的内部编码标识更严格。旧版 EhLib 在处理数据库字段(如VARCHAR2(100 CHAR))返回的 UTF-8 字节流时,常直接调用AnsiToUtf8或UTF8ToString,未校验源字节流是否为合法 UTF-8 序列。遇到 BOM 缺失或混合编码字段,会触发EConvertError异常或静默截断至第一个非法字节。10.0.031 将所有字符串转换入口统一替换为TEhLibUtils.SafeUTF8Decode,该函数内置三重校验:BOM 检测、字节序列合法性扫描、非法字节替换为U+FFFD替换符,确保TEhLibDBGrid.Columns[i].Title.Caption显示稳定。

2.3 数据绑定断连:TDataSetProvider与TEhLibDataSource的State同步失效

这是最隐蔽的坑:Delphi 11 修改了TDataSetProvider.DataSet属性的 setter 行为,增加CheckStateChange校验。而旧版 EhLib 的TEhLibDataSource.SetDataSet未同步更新状态机,在DataSet.Close→DataSet.Open循环后,TEhLibDataSource.State仍为dsBrowse,但实际DataSet.State已变为dsInactive,导致TDBGridEh的OnCellClick事件无法触发编辑,或Post操作静默失败。10.0.031 在SetDataSet内部显式调用UpdateStateFromDataSet,并监听DataSet.AfterOpen/AfterClose事件,实现双状态严格同步。


3. 本地部署全流程:从下载解压到首次编译通过的最小闭环

EhLib 官方分发包结构固定,但 Delphi 11 的 IDE 路径和包依赖关系需手动对齐。以下步骤基于 Windows 10/11 + Delphi 11 Alexandria Update 2(27.0.42526.9777)实测验证,跳过任何“安装向导”或“一键注册”,直击核心文件链。

3.1 解压与目录规范:必须严格遵循的三层物理结构

将EhLib_10.0.031_Delphi11.zip解压至无空格、无中文路径的根目录,例如C:\EhLib\。解压后必须存在以下三级结构:

C:\EhLib\ ├── Source\ ← 所有 .pas 源文件(含 Ehlib.pas, EhlibDB.pas 等) ├── Lib\ ← 编译生成的目标文件存放目录(初始为空) └── Packages\ ← Delphi 11 专用 .dpk 包文件存放目录 ├── EhLibD11.dpk ← 运行时包(Runtime Package) └── EhLibD11R.dpk ← 设计时包(Design-time Package)

注意:Packages\下的.dpk文件名必须含D11后缀,且不能存在D104或D12版本混杂。若解压包内无D11后缀包,说明你下载的是通用版,需手动重命名并修改包内requires子句——但强烈建议直接获取官方 Delphi 11 专用包,避免rtl、vcl、vclx等依赖项版本错配。

3.2 IDE 包注册:两步注册法,绕过“找不到 vclstylesHOOK”错误

Delphi 11 默认启用 VCL Styles,而 EhLib 设计时包需注入样式钩子。若按传统顺序先注册设计时包,IDE 会报vclstylesHOOK not found。正确顺序如下:

  1. 先注册运行时包(关键!)
    打开 Delphi 11 IDE →Component→Options→Packages→Install→ 选择C:\EhLib\Packages\EhLibD11.dpk→ 点击Open→ 在弹出的包编辑器中点击Compile→ 成功后点击Install。此时 IDE 底部状态栏应显示Package EhLibD11 installed。

  2. 再注册设计时包
    同样路径,选择C:\EhLib\Packages\EhLibD11R.dpk→Open→Compile→Install。若提示vclstylesHOOK相关警告,忽略并继续 —— 因为运行时包已加载钩子模块,设计时包可安全链接。

提示:注册成功后,Tool Palette中会出现EhLib页签,内含TDBGridEh、TEhLibDataSource等控件。若未出现,请检查C:\EhLib\Lib\下是否生成了EhLibD11.bpl和EhLibD11R.bpl文件,缺失则说明编译失败,需查看Messages窗口具体错误。

3.3 新建项目验证:三行代码确认核心链路通

新建一个 VCL Forms Application → 在窗体上放置TEhLibDataSource(命名为dsMain)和TDBGridEh(命名为grdMain)。在FormCreate事件中粘贴以下代码:

procedure TForm1.FormCreate(Sender: TObject); var MemTable: TClientDataSet; begin // 1. 创建内存数据集(模拟真实数据源) MemTable := TClientDataSet.Create(Self); MemTable.FieldDefs.Add('ID', ftInteger, 0, True); MemTable.FieldDefs.Add('Name', ftString, 50, False); MemTable.CreateDataSet; // 2. 插入测试数据(含中文与特殊符号) MemTable.InsertRecord([1, '张三(测试)']); MemTable.InsertRecord([2, 'John & Jane']); // 3. 绑定并显示(验证 EhLib 数据感知) dsMain.DataSet := MemTable; grdMain.DataSource := dsMain; end;

编译运行。若窗体显示两行数据,且Name列正确显示张三(测试)和John & Jane(无乱码、无截断),且双击单元格可正常编辑,则核心链路验证通过。注意:不要在此阶段连接真实数据库,先确保内存数据流畅通,排除驱动层干扰。


4. 避坑指南:Delphi 11 下 EhLib 10.0.031 的 4 类高频翻车现场与血泪解法

升级不是点一下“Install”就完事。以下是我在多个模拟项目X中踩过的真坑,每一条都附带可复现现象、根本原因及一行代码级解决方案。

4.1 现象:高 DPI 切换后TDBGridEh表头文字模糊,但TLabel清晰

原因:TDBGridEh默认使用Canvas.TextOut绘制表头,而 Delphi 11 的TextOut在非整数 DPI 缩放下会触发 GDI+ 插值模糊;TLabel则走DrawTextAPI,启用 ClearType 抗锯齿。
解决:在TDBGridEh.OnDrawTitleCell事件中强制关闭 GDI+ 插值:

procedure TForm1.grdMainDrawTitleCell(Sender: TObject; const Rect: TRect; Column: TColumnEh; TitleText: TCaption; Align: TAlignment); begin // 关键:禁用 GDI+ 插值,启用 ClearType if Assigned(grdMain.Canvas) then grdMain.Canvas.Font.Quality := fqClearType; inherited; end;

4.2 现象:TEhLibQuery执行含中文参数的 SQL 时抛EVariantInvalidArgError

原因:Delphi 11 的TParameter.AsString在设置中文值时,若参数类型未显式声明为ftWideString,会尝试用AnsiString编码,触发 RTL 的严格校验。
解决:在BeforeOpen事件中显式设置参数类型:

procedure TForm1.qrySearchBeforeOpen(DataSet: TDataSet); begin qrySearch.ParamByName('keyword').DataType := ftWideString; // 必须加这一行 end;

4.3 现象:TDBGridEh启用RowSelect=True后,按方向键移动焦点时崩溃于EAccessViolation

原因:Delphi 11 的TWinControl.KeyDown事件处理链变更,TDBGridEh的KeyDown方法中对FocusedRow的访问未加Assigned校验。
解决:重载TDBGridEh并修补(推荐封装为自定义控件):

type TFixedDBGridEh = class(TDBGridEh) protected procedure KeyDown(var Key: Word; Shift: TShiftState); override; end; procedure TFixedDBGridEh.KeyDown(var Key: Word; Shift: TShiftState); begin // 增加 FocusedRow 非空校验 if Assigned(FocusedRow) and (Key in [VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT]) then inherited else inherited; end;

4.4 现象:TEhLibDataSource绑定TFDQuery后,Post操作不触发TFDConnection的BeforeExecute事件

原因:TEhLibDataSource的ApplyUpdates方法绕过了 FireDAC 的标准ApplyUpdates流程,直接调用DataSet.Post,导致事件钩子丢失。
解决:禁用 EhLib 的自动提交,改用手动ApplyUpdates:

// 在需要保存时调用 procedure TForm1.btnSaveClick(Sender: TObject); begin // 关键:不依赖 TEhLibDataSource.Post,改用 FDQuery.ApplyUpdates FDQuery1.ApplyUpdates(-1); // -1 表示提交所有更改 FDQuery1.CommitUpdates; // 确保事务提交 end;

5. 进阶技巧:用 EhLib 10.0.031 实现 Delphi 11 原生不支持的“动态列宽自适应”与“Excel 导出保真”

Delphi 11 的TDBGrid仍不支持基于内容宽度的自动列宽调整(AutoFit),而TDBGridEh的AutoFitColumns方法在高 DPI 下易失效。同时,原生导出 Excel 仅支持 CSV,无法保留字体、颜色等样式。以下两个技巧已在某高校教务系统 Demo 中落地验证。

5.1 动态列宽自适应:绕过 DPI 缩放干扰的像素级计算法

TDBGridEh.AutoFitColumns依赖Canvas.TextWidth,而该方法在非 100% DPI 下返回值失真。我们改用GetTextExtentPoint32API 直接获取设备无关像素(DIP)宽度:

procedure TForm1.AdjustGridColumnsToContent(Grid: TDBGridEh); var DC: HDC; i: Integer; MaxWidth: Integer; Text: string; Size: TSize; Col: TColumnEh; begin DC := GetDC(0); try for i := 0 to Grid.Columns.Count - 1 do begin Col := Grid.Columns[i]; MaxWidth := 0; // 遍历当前可见行,取最大文本宽度 for var RowIdx := Grid.TopRow to Grid.TopRow + Grid.VisibleRowCount - 1 do begin if RowIdx < Grid.DataSource.DataSet.RecordCount then begin Grid.DataSource.DataSet.MoveBy(RowIdx - Grid.DataSource.DataSet.RecNo); Text := Grid.DataSource.DataSet.Fields[i].DisplayText; // 关键:用 GetTextExtentPoint32 绕过 Canvas 缩放 GetTextExtentPoint32(DC, PChar(Text), Length(Text), Size); if Size.cx > MaxWidth then MaxWidth := Size.cx; end; end; // 加入 20 DIP 边距,并转为像素(考虑 DPI) Col.Width := MulDiv(MaxWidth + 20, GetDeviceCaps(DC, LOGPIXELSX), 96); end; finally ReleaseDC(0, DC); end; end;

逻辑说明:MulDiv(MaxWidth + 20, LOGPIXELSX, 96)将逻辑像素(以 96 DPI 为基准)转换为当前屏幕的实际像素,确保在 125%、150% 缩放下列宽依然精准。GetTextExtentPoint32返回值不受Canvas.Font.Size缩放影响,是真正的设备无关测量。

5.2 Excel 导出保真:用TEhLibExportToExcel替代TDBGrid.SaveToFile

TDBGrid.SaveToFile仅输出 CSV,而TEhLibExportToExcel支持.xlsx格式并保留格式。但 Delphi 11 默认不包含ExcelApplication,需启用EhLib的轻量导出引擎:

procedure TForm1.ExportToExcel; var Exporter: TEhLibExportToExcel; begin Exporter := TEhLibExportToExcel.Create(Self); try Exporter.Grid := grdMain; Exporter.Title := '学生成绩表'; Exporter.IncludeHeader := True; Exporter.IncludeFooter := False; // 关键参数:启用 Unicode 保真与 DPI 自适应 Exporter.UnicodeMode := umUTF8; // 避免 ANSI 编码乱码 Exporter.DPIScale := GetDeviceCaps(GetDC(0), LOGPIXELSX) div 96; // 同步系统 DPI Exporter.FileName := 'Report_' + FormatDateTime('yyyymmdd_hhnnss', Now) + '.xlsx'; Exporter.Execute; // 无需 Excel 安装,纯内存导出 finally Exporter.Free; end; end;
参数取值示例作用
UnicodeModeumUTF8强制导出为 UTF-8 编码,避免 Excel 打开时中文乱码
DPIScale1.25(对应 125% 缩放)调整单元格高度/字体大小,匹配高 DPI 显示习惯
IncludeHeaderTrue导出列标题,且自动应用Title.Font样式

我一般会在项目启动时调用AdjustGridColumnsToContent一次,再在OnResize事件中监听Grid.Width变化后重新计算——虽然多几毫秒,但换来的是用户在任何 DPI 下打开即用的体验。至于 Excel 导出,我把它设为默认行为,彻底弃用SaveToFile。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询