ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EhLib 10.0.031 在 Delphi 11 中的安装配置与性能优化实战

EhLib 10.0.031 在 Delphi 11 中的安装配置与性能优化实战

简介:EhLib 10.0.031 是面向 Delphi 11 开发者的第三方组件库资源,适合需要快速构建图形处理、数据库应用与报表系统的中高级开发者使用。该版本已在 Delphi 11.1.5 环境下验证可正常安装运行,核心价值在于提供图像查看与格式转换、多数据库连接与查询、报表设计预览打印、图表可视化以及工具栏菜单等界面增强组件,并附带完整源码便于二次开发与功能扩展。压缩包共 890 个文件,以 302 个 dfm 窗体文件、299 个 pas 源码单元、70 个 res 资源文件、55 个 dpr 工程文件及 30 个 dcu 编译单元为主,另含 doc、chm 帮助文档与示例数据库,整体约 24.63MB,安装程序位于 install 文件夹内。目前已有 886 人学习下载,读者可据此获得一套可直接集成到 Delphi 11 项目的组件源码与配套文档,用于理解组件实现原理、按项目需求裁剪修改,并借助示例快速上手图形、数据库与报表相关功能。

1. EhLib 10.0.031 在 Delphi 11 里到底能省多少事

上周有个做设备管理系统的朋友发来一段录屏:一个用 Delphi 11 编译的老项目,表格控件滚动到第 3000 行时开始卡顿,筛选一列要等两秒,导出 Excel 直接卡死。他问我有没有必要换控件库。我让他先别急着重构,把 EhLib 10.0.031 挂上去试一轮——这个版本对 Delphi 11 的适配是完整的,装完替换掉原来的 TDBGrid,同一份数据源、同一套字段绑定,滚动和筛选的体感差异当场就能看出来。EhLib Delphi 这套组件在国内的 Delphi 圈子里属于「老牌但低调」的存在,它不解决业务逻辑,专治数据感知控件的显示与交互:网格、树、下拉、打印、导出,都是围绕数据集做文章。适合谁?手上有一堆 VCL 老项目、不想大改架构、又想让表格体验跟得上现在的用户预期的人。这篇不聊虚的,从安装到参数到翻车点,按我实际拆包复现的顺序走一遍。

2. 装进 Delphi 11:包结构、编译顺序与第一个网格

2.1 先看清包里有什么,别急着点安装

拿到 EhLib 10.0.031 的包,第一件事不是双击,而是把目录结构过一遍。常见做法是分三层:Common放跨版本共享的单元,Delphi11放该版本专用的包工程和编译产物,Demos放示例。真正要编译的是.dpk设计期包和对应的运行期包,运行期包必须先编,设计期包依赖它。我一般会先把整个目录复制到非系统盘、路径不含中文和空格的目录下,比如D:\Libs\EhLib10031,因为 Delphi 的包搜索路径对空格和中文的容忍度很低,路径里带一个空格,后面报「找不到单元」能查到你怀疑人生。

包工程里通常能看到类似EhLib110.dpk(运行期)和EhLib110D.dpk(设计期)这样的命名,数字对应 Delphi 版本。编译顺序固定:先运行期、再设计期。设计期包编译成功后点 Install,组件面板上才会出现对应页签。这里有个细节:如果之前装过旧版 EhLib,务必先在 IDE 的「Install Packages」里把旧的设计期包卸载掉,再删掉旧的.bpl,否则两个版本的设计期包同时注册,组件面板会出现重名项,拖到窗体上编译能过、运行时却报类找不到,这种玄学问题八成是残留包导致的。

2.2 编译与安装的可复现步骤

下面这套流程我在 Delphi 11 上走过不止一次,按顺序执行基本不会出岔子。命令行的部分用msbuild更稳,避免 IDE 里手动点编译时被缓存干扰。

# 1. 进入运行期包所在目录(路径按实际调整,不要带空格) cd /d D:\Libs\EhLib10031\Delphi11 # 2. 先编译运行期包,Release 配置,Win32 目标 msbuild EhLib110.dproj /p:Config=Release /p:Platform=Win32 /t:Build # 3. 再编译设计期包 msbuild EhLib110D.dproj /p:Config=Release /p:Platform=Win32 /t:Build # 4. 确认输出目录里生成了 .bpl 和 .dcp dir /b *.bpl *.dcp

逻辑说明:/t:Build是完整重建,比增量编译更能暴露路径缺失问题;/p:Platform=Win32必须显式给,Delphi 11 默认可能停在 Win64,而很多老项目的第三方依赖只有 32 位版本,平台对不上会在链接阶段报一堆符号未解析。第 4 步是自检,.bpl是运行期二进制包,.dcp是编译期需要的符号文件,两个都在才算编对了。如果只出.dcp不出.bpl,多半是包工程里的Requires列表引用了本机不存在的包,打开.dpk源码看requires段逐个核对。

