简介:面向 Delphi 开发者的 EhLib 控件库资源包,覆盖 Delphi 7 至 XE8 多版本环境,解决 DBGridEh 等增强型网格组件在桌面数据库项目中的安装与适配问题,适合需要快速增强表格编辑、展示能力的开发者。压缩包共 1401 个文件,约 42.15MB,包含 166 个 pas 源文件、284 个 dcu 编译单元、183 个 hpp 头文件以及 31 个 bpl 运行期包,同时配有安装程序、dpk 工程文件和若干示例窗体,可按开发环境直接选用对应模块。资源附引导式安装说明,运行 EhLibInstaller.exe 选择当前 Delphi 版本即可部署,已在 Delphi 7 下验证通过;针对 Windows 7/8 与 64 位系统可能出现的权限和 bpl 加载问题,也给出具体注意事项,包括以管理员身份运行、将 EhLib 路径加入系统 PATH 变量等排错思路,可帮助减少环境配置上的弯路。已有 553 人浏览学习,适合数据库应用、报表界面开发及需要维护多版本 Delphi 项目的中高级开发者参考。
1. 项目背景:为什么到今天还要聊DBGridEh和Delphi Xe8
先说个真实场景。前阵子我接手一个老客户的项目,他们的进销存系统还是Delphi 7写的,界面上一排DBGridEh表格控件,跑得挺稳,但客户提了个需求:要在Windows 10的新机器上部署,还想用上64位编译。这就尴尬了,Delphi 7压根不支持64位,客户又不愿意全面重写——几万行业务代码,说重写就重写,那是要出人命的。最后方案定下来:升级到Delphi Xe8,控件继续用DBGridEh。
你可能觉得奇怪,Xe8都是2015年的老版本了,现在都出到Delphi 12了,怎么还有人往回折腾?但实际情况是,在制造业、医疗、政务、财务这类行业里,Delphi写的老系统生命力极其顽强。尤其是DBGridEh配ADO这套组合,几乎是国内Delphi开发者做管理信息系统的标配。Grid要能冻结列、要能合并单元格、要能树形展示、要能在单元格里嵌下拉框和日期控件,原生TDBGrid做不到这些,DBGridEh就是为这个而生的。
这次项目做完,我把DBGridEh在Xe8下的安装、迁移、踩坑过程完整记录了一遍。如果你也恰好被困在“老项目要升级但没有完整技术文档”的处境里,这篇内容应该能帮你省下至少一个周末的折腾时间。
2. EhLib控件库整体设计与版本选型思路
2.1 一个控件库撑起一套UI框架
EhLib不是一个单独的控件,而是一整套VCL控件库,核心是DBGridEh,但配套的还有DBLookupComboboxEh、DBEditEh、DBDateTimeEditEh、DBComboBoxEh、DBMemoEh这一票兄弟控件。它们统一了数据感知控件的交互风格,比如下拉框支持自动补全,日期控件支持空值显示“请选择”,这些在原生控件里都要自己写一堆代码才能实现。
这次升级过程中我最大的体会是:换版本不是换一个DLL那么简单,而是一个生态的切换。DBGridEh的代码量非常大,官方源码解开后光.pas文件就几十个,里面涉及大量VCL底层接口的调用。所以千万不要试图直接把老版本的DCU拿过来用,编译器版本对不上,轻则提示“Unit not found”,重则直接报非法内存访问。
2.2 版本支持矩阵:从Delphi 5到Xe8意味着什么
EhLib的版本支持范围非常宽,早期版本从Delphi 5一路支持到最新的RAD Studio。标题里说的“支持到Xe8”,指的是EhLib对应版本中包含了针对XE8的编译器和RTL适配层。
| Delphi版本 | 推荐的EhLib版本 | 备注 |
|---|---|---|
| Delphi 7 | EhLib 4.x / 5.x | 经典搭配,资料最多 |
| Delphi 2007 | EhLib 5.x | 老项目常见 |
| Delphi 2010 / XE | EhLib 6.x | 开始支持Unicode完善 |
| Delphi XE8 | EhLib 7.x / 8.x | 支持64位编译 |
| Delphi 10.4+ | EhLib 9.x / 10.x / 11.x | 新版官方持续更新 |
这次项目用的是Delphi Xe8 + EhLib 8.x,实测稳定。如果你想用EhLib 11往Xe8上怼,思路是对的——新版控件通常保持向后兼容,但要注意新版可能使用了更新的语言特性,导致在Xe8上编译不过。遇到这种情况,要么换回旧版,要么手动改源码,我这次就踩了这个坑,后面细说。
2.3 选型时为什么放弃DevExpress留用EhLib
比较戏剧性的是,这个项目原来的开发者在一张表里用了DevExpress的cxGrid,另外十几张表全是DBGridEh。升级的时候客户问能不能顺便统一了。我看了下代码,DevExpress那套需要额外的运行时包、额外的授权文件,而且cxGrid和DBGridEh的API完全不兼容——原来写在DBGridEh的OnGetCellParams事件里的那些格式化逻辑,平移过去几乎等于重写。
最后我的建议是:统一保留DBGridEh,把那张cxGrid的表也改回DBGridEh。理由很简单:一是团队对这个控件更熟,二是DBGridEh在普通二维表展示上完全不输cxGrid,三是避免了引入更多依赖。DevExpress不是不好,但老项目升级的第一原则是“减少变量”,不要借升级的机会顺手搞技术栈迁移,那是另一个项目该干的事。
3. 核心细节解析:DBGridEh那些值得深挖的硬核能力
3.1 数据展示层的高级玩法
DBGridEh最让我服气的一点是它把数据显示层该有的能力几乎都做全了。列标题多行显示、表尾统计行(FooterRow)、自动适应列宽、下拉列表过滤(AutoFilter)、单元格合并(MergeColumns)、树形结构展示(TreeView模式),这些功能在配置层面基本开箱即用。
这里重点说一下单元格合并。做报表的人应该深有体会:客户要求“相同值的列要合并单元格”,这在原生TDBGrid里你得自己处理Canvas绘制,工作量巨大。DBGridEh里只需要设置DBGridEh1.OptionsEh := DBGridEh1.OptionsEh + [ghGrouping],然后在DBGridEh1.Columns[i].MergeCells := True,运行起来就是自动合并,而且带边框和焦点处理,效果非常接近Excel。注意,合并是针对整列生效的,如果你只想合并某几行,得配合OnMergeColumns事件自己判断行号。
3.2 编辑控制:单元格级的下拉和自动补全
DBGridEh在编辑控制上把DBLookupComboboxEh和DBGridEh的联动做得很顺。比如做单据录入界面,客户要求在“物料编码”列里直接下拉选择物料,选中后自动带出规格、单位。用原生控件你得放一个DBNavigator再加一个DBLookupComboBox在表格上方,切换记录时还要担心焦点丢失。
DBGridEh的做法是在列的EditButtons里添加一个按钮,按钮的Style := ebsDropDown,然后指定DropDownForm或者DropDownBox。我比较常用的是配置Columns[i].PickList,直接把一个静态列表塞进去:
DBGridEh1.Columns[2].PickList.Add('A类'); DBGridEh1.Columns[2].PickList.Add('B类'); DBGridEh1.Columns[2].PickList.Add('C类');这样用户点进单元格就会自动弹出一个下拉列表,无需写任何事件代码。更复杂的需求比如“根据A列的值动态决定B列的下拉选项”,可以在OnCellDataChanged里动态改另一列的PickList,实测响应速度非常快,基本无感知。
3.3 打印与导出能力:报表导出Excel不写一行代码
DBGridEh的导出功能是我决定“不换控件”的最重要原因。DBGridEh.PrintDBGridEh方法可以不需要第三方报表控件就完成网格内容的打印和预览,这在做单据打印时非常方便。导出Excel更是好用,只要这一行:
DBGridEh1.SaveDBGridEhToExportFile(TDBGridEhExportAsUnicodeText, 'output.txt');如果想要真正的.xls格式,用TDBGridEhExportAsXLS。实测Xe8 + EhLib 8.x导出到Excel 2003格式没问题,列宽、合并单元格、FooterRow都能带过去。另外注意,导出xlsx(Excel 2007+格式)在EhLib 8.x里支持得不太好,如果你客户一定要求.xlsx,建议用另一个方案:走OLE方式让Excel自己填充数据。
4. 实操过程:在Delphi Xe8下安装DBGridEh的完整记录
4.1 安装前准备:源码、IDE版本、路径规划
开始前先把本机环境理清楚。我建议在干净的虚拟机或者至少独立用户目录下做。需要准备:
- Rad Studio XE8完整安装(建议打上最新Update补丁)
- EhLib对应版本的源码包(我这次用的8.x,从官方GitHub下载)
- 一个专门存放第三方控件的目录,比如
D:\Components\EhLib
一个容易翻车的点:安装路径不要带空格和中文。Delphi的库路径处理在某些老版本里对空格支持有bug,虽然Xe8修了一些,但保险起见全英文路径最省心。
4.2 编译安装包:运行install.bat的正确姿势
EhLib源码包里每个版本都带install.bat或者Install目录下的批处理脚本。双击运行之前,先打开看看里面内容,确认是针对哪个IDE版本的。Xe8对应的是install_xe8.bat。运行前建议把Delphi的IDE完全关掉,包括BDS进程。
脚本执行完,正常情况下它会自动把编译好的包(.bpl)复制到系统目录(或者IDE的bin目录),并把源文件路径写进注册表。但这不是100%可靠,我遇到过一次脚本执行成功但IDE里怎么都找不到控件的情况。原因是用管理员权限运行时,脚本写入了HKEY_LOCAL_MACHINE,而Delphi IDE以普通用户运行时读的是HKEY_CURRENT_USER。解决方式:用和IDE相同的权限级别运行脚本,或者干脆手动在IDE里配置库路径。
手动配置路径的步骤如下:
- 打开Delphi Xe8,菜单
Tools > Options > Environment Options > Delphi Options > Library - Win32 - 在
Library Path里添加EhLib源码目录(比如D:\Components\EhLib\Library,注意还要包含Library\Delphi\XE8这类子目录) - 再到
Component > Install Packages里,检查有没有EhLib相关的运行时包,没有就点Add,找到dclEhLibXXXX.bpl(设计期包),添加进去 - 重新打开一个Form,工具栏上应该能看到
DBGridEh那一排控件图标
4.3 编译顺序与依赖关系
如果你只装了DBGridEh,但运行时不加载其他EhLib兄弟控件,编译时可能会提示找不到EhLibXXXX.dcu。这是因为EhLib的包之间存在依赖,安装时应该把包按顺序编:先编译运行时包(EhLibXX.bpl),再编译设计期包(dclEhLibXX.bpl)。千万不能跳过运行时包直接装设计期包,否则设计期包加载时就报“Can't load package”。
我习惯的做法是打开EhLib.dpk(运行时包项目),在IDE里右键Build,编完后再编译dclEhLib.dpk,然后右键Install。如果编译过程中报找不到DCU,多半是库路径没配全,对照4.2节把路径补充完整再试。
4.4 64位编译的注意点
Xe8最大的价值之一是原生支持64位。但DBGridEh在32位和64位下的编译单元是分开的。安装完32位版本后,记得在Project Manager里把目标平台切换到Win64,然后重新编译一次包。这一步很容易被忽略——很多人只装了32位,然后新建一个64位项目时发现DBGridEh怎么都拖不上去。
在64位下要注意指针类型转换。DBGridEh部分自定义事件里用了TDateTime和TStream的回调,这些在64位下字节对齐要求更高,如果只是使用已经编译好的包,基本没坑。怕的是你为了修bug改到了源码内部,改完32位正常,64位崩溃——这就是典型的指针强转问题,建议改源码时遵循官方代码里已有的写法,不要自己发明类型转换。
5. 常见问题与排查技巧实录
5.1 编译报错与DCU版本不一致
最常见的问题:项目编译时提示“Unit DBGridEh was compiled with a different version of XXXX”。这个错误90%的原因是你项目里引用了一个旧版本的DBGridEh.dcu,而IDE当前库路径里指向的是新版本。排查方式:按下编译快捷键,看输出窗口里找“Search path”那段打印,确认实际加载的.pas/.dcu来自哪个目录。
我的经验是,一个机器上不要同时装两个版本的EhLib。如果需要同时开发多个项目,而它们依赖不同版本,最稳妥的做法是分别建Windows用户账户、分别装IDE,或者用虚拟机隔离。同机多版本即使能装,也极易出现“昨天能编译今天不行”灵异事件。
5.2 运行时数据刷新异常:TClientDataSet的坑
这个跟DBGridEh本身关系不大,但却是DBGridEh配合数据源时最容易踩的雷。热搜里有条TClientDataSet的CloneCursor函数是干什么的——我知道有人想问,为什么DBGridEh刷新后,多出了一个数据集?CloneCursor是把游标(记录位置)共享给另一个TClientDataSet,它们共享同一份数据,但可以拥有不同的过滤和排序状态。如果你在DBGridEh的OnGetCellParams事件里做动态格式化,而这个事件的触发又和主从表结构绑定,千万注意不要在事件里执行ClientDataSet.Close/Open,否则可能触发游标失效,导致“Cursor not currently on a row”之类的运行时报错。
如果一定要在主从结构下动态刷新某列的数据,推荐用DBGridEh1.InvalidateColumn强制刷新某一列,而不是刷新整个数据集。
5.3 中文乱码与字符集适配
Xe8的Unicode支持已经很完善了,但老项目的数据源/数据库可能还是GB2312或GBK编码。DBGridEh显示乱码时,先分清是数据源层面就乱,还是Grid显示层乱。用ShowMessage(FieldByName('name').AsString)打出来,如果也是乱的,说明问题在数据库连接和数据集之间,跟Grid无关。
如果数据源正常但Grid显示乱,检查DBGridEh1.Columns[i].Title.Caption的字体设置,以及Form的Font.Charset是否设成了DEFAULT_CHARSET。有些老代码喜欢把Font.Charset设成GB2312_CHARSET,在XE8的Unicode模式下反而会触发字体映射异常,建议统一改成DEFAULT_CHARSET。
5.4 与其他控件库的冲突排查
装了DevExpress、TMS之类的库再装EhLib,有时会提示“Package ... contains unit ... which is also contained in package ...”。这是不同控件库命名冲突。大多时候发生在vcl相关单元,比如DBGridEh和cxGrid都带了一个DBGrid的注册类。解决方式:在Component > Install Packages里选择性启用,不要一次装太多,或者用Project > Options > Packages里的Runtime Packages设置为“Build with runtime packages”把不需要的包排除掉。
6. 经典业务场景实操:结合热词看DBGridEh的周边玩法
6.1 读取Excel数据并展示到DBGridEh
热词里有delphi 读取excel,我就顺手把这一套流程写在DBGridEh的场景里吧。老项目最常干的事就是把Excel表导入数据库,然后Grid展示。
最快的方式是走ADO连接Excel文件(Jet/ACE引擎),再把数据读进临时表,Grid绑定上去:
// 假设Excel里第一行是标题 ADOTmp.ConnectionString := 'Provider=Microsoft.ACE.OLEDB.12.0;' + 'Data Source=C:\data.xlsx;' + 'Extended Properties="Excel 12.0;HDR=YES;IMEX=1";'; ADOTmp.CommandText := 'SELECT * FROM [Sheet1$]'; ADOTmp.Open; // 等价于打开数据集 DataSource1.DataSet := ADOTmp; DBGridEh1.DataSource := DataSource1; DBGridEh1.AutoFitColWidths := True;这段代码跑起来,Excel表格直接映射到Grid。注意IMEX=1是给混合类型列用的,防止“数字被读成文本”或“文本被截断”。
6.2 局域网消息联动Grid刷新
另一个热词是delphi 局域网 从一个程序 发消息给 另一个程序 sendmessage。在业务系统里,A客户端新增了一条数据,B客户端DBGridEh界面需要实时感知并刷新。除了轮询,也可以用Windows消息。
发送端:
SendMessage(HWND_BROADCAST, WM_USER + 1024, 0, 0);接收端在Form里重载WndProc或者用消息映射,收到消息后调用DBGridEh1.DataSource.DataSet.Refresh。但这个方案有个前提——双方必须跑在同一台机器(消息是窗口级的),跨机器得用Socket或者数据库触发器。如果你的项目是C/S架构,客户端和数据库不在一起,推荐用简单的用ADO重新查询,别折腾消息。
6.3 禁用U盘防止数据外泄
热词里还出现delphi 禁用u盘,这属于DBGridEh周边业务安全需求。在网格类管理软件里,禁止U盘是为了防止用户直接把Grid导出的Excel拷走。Xe8里禁用U盘不复杂,在FormCreate里写一句:
// 比较硬核的写法是注册表策略,这里给个简单的API方式 if not CheckU盘Allowed then MessageBox(Handle, '当前环境禁止使用U盘', '提示', MB_OK);更彻底的方案是调用Windows APISetupDiGetDeviceRegistryProperty枚举存储设备,把U盘设成禁用。但这是Windows驱动层面的操作,风险较大,不建议在业务系统里直接做。一般配合管理员的组策略就够了。其实防君子不防小人,U盘禁了还有网盘,真要泄密怎么都拦不住。
6.4 判断周六日:DBGridEh高亮非工作日的做法
delphi如何判断是周六日,这个热词放在DBGridEh场景里更实用——比如排班表里,周六日列要高亮显示。实现方式是DBGridEh1.OnGetCellParams事件:
procedure TForm1.DBGridEh1GetCellParams( Sender: TObject; Column: TColumnEh; AFont: TFont; var Background: TColor; State: TGridDrawState); var ADate: TDateTime; begin if Column.FieldName = 'WorkDate' then begin ADate := Column.Field.AsDateTime; if (DayOfWeek(ADate) = 1) or (DayOfWeek(ADate) = 7) then begin AFont.Color := clRed; Background := $00F0F0F0; // 浅灰底 end; end; end;这段代码刷新时自动生效,不用手动调用Invalidate(OnGetCellParams每次绘制都会触发)。如果数据量大(上万行),这个事件会被频繁调用,注意别在里面写数据库查询。
6.5 控制WebBrowser缩放:一套界面两种数据展示
还有webbrowser 控制放大缩小,这个我实测过跟DBGridEh组合的场景:Grid展示明细数据,客户还要一个“预览网页样式报表”的按钮,网页里嵌入同样的数据,而且支持Ctrl+滚轮缩放。实现上,用TWebBrowser打开本地HTML模板,数据用JavaScript渲染,缩放用IOleCommandTarget接口:
var CmdTgt: IOleCommandTarget; vaIn, vaOut: OleVariant; begin WebBrowser1.ControlInterface.QueryInterface(IOleCommandTarget, CmdTgt); vaIn := 10; // 缩放百分比增量 CmdTgt.Exec(PGUID(nil), 63, OLECMDEXECOPT_DONTPROMPTUSER, vaIn, vaOut); end;这个场景说明,DBGridEh在系统里往往不只是“一个表格控件”,它是一整个管理系统数据展示的入口,周边联动需求非常多。
7. 从Xe8看向未来:老项目升级的几条经验总结
项目交付后我复盘了一下这次升级的决策链,想分享几条实际经验。
第一,老项目用DBGridEh升级到Xe8,方案是成立且值得推荐的。Xe8对老代码的兼容性在Delphi历史上算是比较友好的一个版本,不仅支持32位也引入了64位支持,DBGridEh从7.x开始对Xe8做了完整适配。如果你手里有Delphi 7/2007的老项目,与其硬着头皮继续在旧环境里修修补补,不如趁早迁到Xe8,后面再升级也有个中间跳板。
第二,不要为了控件本身去升级,而是为了场景去升级。客户要的是“能在新电脑上运行”“能处理大数据量”“能导出Excel”,这些需求的背后是64位内存访问和Unicode支持——这才是升级真正的动机。控件只是船,业务需求的河才是你要过的。
第三,升级前先在仓库里做一个“控件兼容性验证分支”。我当时花了半天时间专门写了个测试项目,把DBGridEh每项核心功能都拖进Form跑一遍,验证无误后才开始动主项目代码。这个预防针值得打,它避免了在主项目里改到一半发现控件不兼容,进退两难的状况。
最后再给一个小技巧。DBGridEh的宿主Form在Xe8里如果感觉滚动有点卡,特别是大批量数据时,试试在FormCreate里加这么一句:
DBGridEh1.OptionsEh := DBGridEh1.OptionsEh - [ghThumbTracking];把这个选项去掉,滚动条拖动时的实时刷新就关掉了,松手才刷新,流畅度提升非常明显。这个取舍要看业务场景——如果客户需要在拖动滚动条时实时看到数据变化,那就保留;如果只是快速浏览,去掉体验反而更好。
这次从接手需求、环境污染排查、控件安装到最后的代码迁移,整个流程走下来,我最大的体会是:老项目升级,技术难度还在其次,对历史包袱的判断和取舍才是核心能力。保持克制,减少变量,一次只改一个维度——这就是我从DBGridEh在Xe8这次实战里学到的最值钱的东西。
本文还有配套的精品资源,点击获取