EhLib在Delphi 11中的落地实践与常见坑

EhLib在Delphi 11中的落地实践与常见坑 简介EhLib 10.0.031是一套面向Delphi 11开发环境的组件库涵盖图像处理、多数据库连接、报表设计、图表可视化和界面增强等能力适合需要快速搭建数据管理系统、业务报表和图形化工具的中高级Delphi开发者。组件内置图像查看编辑与常见格式转换功能支持主流数据库连接查询报表模块可预览、打印并导出多种通用格式配合可自定义的界面控件能显著提升企业级应用开发效率。压缩包共890个文件大小24.63MB核心包含302个窗体定义文件、299个源代码单元、10个组件包工程以及30个编译单元同时附带帮助文档和多种示例工程便于安装后对照学习。已有882人浏览学习从数据表格、报表打印到图表联动均有现成案例可循。借助完整源码和示例使用者可以理解组件底层机制也能按项目需求扩展自定义控件缩短开发周期。 上个月把一个维护了六七年的 Delphi 项目迁到 Delphi 11折腾了几天最痛的一环居然不是老代码兼容而是表格控件。客户这些年攒下的需求越来越离谱点列头要排序、底部要合计、行要根据状态变色、还要一键导出 Excel。原生 TDBGrid 做前面两件事还行后面全靠 OnDrawColumnCell 硬画代码又丑又难维护新增一列状态就要牵一发动全身。我最后整套换成了 EhLib 10.0.031 for Delphi 11跑了一个多月总算消停了。这篇文章不是官方文档的翻译是我从选型、安装、授权配置、核心功能落地到真实报错排查的完整记录。正在评估表格控件或者已经被 DBGrid 维护成本折磨得不行的 Delphi 开发者看完应该能少走不少弯路。先说结论EhLib 这个版本在 Delphi 11 下表现很稳但安装和授权环节确实有几个容易卡住的点我后面会一个个拆开讲。1. 选型复盘DBGridEh 比原生 DBGrid 多出来的价值先说清楚一件事如果你的项目只是显示一个只读表格用原生 TDBGrid 没有任何问题。但一旦需求开始叠加原生控件的劣势就会迅速放大。我这里给一份基于实际维护经验的对比而不是官网上那些宣传话术。能力原生 TDBGridDBGridEh点击列头排序自己写 OnTitleClick维护排序状态内置 SortLocal 和排序标记底部合计/平均值/计数自绘 Footer代码量大FooterRowCount 配合列 Footer.ValueType列头下拉筛选没有STFilter 内置下拉筛选行/列颜色样式OnDrawColumnCell 手工绘制容易出文字错位OnGetCellParams 事件只改单元格参数导出 Excel/CSV/HTML完全没有得接第三方库组件自带多种导出途径单元格编辑体验简单编辑下拉列表要自己写PickList、查找字段、内置编辑器体系完整大数据量下的绘制性能一般编辑状态多时明显卡顿只读模式下绘制优化明显选型阶段我也对比过 DevExpress 那套控件包东西确实全但对这个小项目太重了。团队里没人熟悉那套 API部署体积、学习成本、许可证价格都要重新评估。EhLib 的优势在于只专注于数据表格和相关编辑器这一个领域它在 Delphi IDE 里的存在感非常克制——一个控件面板页几个核心组件不绑架你的项目结构。还有一个很实际的考量EhLib 的随包源码是完整的。我在改造中遇到一个导出格式细节问题直接翻到源码把那个属性赋值链路看懂比自己猜行为要快得多。对长期维护的项目来说这个可读源码的价值很难用价格衡量。版本上也提醒一句10.0.031 是专门针对 Delphi 11 Alexandria 构建的适配版本不是大版本 10.0 和 Delphi 老版本混为一谈。安装前先确认下载包里的 IDE 版本目录别装错构建号不然后面 IDE 弹出来的授权和兼容问题会非常迷惑。2. EhLib 10.0.031 的安装与授权处理实践安装本身不复杂但我在 Delphi 11 下实际装的时候踩了几个小坑重新捋一遍完整流程。第一步是从官方渠道拿到对应 Delphi 11 的安装包后先把压缩包解开。安装包里通常按 RAD Studio 版本分了子目录找到 Delphi 11 Alexandria 对应的目录里面是运行时包源码和设计期包。第二步是用 Delphi 11 打开运行时包项目直接编译一次生成 BPL 和 DCU。第三步是安装设计期包打开 .dpk 后执行 Install这时 IDE 的控件面板上会出现 EhLib 相关页。编译前记得确认 Tools Options Environment Options Delphi Options Library 里的路径配置。Delphi 11 的默认搜索路径不会自动包含 EhLib 的源码目录需要手动加进去。这一步不做编译工程时会一直报找不到 DBGridEh.dcu很多人卡在这。真正容易让人困惑的是运行时授权提示。我在 Delphi 11 里第一次打开带 DBGridEh 的旧窗体时IDE 直接弹了个无效的授权说明类似的提示第一反应以为是控件没装好。后来排查下来原因就一句话IDE 搜索路径里同时存在两个不同版本的 EhLib BPL新版设计期包加载后运行期却连到了旧版 DCU模块签名对不上。提示在正规渠道安装 EhLib 后如果还弹授权相关提示九成以上不是激活问题而是 BPL/DCU 路径串了。把 Library 路径里所有旧的 EhLib 目录删干净只保留当前 10.0.031 对应目录然后完全退出 IDE 再重开问题基本消失。还有一个小经验卸载旧版本时不要只在 IDE 里 Remove Package还要检查 Windows 的 BPL 搜索路径里是否残留旧文件。我见过同事把这个忽略掉导致同一个项目里一半窗体用新版、一半用旧版运行期行为完全不一致特别难查。安装完成后建议先建一个空工程拖一个 DBGridEh 到窗体上绑定一个临时 DataSource编译跑通再并入现有项目。这一步能快速验证 IDE 集成是否正常避免把脏环境问题带到业务代码里。3. 把 DBGridEh 调教成生产工具排序、合计、筛选、行样式安装只是开始真正让 DBGridEh 体现价值的是几个常用功能的落地方式。我先说排序。绝大多数业务表格的排序需求很简单用户点列头数据按这一列归序再点一次反序。原生做法是自己维护一个排序字段和方向每次重新查询数据库DBGridEh 的做法要粗暴也高效得多——如果当前 DataSet 的数据已经加载到本地直接开 SortLocal 和 SortMarkedColumns点列头时由控件自己完成排序和三角形标记。我通常会在 FormCreate 里做一次全局配置代码大致如下procedure TfrmMain.FormCreate(Sender: TObject); begin DBGridEh1.SortLocal : True; // 允许本地排序 DBGridEh1.SortMarkedColumns : True; // 列头显示排序标记 DBGridEh1.FooterRowCount : 1; // 底部合计行 DBGridEh1.STFilter.Visible : True; // 打开列头筛选下拉 // 对金额列加合计格式保留两位小数 DBGridEh1.Columns[0].Footer.ValueType : fvtSum; DBGridEh1.Columns[0].Footer.ValueFormat : #,##0.00; end;注意这里的排序和筛选都有本地和远端两种思路。本地模式适合主数据表已经全量加载的场景点列头立即响应用户体验非常顺滑但如果底层是百万级数据的实时查询客户端排序就很不明智应该在 SQL 里 ORDER BY或者让 STFilter 重新查询。我见过有人把 80 万行的表直接开本地排序结果点一次列头卡三秒这不是控件的问题是使用姿势不对。颜色样式这块是 DBGridEh 替换原生 DBGrid 后体验提升最明显的地方。原生方案要写 OnDrawColumnCell处理 DrawText、画背景、画边框稍不注意就出现文字和底色错位。DBGridEh 提供一个 OnGetCellParams 事件只管给单元格的显示参数赋值procedure TfrmMain.DBGridEh1GetCellParams(Sender: TObject; EditMode: Boolean; Params: TColCellParamsEh); begin if Params.Column.FieldName ORDER_STATE then begin if Params.Text 0 then begin Params.Color : $00EAF7EA; // 浅绿背景 Params.Font.Color : clGreen; end else if Params.Text 2 then begin Params.Color : $00FBE9E7; // 浅红背景 Params.Font.Color : clRed; end; end; end;这个事件里 Params.Text 是当前单元格的显示文本Params.Column.FieldName 能定位到是哪一列。相比自绘来说代码量少、可读性强而且不会因为网格滚动出现绘制闪烁。如果你想根据另外一列的值给当前行上色也可以在这个事件里直接读 DataSet 当前记录的字段值灵活性比原生方案高很多。STFilter 是另一个让我觉得回不去的功能。开启后每列标题上会出现一个下拉箭头用户可以根据列的唯一值快速筛选和 Excel 的筛选体验类似。第一次配置我只设置了 STFilter.Visible : True没看筛选范围结果发现筛选是作用在客户端数据集上的底层没有重新查询。理解这个机制后我对不同的表做了区分数据量小的表用本地筛选数据量大且需要灵活筛选的表还是走 SQL 条件刷新避免内存里筛出一堆无用数据。4. 运行期报错排查从cannot perform this operation on an open dataset说起这个报错文案很经典Delphi 老手看一眼就知道是数据集状态出了问题。BDE 时代它的意思是你试图对一个已经打开的 DataSet 执行再次打开或者在打开状态下做非法操作。到了 EhLib 场景下这个报错最常见的三个触发点我都实际遇过。第一个触发点是 MemTableEh 的反复加载。业务里常需要一个临时表结构把查询结果拷贝进 MemTableEh 再做二次加工。刚接手时我图省事在循环里直接写// 错误用法MemTableEh1 还处于 Active 状态时再次加载 MemTableEh1.LoadFromDataSet(qrySource);第一次运行没问题第二次进到这段代码就报 cannot perform this operation on an open dataset。原因是 MemTableEh 自己已经 Open再往里灌数据时内部游标冲突。正确做法是加载前先判断 Active 状态关掉再加载if MemTableEh1.Active then MemTableEh1.Close; MemTableEh1.LoadFromDataSet(qrySource); MemTableEh1.First;第二个触发点更隐蔽发生在 DBGridEh 的排序或筛选事件里。我在一个窗体的 AfterScroll 事件里写了统计逻辑顺手又刷新了一次查询结果每次滚动到当前记录就崩。表面看是数据集的打开时机问题本质上是多个组件共用了同一个 DataSource某个组件的滚动事件触发了重新 Open把游标位置重置了。排查方法也很笨但有效在 IDE 的调用栈窗口里看是谁第二次触发了 Open然后把业务逻辑从滚动事件里挪走或者在滚动事件里加一个状态锁避免重复刷新。第三个触发点跟 DataSetDriverEh 有关。EhLib 支持通过 DataSetDriverEh 为某些组件提供数据驱动切换数据源时如果旧的 ProviderDataSet 还处于打开状态就直接赋新数据集同样会撞上这个错误。我踩完这个坑之后的习惯是所有数据集初始化统一放在一个方法里先 Close 再切数据源最后 Open不让状态有机会处于半打开。排查这类问题我给一个固定思路先看调用栈确认谁在哪个时机触发了 Open 或 Close检查这个 DataSet 是否被多个 DataSource 或组件共用在 IDE 的 DataSet 状态面板里观察 Active 和 State确认是不是存在 Edit/Insert 状态残留统一初始化顺序避免在 AfterScroll、AfterClose 这类事件里再做数据加载。按这个顺序走绝大多数数据集状态类报错都能定位到具体位置。不要一上来就翻控件源码先把这个数据集的生命周期理清楚省很多时间。5. 数据量上去之后的优化刷新策略、Excel 导出与字典缓存EhLib 用顺了之后你大概率会开始把更多需求交给它这时候就要考虑性能问题了。我的原则很简单DBGridEh 负责展示不负责把几十万行数据一次性拖进内存。真遇到大数据量优先在 SQL 层做分页和条件过滤网格只拿当前页数据。如果业务上确实需要全量显示至少把网格设为只读模式因为只读状态下 DBGridEh 不需要维护每行的编辑状态绘制开销明显下降滚动也能顺一些。刷新数据时的界面卡死问题项目里原本很常见。有人在按钮点击事件里直接遍历一个大查询界面整个冻住然后丢给用户一个正在加载的静态提示。更好的做法是后台线程取数取完切回主线程更新界面。Delphi 11 里我习惯用 TTask.Run代码长这样TTask.Run( procedure var LData: TListTSomeRecord; begin LData : LoadRemoteData(); TThread.Queue(nil, procedure begin FillMemTableFromList(MemTableEh1, LData); LData.Free; end); end);这里要注意DataSet 和 DBGridEh 这些可视化控件永远不要直接在后台线程里碰所有数据集操作必须在主线程完成。后台线程只负责做取数、计算、组装内存数据这些纯逻辑最后通过 TThread.Queue 回到主线程刷新 MemTableEh。很多人在匿名线程里直接操作数据集结果就是各种诡异的运行期崩溃排查起来极其痛苦。导出 Excel 也是高频需求。EhLib 10 里我主要用 DBGridEhExcelExport 这个组件面板里拖一个到窗体上设置它关联的 DBGridEh指定导出文件名调用 Execute 就完成了。相比以前用 OLE 操作 Excel 的方式这个方案不需要目标机器安装 Office也不容易出现 Excel 进程残留。导出前我会先处理一下用户当前看到的列顺序和筛选状态因为导出的是网格当前展示的数据如果把隐藏列也带出去客户收到的表格就会莫名其妙多出几个没有表头的字段。导出完成后如果还想自动打开文件所在目录我会用 ShellExecute 处理ShellExecute(Handle, open, PChar(ExtractFilePath(ExportFileName)), nil, nil, SW_SHOWNORMAL);ShellExecute 是异步返回界面不会卡住也不需要等待外部进程结束够用且干净。再分享两个小优化。项目里经常要在 OnGetCellParams 里根据状态字符串决定颜色每行都做字符串比较几万行下来还是有点开销。我优化成预构建一个 TDictionarystring, TColorvar StatusColorMap: TDictionarystring, TColor; begin StatusColorMap : TDictionarystring, TColor.Create; try StatusColorMap.Add(0, $00EAF7EA); StatusColorMap.Add(1, $00FFF8E1); StatusColorMap.Add(2, $00FBE9E7); // 数据加载完成后把字典挂在某个字段或 Form 私有成员上 finally StatusColorMap.Free; end; end;在 OnGetCellParams 里用 TryGetValue 查一次字典比每次比较三个字符串要快得多。如果按日期分组统计建议把日期转成序数或者周序号作为字典 Key避免日期格式化后拼字符串当 Key那个开销在大量数据下并不划算。跑了一个多月我个人最大的体会是控件换掉只是第一步真正省心的是它把表格领域常见的交互需求都做成了标准化配置让我能把精力放回业务本身。如果你正在 Delphi 11 上处理类似的数据表格改造按上面这几步走应该能少踩不少坑。本文还有配套的精品资源点击获取