编译通过后,在 IDE 里Component → Install Packages → Add,选中设计期.bpl,组件面板出现 EhLib 页签即成功。接着建一个最小验证工程:放一个TFDConnection或TADOConnection连到任意测试库,放一个TDataSource,再拖一个TDBGridEh绑上去,DataSource指过去,Columns里点「Add All Fields」。能正常显示数据、能点列头排序,说明基础链路通了。这一步别省,后面所有高级功能都建立在这个最小闭环上。

2.3 搜索路径与单元引用

装完包不等于工程能编译。新建的工程如果直接uses DBGridEh,IDE 可能报找不到单元,原因是库路径没配。在Tools → Options → Language → Delphi → Library里,把D:\Libs\EhLib10031\Common和对应版本的编译单元输出目录加进Library path(Win32 和 Win64 分开配)。我习惯把这两个路径也写进项目的.dproj里作为相对路径备份,团队协作时别人拉代码不用再手动配一遍。单元引用上,网格用DBGridEh,下拉用DBLookupComboboxEh,打印相关在PrinterEh一族单元里,按需引用,别一股脑全uses进来拖慢编译。

3. 网格参数怎么调:从列宽到大数据量渲染

3.1 列、标题与自适应宽度

TDBGridEh的属性多到让人第一眼发怵,但真正高频的就那么几组。列级配置在Columns集合里,每个TColumnEh有Width、Title.Caption、Alignment、ReadOnly、Visible。标题多行显示靠Title.TitleButton和Title.ToolTips,标题换行则设Title.WordWrap := True并给足Title.RowCount。自适应宽度是我用得最多的:AutoFitColWidths := True让列宽按内容撑开,但它对超长文本不友好,会把列撑到屏幕外,所以通常配合Columns[i].MaxWidth设上限,常见做法是文本列给 300、备注列给 400,超过就靠ShowHint或单元格内省略号处理。

行高和字体要一起调,只改一个会显得挤。RowHeight默认偏小,中文环境下我一般设到 22 到 26 之间,同时把Font.Size提到 9 或 10,Title.Font.Style加粗。这里有个容易忽略的点:RowHeight设了但RowLines没开,视觉上还是平的,想要分隔线得把Options里的dgRowLines打开。这些属性在对象观察器里点一遍就能看到效果,但建议最终固化成代码,方便多窗体复用:

// 统一网格外观,放在窗体创建后调用 procedure TForm1.SetupGrid(Grid: TDBGridEh); begin Grid.Options := Grid.Options + [dgRowLines, dgColLines, dgTitles, dgIndicator]; Grid.RowHeight := 24; // 中文行高,低于 20 会挤 Grid.TitleHeight := 28; // 标题行单独给高,放两行标题时够用 Grid.Font.Size := 9; Grid.Title.Font.Style := [fsBold]; Grid.AutoFitColWidths := True; // 先自适应,再用 MaxWidth 兜底 Grid.FrozenCols := 1; // 冻结首列,横向滚动时定位用 end;

参数说明:dgIndicator是行首指示列,配合IndicatorOptions可以显示当前行标记;FrozenCols冻结左侧若干列,宽表横向滚动时首列(通常是编号或名称)始终可见,这个在设备台账类界面里几乎是刚需。AutoFitColWidths打开后每次数据刷新都会重算列宽,数据量大时有额外开销,如果表格是只读展示、列宽固定,建议关掉它改用显式Width,省下的时间在万行级数据上能感觉到。

3.2 大数据量下的渲染与分页取舍

EhLib 本身不做数据分页,它渲染的是数据集当前可见的部分,所以「卡不卡」很大程度取决于数据集怎么取。我踩过的坑是:有人把TFDQuery的FetchOptions.Mode设成fmAll,一次性把十万行拉进内存,然后怪网格慢。正确做法是让数据集按需取,fmOnDemand配合合理的RowsetSize(比如 200),网格滚动时再增量取。EhLib 的网格在滚动时会触发数据集的移动,只要数据集支持单向游标,这个链路是顺的。

另一个影响体感的是Options里的dgEditing和dgAlwaysShowEditor。编辑模式下每个单元格都可能创建编辑控件,行数多时开销明显。只读展示的界面把dgEditing去掉,滚动会顺很多。还有DrawMemoText、ShowMemoText这类和备注字段相关的开关,备注列如果不需要在格子里展开,关掉能省一次文本测量。这些不是玄学,是实打实的绘制路径差异,我在一个两万行的日志查看界面上对比过,关掉编辑和备注展开后首屏渲染时间从肉眼可感的停顿降到基本无感。

3.3 排序、筛选与数据集联动

