简介:Delphi 12.3专用的EhLib 11.1.015控件库压缩包,面向使用Object Pascal开发Windows应用的工程师,尤其适合需要在项目中快速实现复杂表头、分组汇总、数据排序过滤及灵活数据库交互的开发者。压缩包共2000个文件,以dfm窗体和pas单元为界面与逻辑核心,配以dpk/dproj工程文件、res/ico/png资源以及chm帮助文档,完整呈现了控件库的工程组织方式。资源包整体约32.7MB,已有55人学习下载。包内附英文与俄文双版本介绍文档,详细讲解控件功能、安装方法、使用示例与编程技巧;history文件记录了各版本新增特性与修复内容;license与readme则分别提供合规使用指引和配置快速入门,并额外包含最新版本下载说明。借助这套资料,开发者能够系统掌握EhLib的架构设计、版本演进和实际用法,减少探索成本,高效集成到Delphi 12.3环境中。
1. EhLib 11.1.015 在 Delphi 12.3 环境里的真实定位
做 Win32 桌面报表的老项目最怕什么?不是写不出 SQL,而是客户临时要求“表头加两层、分组带小计、点列头就排序”。用标准 TDBGrid 硬拼,这几个需求叠加起来,代码能膨胀到没法维护。EhLib 11.1.015 的定位就是把这些高频交互变成控件属性,我在 Delphi 12.3 里装上之后,第一件事是把一个 3000 行的报表单元删到 1200 行。这个压缩包不是单纯一份控件源码,里面带完整帮助文档、版本历史和俄英双语手册,适合两类人:一类是拿 Delphi 12.3 做企业 MIS 的,另一类是维护老项目、被 DBGrid 交互折磨到想重构的。需要说明的是,网上流传的“Delphi 12.3 绿色版下载”大多不包含商业控件,EhLib 的 IDE 包必须自己安装编译,本文就按这个前提展开。
2. 安装前置:读懂包内文件与 Delphi 12.3 的安装顺序
2.1 包内文件到底哪些是安装必需的
解压后看到的文件列表里,最容易迷惑人的是Project1.bpr、Project2.cfg和delphixe.bmp。.bpr是 C++Builder 的工程文件,.cfg是编译配置,这两个文件对 Delphi 12.3 用户来说几乎无用,可以直接忽略,它们的存在只是说明资源包同时在为 C++ Builder 侧保留构建入口。真正有用的分组是:
| 文件 / 目录名 | 作用 | 安装阶段 |
|---|---|---|
| EhLibHelp.chm、EHLIB.CNT | 离线帮助与目录索引 | 安装后查阅 |
| About EhLib.VCL 11.1 Eng/Rus.docx | 双语介绍、架构说明、快速上手路径 | 安装前阅读 |
| history-eng.html、history-rus.html | 逐版本变更记录、缺陷修复对照 | 升级评估时使用 |
| license.rus、license.txt | 许可证条款,分俄文和英文 | 安装前确认 |
| readme.rus、readme.txt | 安装配置指南与常见问题 | 安装中对照 |
| 最新版本下载.txt | 官方后续版本获取说明 | 需要升级时参考 |
有一个关键点容易被忽略:EHLIB.CNT是帮助文档的目录文件,在 Windows 10/11 上如果 CHM 打不开,右键选择“解除锁定”即可,不需要改注册表。我一般先把中文文档读一遍,重点是确认它支持的 IDE 版本范围——11.1.015 明确为 Delphi 12.x 设计,但这里有个坑:如果你的 IDE 是社区版(Delphi Community Edition)而非商业版,安装包仍然可以编译,只是需要手动指定 BPL 输出目录。
2.2 标准安装步骤与参数说明
常见的安装流程是打开 Delphi 12.3,依次执行Component -> Install Packages -> Add,然后选择EhLib 11.1.015对应的.bpl文件。但刚解压完没有现成的 BPL,必须先编译 RUN 和 DESIGN 两个包。下面是把命令行编译作为备选方案的做法,适合在没有 IDE 图形界面的 CI 机器上预编译:
cd /d C:\EhLib\11.1.015\Delphi\12 msbuild /t:Rebuild /p:Config=Release /p:Platform=Win32 EhLib.dproj msbuild /t:Rebuild /p:Config=Release /p:Platform=Win32 EhLibDesign.dproj参数含义拆开说:Config=Release指生成非调试版本,运行时不需要携带 RTTI 调试符号;Platform=Win32是因为大多数存量业务系统仍是 32 位,若目标程序是 64 位,两个包要分别切换平台重新编译。/t:Rebuild强制全量重建,避免增量编译留下的过期 DCU 干扰 IDE 注册过程。编译结束后,把生成的EhLib*.bpl放到 Delphi 的bin目录,或者单独建一个D:\Libs\BPL目录并在 IDE 的Tools -> Options -> Environment Variables里添加路径。
2.3 安装验证:别装完就跑
readme.txt虽然提供了 Quick Start,但没有强调验证动作。安装完成后,新建一个 VCL 工程,在窗体上放一个TDBGridEh,如果控件面板出现EhLib 11.1.015分组且拖拽无异常,这只是第一步。真正的验证是编译一个用到TMemTableEh的 Demo 工程——因为 TDBGridEh 只是外壳,排序和合计依赖TMemTableEh内部实现,如果这个数据集控件没有正确注册,设计期会报“类未注册”。我习惯用一个最小工程验证:
with TMemTableEh.Create(nil) do begin FieldDefs.Add('Name', ftString, 20, False); FieldDefs.Add('Score', ftInteger, 0, False); CreateDataSet; AppendRecord(['ZhangSan', 88]); AppendRecord(['LiSi', 95]); First; ShowMessage(FieldByName('Name').AsString); Free; end;逻辑很简单:FieldDefs 声明两列,CreateDataSet 时把元数据物化为数据集,AppendRecord 写入两行,最后读取第一行姓名。如果这段代码能编译并弹窗,说明核心包已完整注册。若报E1048 Unknown identifier: TMemTableEh,优先检查 DCU 输出目录是否加入了 Library 搜索路径,而不是重新安装——这个错误九成是 IDE 找不到编译单元,不是控件坏了。振动提示:在第 2.1 节的基础上,从history-eng.html里搜12.3,能看到该版本对 IDE 版本适配的明确记录,比在论坛问“支不支持”可靠得多。
3. DBGridEh 核心配置:多层表头、分组合计与列宽策略
3.1 三层表头如何在设计期一次配好
EhLib 在报表领域的口碑主要来自DBGridEh的标题分级能力。标准 TDBGrid 想实现“主表头下面再拆两个子表头”,只能靠多行 DrawGrid 事件手工绘制,而 DBGridEh 内置了TitleLines和Columns[].TitleLines结构。实际操作上,我在设计器里把网格的TitleLines设为 2,然后逐列在Columns编辑器里拖动列位置,属于同一组的列会在第 1 层显示为合并单元格——这是自动合并的,前提是OptionsEh里的dghAutoSortMarking不干扰标题绘制。
放一张实际参数对照表,方便直接抄作业:
| 目标效果 | 属性路径 | 推荐值 | 备注 |
|---|---|---|---|
| 表头显示两行 | TitleLines | 2 | 第 1 行为分组名 |
| 禁用列表格线 | OptionsEh.dghClearSelection | False | 保持选中状态可读性 |
| 奇偶行底色区分 | OddRowColor/EvenRowColor | 例如clBtnFace/clWindow | 需同时启用UseOddRowColor |
| 列头点击排序 | SortLocal | True | 依赖数据集支持本地排序 |
| 行高自适应 | RowHeight.AutoFit | True | 自动按字体和内容计算 |
还有一种是 I 级标题跨列合并,比如“2024 年销售数据”横跨 1~3 列。做法是Columns[0].TitleLines := 2; Columns[0].Title写分组名,再把Columns[0].Title.ToolTips关掉,否则鼠标悬停会弹黑框。列宽这一步最容易翻车:直接用Columns[].Width写死像素值,在高 DPI 屏幕上会错位。我一般是Width := 120作为基准,然后配合OnGetColumnTitleParams调整最小宽度。注意dghAutoFitColWidths这个选项只对普通列有效,对合并标题下的子列不起作用,需要自己写循环按字符数估算。
3.2 分组合计的三种实现路径
DBGridEh的统计行来自FooterRowCount和FooterRows[0].Value。最常见的三种合计需求:每组小计、全局总计、以及按页面分页统计。全局总计最简单,在设计器里把FooterRowCount设为 1,任意列选中FooterRows[0].FieldName,然后设Value值为SUM。但“每组小计”不能靠这个,得用DBGridEh的GroupPanel拖拽分组字段,此时FooterRows[0].ValueType需要设为gvtSum。
procedure TForm1.DBGridEh1GroupDataHint( Sender: TObject; Column: TColumnEh; GroupField: TField; var Value: Variant); begin if Column.FieldName = 'Amount' then Value := GroupField.DataSet. AggregateField('Amount').Value; end;这段代码解释一下:GroupField是当前分组依据字段,AggregateField('Amount')在TMemTableEh内部按组聚合。引用Value走的是 Varaint,返回浮点数时注意壳类型,否则Value := ...会出现 EConvertError。这里有个小坑:如果数据集是TADOQuery,AggregateField不可用,需要在 SQL 里先按分组字段排序,再配合DBGridEh的GroupFieldView属性把分组渲染交给前端——本质上仍是局部排序 + 前端捞每条记录做累计。
分页统计是另一个场景。当网格数据量大、开了PageSize时,FooterRows[].Value默认统计的是当前页,不是整表。想统计全表,需要把DBGridEh.DataSource.DataSet.AggregateField放到自己维护的合计字段上,并显式设置FooterRows[].FieldName := 'TotalAll',把总合计放到FooterRows[1],第二行再写页合计。值类型建议看history-eng.html里对TFooterValueType从 8.x 到 11.x 的变更描述——旧的gvtCount在 11.1.015 里被拆成了gvtCount与gvtCountDistinct,前者统计行数,后者统计去重数,语义不小。
3.3 列宽自适应与高 DPI 拖动容差
高 DPI 下有个细粒度的体验问题:手动拖列宽时,鼠标在列边界位置偏移超过 4 个像素就拖不了。这是 EhLib 的dghColumnResize选项配合系统 DPI 缩放造成的容差偏差。我建议在FormCreate里做一次全局初始化:
DBGridEh1.OptionsEh := DBGridEh1.OptionsEh + [dghColumnResize, dghExtendVertLines]; DBGridEh1.ColumnDefValues.EndEllipsis := True; DBGridEh1.ColumnDefValues.Title.ToolTips := False;dghExtendVertLines让竖线延伸到底部,避免最后一行下边框缺失;EndEllipsis让超长文本以省略号展示而不是撑宽列;Title.ToolTips关闭后,列头长文本不再弹出提示,防止和分组列的拖拽提示冲突。如果你发现DBGridEh1.Columns[0].Width在设计器里怎么改都会被还原,检查是否勾选了AutoFitColWidths,这个属性在数据源变更时会重算列宽,优先级别高于你手工设置的值。排序标记那块,TitleSortMarker在 11.x 里能显示“升/降/组内”三种图标,但对SortMarkerStyle的合法值,EhLibHelp.chm里列出的是smDefault、smArrow、smArrowAndUpDown,其中smArrowAndUpDown在中文系统下配合微软雅黑 9pt 会有 1 像素截断,建议字体调到 10pt 或改用smArrow。
4. TMemTableEh 实战:内存数据集排序过滤与 Excel 导出
4.1 为什么报表场景优先选 TMemTableEh
业务数据量大时,很多人在TFDQuery上直接开Indexes排序,但报表多级过滤会反复触发 SQL,压力全在后端。EhLib 的TMemTableEh是一个客户端内存数据集,排序、过滤、分组都不再回库。它的定位接近轻量级 ClientDataset,但内部针对 DBGridEh 做了优化:Aggregates增量更新、Filter字符串语法直接复用 DBGridEh 的过滤表达式、DataSet属性可以直接包一层 TDataSet 做整表载入。
MemTableEh1.LoadFromDataSet(ADOQuery1, lmCopy, lmCopy, True);参数说明:第一个lmCopy是结构复制模式,指复制字段元数据;第二个lmCopy是数据复制模式;第三个布尔值True表示载入后游标停留在首行。注意LoadFromDataSet默认关闭索引,所以载入后需要手动AddIndex。排序通常用SortOrder替代旧版的IndexName:
MemTableEh1.SortOrder := 'Score DESC; Name ASC';SortOrder与IndexName的差别在于前者是内存快速排序,临时生效,不维护永久索引文件;后者适合频繁随机查询。报表临时排序用SortOrder,不要建索引——建索引会带来维护开销,在 10 万行内存表上每次插入都会变慢。
4.2 过滤表达式与编码坑位
TMemTableEh的过滤语法兼容 DBGridEh 的筛选器,比如Score > 90 and Name like '张%'。但有个老生常谈的麻烦:SQLite 里的数据源常常出现乱码,这不是 EhLib 的问题,而是源数据集把 UTF-8 字节流直接装进 AnsiString 字段。方向盘在数据集层解决,而不是在控件层。稳妥做法是载入前把字段类型显式指认为ftWideString:
with MemTableEh1.FieldDefs.AddFieldDef do begin DataType := ftWideString; Size := 200; Name := 'NameW'; end;这样赋值时使用宽字符,过滤条件里的中文字符串不再发生字节错位。字段长度Size我这里写 200,是按“常见姓名+备注”估的;如果标题列更多,按最大字节数除以 2 再留余量。ftWideString在 DBGridEh 显示时如果用默认字体,可能拉高行高,所以数据量大时配合RowHeight.AutoFit := False,锁定行高,渲染性能更稳。
4.3 导出 Excel:从网格到表格文件
EhLib 自带导出器,DBGridEh上挂一个TDBGridEhExportAsText或TDBGridEhExportAsUnicodeText就能导出,但格式控制最细的还是ExportToExcel。这个接口在 11.x 里支持xlExcel8(兼容旧 Excel)和xlExcelXML两种格式,XML 格式对中文兼容更好:
uses EhLibMTE, DBGridEhImpExp; DBGridEh1.ExportToExcel('C:\Temp\Report.xml', 'Sheet1', xlExcelXML, True, False);xlExcelXML写出来是 XML 电子表格,Excel 可以原生打开,不会弹兼容警告。参数True表示导出列头,False表示不导出标题重复的合计行。这里有个坑:导出前必须保证DBGridEh1.FooterRowCount <= 1,否则合计行会被当成数据导出,合计值错位。如果客户只要固定列,临时隐藏多余列再导出——用Column.Visible := False,然后在finally里恢复。
导出数据超过 5 万行时,Excel XML 文件体积会膨胀到几十 MB,此时可以考虑导出 CSV 加 UTF-8 BOM,Excel 双击也能直接打开,但公式和样式全部丢失。我一般遵循这样的原则:5 万行以内用 XML 保留格式,超过 5 万行导出 CSV 并在文件名标注数据范围。控制行数还有一种做法:MemTableEh1.RecordCount超过阈值时,弹出对话框让用户选择按月份拆分为多个文件,下面是一个简化版本。
if MemTableEh1.RecordCount > 50000 then begin dlgSave1.Filter := 'CSV|*.csv'; if dlgSave1.Execute then DBGridEh1.SaveToCSV(dlgSave1.FileName, True, True); end;SaveToCSV的三个参数分别是逗号分隔符、引号转义、是否带列名。默认的逗号分隔在 Excel 中文系统里表现良好,但如果你导出后发给欧美同事,注意本地系统区域设置里的列表分隔符——部分欧系语言区域是分号,需要手动切换文件类型。
5. 历史记录读法:从 history-eng.html 判断升级风险
5.1 三个文件配合使用的方法
压缩包里的历史记录文件不是摆设,在决定是否引入这个版本到存量项目时,它们是唯二的事实依据之一。方法是先把history-eng.html打开,定位到 11.1 系列,然后和readme.txt对照安装基线。真正值得警惕的是破坏性变更,比如某个控件的属性从“默认开启”变成“默认关闭”,这类信息通常藏在“Improvements”段落里而不是“Bug fixes”里,因为官方觉得改进不破坏兼容,但对老项目来说可能就是坑。
一个实用的判断指标:在历史记录里搜索Added和Changed的次数,再加Fixed的次数,三者的比例关系能反映版本稳定性。如果某个子版本Fixed数量远大于Added,说明它在修上一个大版本引入的回归,优先选择这一类。比如 11.1.015 的变更更密集地集中在TDBGridEh的排序标记与TMemTableEh的过滤后端,说明 11.1 系列的核心逻辑处在一个稳定收敛期。
5.2 许可证文件与团队合规检查
license.txt和license.rus拿来直接读不需要逐行翻译,重点看三点:是否允许修改源码、是否允许分发 BPL、是否允许在商业闭源软件中收费集成。如果团队有人把编译出的EhLib*.bpl直接随安装包拷给客户,就要确认“再分发”条款。这个包的许可文本(英文版)对“应用本身可以闭源”没有阻挠,但对象链接库文件本身不允许拆出去单独售卖。
| 许可证关注点 | 英文版常见表述 | 团队落地检查清单 |
|---|---|---|
| 修改源码后是否保留版权声明 | Yes | 在源码头部保留原注释 |
| 是否可随商业软件分发 | 通常允许 | 不要分发.dpk源码包 |
| 是否禁止反向工程 | 明确禁止 | 不进行任何形式的调试监听 |
| 是否提供升级续费说明 | 见“最新版本下载.txt” | 评估当前版本是否满足项目期 |
这一章结合实际项目经验,比如目标程序需要部署到客户内网环境,license.txt还有一条容易踩的约束:部分版本禁止在非 VCL 框架(比如 FMX)中使用。开发人员如果顺手把DBGridEh拖到 FMX 窗体上,编译能过但运行报缺少 VCL 主题授权,那就是这个限制的表现。所以不要在uses里混用 VCL 和 FMX 单元。
5.3 升级评估的具体手势
给老项目升级前,先拿history-eng.html与当前版本的差异清单出一份风险表。实操上可以这么做:打开当前项目的.dproj,记下所有引用的 EhLib 相关包名,再对照最新版本下载.txt里指明的官方发布渠道,判断升级路径是否跳跃。如果项目当前用的是 8.x,直接跳到 11.1.015,建议先做一次 “空白工程 + 核心报表页” 的原型验证,时间预算 2 天——因为 8.x 到 11.x 之间有属性名重命名(例如ShowFooter改为FooterRowCount),编译期错误不难修,运行期表现差异才是重点。
具体验证动作:把DBGridEh1.FooterRowCount := 1写到 FormCreate 里,然后用TButton触发一次分组排序和导出,再执行Application.MessageBox看是否有异常。这样能一次性覆盖三个核心路径。
6. 运行期行为校准:几个影响交付质量的技巧
这一章写给已经装好控件、正在收尾的人。EhLib 的功能开关多到可以当教学案例,但交付时真正决定“客户觉得好不好用”的,往往是几个边缘属性。
第一个技巧是列头排序图标和数据刷新的一致性。DBGridEh的排序标记在SortLocal := True时会显示,但当你调用了MemTableEh1.Refresh重新载入数据后,DBGridEh1.SortMarkedColumns可能残留旧状态。解决办法是在刷新后显式清掉排序描述:
DBGridEh1.RestoreColumnsLayout( TDBGridEh.InitLayout( DBGridEh1, nil, nil, nil, nil), true, false);RestoreColumnsLayout会重置列状态,InitLayout这个第二参到第五参分别对应筛选、排序、合计、分组,全传nil表示按默认值清空。这是重置不是重载,数据不受影响。
第二个技巧是报表合计行的千分位显示。FooterRows[].Value里的数字在 DBGridEh 默认按字段类型显示,不带千分位。想统一格式,处理OnGetFooterParams事件:
procedure TForm1.DBGridEh1GetFooterParams( Sender: TObject; DataIndex: Integer; var Value: Variant); begin if DataIndex = 0 then Value := FormatFloat('#,##0.00', StrToFloatDef(VarToStr(Value), 0)); end;DataIndex对应列索引,Value在进入事件前已经是合计运算结果,这里做一次格式转换。StrToFloatDef兜底,防止空值触发异常。这个事件每行合计都会触发,数据量大时耗时可忽略,但如果Value本身是字符串型合计(比如计数),FormatFloat会抛异常,所以只在DataIndex = 0这个确定列做格式化,别全局套用。
第三个技巧是关于DBGridEh的快速滚动。数据量到十万行时,把DBGridEh1.RowHeight.AutoFit设为 False,同时启用dghAutoFitColWidths的替代方案——按比例计算列宽而不是逐列精测。开启Timer延迟重绘也能减少滚动时闪烁。具体手式:
DBGridEh1.OptionsEh := DBGridEh1.OptionsEh - [dghAutoFitColWidths]; DBGridEh1.OptionsDBGrid := DBGridEh1.OptionsDBGrid + [dghColumnResize];这里把dghAutoFitColWidths从选项里移除,换取滚动帧率稳定;保留列宽手动调整权限,这份取舍适合交互优先的管理端报表页面。如果你在写 Delphi 12.3 下的新项目,还可以研究一下EhLibHelp.chm里TCustomDBGridEh的ReadOnly属性在 11.1.015 中的行为变化——它从单列级推进到了单元格级,这对只读报表的数据防误改是一个更有力的保证。
本文还有配套的精品资源,点击获取