列头点击排序依赖数据集的排序能力,网格只负责发信号。TDBGridEh的Options里dgColumnResize、dgColumnMove管交互,排序则要看Title.SortMarker和数据集自身的IndexFieldNames或Sort属性。常见做法是在OnTitleBtnClick里根据点击列切换升序降序,然后设置数据集的排序字段再Refresh。如果底层是TClientDataSet,用IndexFieldNames最快;如果是TFDQuery,改ORDER BY需要重新打开查询,代价大,通常改用内存排序或服务端分页。

筛选方面,EhLib 提供了STFilter(即dgFilterRow那套)在列头下方生成筛选行,Columns[i].STFilter.Visible := True打开,STFilter.CaseInsensitive控制大小写。它生成的是类似LIKE的条件,最终怎么落到 SQL 取决于你接的OnSTFilterChanged事件。我一般不在客户端拼 SQL,而是把筛选条件收集起来交给服务端接口,避免把整表拉到本地过滤。这里提醒一句:筛选行打开后,网格的可用高度会减少,RowHeight和TitleHeight要重新算,否则底部会出现半行被裁掉的情况,视觉上很别扭。

4. 导出、打印与下拉:三个最容易被低估的模块

4.1 导出到 Excel 的两种路径

EhLib 的导出能力是很多人选它的直接原因。常见两条路:一是走DBGridEhExportAsXls这类单元,直接把网格内容和格式写成 xls;二是走DBGridEhExportAsText、DBGridEhExportAsHtml等轻量格式。xls 路径对格式保留较好,列宽、标题、合并单元格能带过去,但依赖 Excel 文件格式的兼容性,目标机器没装 Office 也能生成文件,因为写的是二进制格式而非调用 COM。我一般用下面这种写法,把导出封装成一个函数,界面层只传网格和文件名:

uses DBGridEhExportAsXls; procedure ExportGridToXls(Grid: TDBGridEh; const FileName: string); var Exp: TDBGridEhExportAsXLS; begin Exp := TDBGridEhExportAsXLS.Create(nil); try Exp.FileName := FileName; // 目标路径,确保目录可写 Exp.Grid := Grid; // 绑定的网格 Exp.ExportAllRecords := True; // 导出全部而非仅可见行 Exp.TitleFromGrid := True; // 标题取网格列标题 Exp.Execute; // 同步执行,大表建议放线程 finally Exp.Free; end; end;

参数说明:ExportAllRecords是关键,默认可能只导当前可见区域,用户以为导全了其实只导了一屏,这是最常见的投诉来源。TitleFromGrid为真时用网格列标题,为假则用字段名,中文界面下必须为真。Execute是同步的,几万行导出会卡界面,稳妥做法是丢到TThread里跑,导出期间禁用按钮并给个进度提示。导出路径要提前校验目录存在且可写,UAC 环境下往Program Files下写会静默失败,这个坑我见过不止一次。

4.2 打印与预览的页面参数

打印模块围绕TDBGridEh的PrintOptions展开,phGrid、phTitle、phFooter这些子选项控制打印哪些部分。页面设置走PrintOptions.Page,Orientation选横向还是纵向,Margins设边距。宽表打印几乎一定要横向,并且要开PrintOptions.Title.FillTitle让每页重复标题行,否则翻到第二页用户就不知道哪列是哪列。预览用TDBGridEhPreview或对应的预览窗体,先预览再打印,别直接调打印机。

一个实际经验:打印前把网格的Options临时调整成适合打印的状态(比如隐藏指示列、去掉筛选行),打印完再恢复,比在打印选项里逐项排除更省事。纸张大小和缩放比例在不同打印机驱动下表现不一致,PrintOptions.Scale设成 90 到 95 之间通常能避免最右列被裁,具体值要拿目标打印机实测,没有通用答案。

4.3 下拉与查找字段的绑定

TDBLookupComboboxEh是 EhLib 里使用频率仅次于网格的控件。它和普通下拉的区别在于能直接绑查找字段,显示值和存储值分离。配置要点:DataField指向本表的查找字段,KeyField和ListField指向查找源,ListSource指向查找数据集。常见翻车是查找数据集没打开就绑,运行时下拉为空;或者KeyField类型和DataField类型不一致(一个是整型一个是字符串),选中后存进去的值对不上。我一般在下拉控件的OnDropDown里确保查找数据集Open,并在窗体创建时校验两个字段的DataType是否匹配,不匹配就在设计期改,别留到运行时。

5. 避坑清单:安装、编译、运行时的五条血泪记录

5.1 设计期包装了但组件面板不出现

现象:.bpl编译成功,Install 也点了,组件面板找不到 EhLib 页签。原因多半是设计期包注册的单元没被 IDE 识别,或者旧版残留包占用了同名注册项。解决:先在 Install Packages 里卸载所有 EhLib 相关包,关闭 IDE,删掉%APPDATA%下对应版本的包缓存(Delphi 会缓存已注册包列表),再重新 Install 一次。还不行就检查.dpk里contains段是否包含设计期单元,缺了就不会注册面板页。

5.2 编译报「Unit not found」但路径明明配了

现象:库路径加了,工程还是报找不到DBGridEh。原因通常是平台不匹配——路径只加到了 Win32,工程却在编 Win64,或者反过来。解决:在 Library 设置里把 Win32 和 Win64 的路径分别配全,别指望一个路径通吃。另一个隐蔽原因是路径末尾多了反斜杠或中间有空格,Delphi 的路径解析对这两种情况都不宽容,逐字符核对一遍。

5.3 网格滚动卡顿,数据量并不大

现象:几千行数据,滚动时明显掉帧。原因往往不是网格本身,而是数据集每次移动都触发额外查询,或者AutoFitColWidths在滚动时反复重算。解决:关掉AutoFitColWidths改固定列宽;检查数据集的AfterScroll事件里有没有做重查询;把Options里的编辑相关开关按需关闭。用TStopwatch在OnDrawColumnCell里打点,能快速定位是绘制慢还是取数慢。

5.4 导出 Excel 打开提示格式不符

现象:导出的文件 Excel 能打开但弹「文件格式与扩展名不匹配」。原因多是导出单元版本和文件扩展名对不上,比如用 xls 导出器却存成.xlsx。解决:扩展名和导出器严格对应,xls 导出器就存.xls。如果必须给 xlsx,换对应的导出单元或走其他路径,别靠改扩展名糊弄,Excel 的兼容提示虽然能点掉,但用户第一眼就失去信任。

5.5 打印最右列被裁掉

现象:预览正常,实际打印少一列。原因是打印机可打印区域比纸张小,边距加缩放没算进去。解决:把PrintOptions.Scale降到 90 左右,边距适当收窄,并在目标打印机上实测一次。不同驱动对边距的解释有差异,同一份设置换台机器可能又裁,所以打印功能交付前一定要在客户实际环境里打一张样张,这一步省不得。

6. 进阶:把网格配置沉淀成可复用的运行时方案

做到这里,单个窗体的网格已经能用了,但项目里往往有几十个表格界面,逐个手配属性既慢又容易不一致。我的习惯是把常用配置抽成一个工具单元,用 RTTI 或直接赋值的方式批量套用,再留几个「风格档位」应对不同场景。下面这段是简化后的思路,核心是把「只读展示」「可编辑录入」「大数据浏览」三类界面的差异固化下来,调用方只传一个枚举。

type TGridStyle = (gsReadOnly, gsEditable, gsLargeData); procedure ApplyGridStyle(Grid: TDBGridEh; Style: TGridStyle); begin // 公共部分:行线、标题、行高统一 Grid.Options := Grid.Options + [dgRowLines, dgColLines, dgTitles]; Grid.RowHeight := 24; Grid.TitleHeight := 28; case Style of gsReadOnly: begin Grid.Options := Grid.Options - [dgEditing]; Grid.AutoFitColWidths := True; // 只读界面追求一屏看全 end; gsEditable: begin Grid.Options := Grid.Options + [dgEditing]; Grid.AutoFitColWidths := False; // 编辑时列宽固定,避免输入时跳动 end; gsLargeData: begin Grid.Options := Grid.Options - [dgEditing, dgAlwaysShowEditor]; Grid.AutoFitColWidths := False; Grid.FrozenCols := 1; // 大表冻结首列便于定位 end; end; end;

这段的价值不在代码本身,而在「把经验变成默认值」。新界面创建时调一次,省掉逐项点属性的时间,也避免了这个窗体行高 22、那个 26 的不一致。参数上要注意FrozenCols在列数很少时设 1 没有意义,调用前判断一下Columns.Count。另外这套配置只覆盖外观和交互开关,数据集的取数策略(FetchOptions、分页大小)仍然要按业务单独定,别指望一个函数包打天下。

验证方法我一般用两步:一是拿一个万行级的测试表,分别用三种风格打开,记录首屏时间和滚动流畅度,确认gsLargeData确实比默认配置快;二是导出同一份数据,用 Excel 打开核对行数和列标题,确认ExportAllRecords生效。这两步走完,基本能排除大部分「装上了但不好用」的情况。

说个自己的教训:早些年我图省事,装完包直接往老工程里拖控件,结果旧版残留包和新版打架,查了大半天才发现是包缓存没清。从那以后我每次装这类设计期组件,都强制走一遍「卸载旧包 → 清缓存 → 编运行期 → 编设计期 → 最小工程验证」的流程,一步不跳。这套流程看着笨,但省下的排查时间远比多花的那几分钟值。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进