简介:Delphi 平台知名的 TMS Component Pack v8.3.4.0 控件套件,内含全部源码,覆盖表格、图表、报表、文档处理等常用业务场景,面向需要跨版本维护桌面项目的中高级开发者。原厂版本支持至 XE10.2,本包经修改后可在 XE10.3 中正常安装运行,实测可用,解决了升级开发环境后控件无法加载的兼容性问题。压缩包共两千余个文件,约166.45MB,以源代码、编译单元、头文件为主体,同时包含窗体、资源、工程文件、示例程序与说明文档,目录结构清晰,便于按需检索。截至目前已有697人学习下载。读者既可通读全部源码理解控件实现原理,也可直接安装集成到各版本工程中复用,兼顾源码研究与工程落地,适合需要统一控件库、开展组件二次开发或排查多版本兼容性问题的开发团队与个人程序员。 上周帮同事把一个 Delphi 7 的老项目整体平移到 XE10.3,顺手把开发机上的 TMS Component Pack 统一换成了 v8.3.4.0 full source 版本。同事盯着安装界面问:都这年头了,你还折腾这套老掉牙的东西?我说你只看到版本号老,却没体会过同一套源码从 D7 一路编译到 XE10.3 是什么体验,也没感受过组件内部行为不对时、源码就在手边直接开断点往下追的踏实感。这篇文章就把我这些年用 TMS Component Pack 的核心心得整理出来,重点说清楚 D7~XE10.3 这个跨度意味着什么、full source 版比 dcu 版多出来的实际价值,以及装完以后最容易在哪些位置翻车。适合正在维护老 Delphi 项目、纠结要不要换组件版本、或者第一次接触 TMS 完整源码包的开发者。
1. 为什么一套组件能从 D7 撑到 XE10.3:先说兼容性的真实边界
1.1 十几年的跨度,是条件编译堆出来的"同一套源码"
D7 是 2002 年的产品,XE10.3(Rio)差不多是 2018 年发布的,中间隔了十几年。TMS Component Pack 8.3.4.0 的定位恰好覆盖这段 VCL 开发者的黄金期,同一套源码放进去,既能喂饱老旧的 D7,也能伺候 XE10.3。很多后来接触 Delphi 的人对"版本跨度大"没什么概念,以为就是打开工程文件直接编译这么简单,实际上 TMS 源码里塞满了按 IDE 版本区分的条件编译块。
Delphi 2009 引入 Unicode 是一个巨大分水岭,D7 时代到处是 AnsiString 和 PChar 的假设,2009 之后整个 RTL 字符串模型都变了。TMS 要在同一套单元文件里兼容这两类环境,就必须维护大量{$IFDEF UNICODE}、{$IF CompilerVersion >= ...}之类的判断。XE2 又引入 Win64 平台,单元引用、指针类型、整数类型在 32/64 位下都有差异。所以你打开某个 TMS 源码文件,如果看到一半是各家编译器版本的条件分支,不用惊讶,这些不是给客户看的花架子,是能让一套组件跑遍十几年的地基。
1.2 "D7~XE10.3"不等于万能,边界到底在哪
这句"D7~XE10.3"很容易让人产生错觉:所有组件在这区间所有版本上都应该长得一样、行为一致。实际不是。个别依赖较新系统能力的控件,在 D7 里会被条件编译直接屏蔽掉,面板上根本看不到;反过来,一些老组件在 XE10.3 里也可能因为系统 API 变化而出现绘制差异。所以正确的姿势是:以项目最低 IDE 版本为准,只要最低版本能完整编译并安装所有包,其他版本基本不会出大问题;如果反过来以最高版本为准,拿到 D7 上大概率要删掉一批组件。
还有一个很实际的问题:XE2 之后 VCL 开始支持 Win64,但很多老项目直到今天还在 Win32 上跑。8.3.4.0 这套组件在 Win64 下能不能用,我的经验是核心的网格、编辑、日期控件编译没问题,但冷门组件必须逐个验证。不要想当然认为"源码一样、平台只是换一套编译器参数",64 位下的指针、消息参数、结构体对齐和 32 位差别很大。
1.3 full source 和 dcu-only 的真实差别
没用过 full source 版的人,第一反应是"反正编译出来功能一样,要源码干嘛"。平时确实一样,但一旦控件行为和你预期不一致,差别就出来了。dcu 版只能黑盒调试:报错、闪烁、绘制乱掉,要么猜,要么上网搜,要么绕路。而完整源码版可以直接走进控件内部,看它的鼠标消息处理、绘制状态机、属性赋值逻辑,问题定位从"撞运气"变成"读代码"。对于 TMS 这种自绘控件占比很高的包来说,源码的价值几乎是决定性的。
2. 装包的正确姿势:从目录规划到IDE配置,全流程避坑
2.1 环境准备与目录规划
TMS 8.3.4.0 的源码解压后,第一件事不是急着打开工程,而是先把目录结构规划好。我的习惯是单独建一个无中文、无空格的目录,比如D:\Components\TMS\8.3.4.0,然后在里面把输出目录也分开:BPL\D7、BPL\XE10_3、DCP\D7、DCP\XE10_3。这样不同 IDE 生成的二进制文件不会相互覆盖,切换项目时也不用担心 IDE 加载到错误版本的包。
千万不要把组件放在桌面或者带空格的路径里,D7 的 IDE 对路径解析能力真的很弱,一个空格就能让 BPL 加载失败,排查起来非常绕。另外,各版本源码不要试图混用,每个 IDE 版本打开自己对应的包工程,目录隔离是省事的关键。
2.2 编译顺序:先 Runtime 后 Designtime,先 32 位后 64 位
TMS 的包在工程组织上分成运行时包和设计时包两组。运行时包是控件真正的实现,设计时包负责把组件注册到 IDE 的组件面板上。顺序很重要:必须先编译运行时包,再编译设计时包。如果先装设计时包,IDE 会找不到对应的运行时类,安装会报错,或者面板上出现一堆问号组件。
具体操作上,打开对应 IDE 版本的工程文件(通常目录下会有按版本区分的 .dpk 或 .groupproj),先把所有运行时包编一遍,得到 dcu、dcp、bpl 文件;然后在设计时包的工程选项里确认注册方式,编译后通过 Component > Install Packages 添加生成的 BPL。需要 64 位支持的话,再把平台切换到 Win64 重新编译一套,Win32 和 Win64 的包不能互相复用。
2.3 我踩过的四个坑
坑一:切换 Delphi 版本后 dcu 串包。症状是编译时突然报某个单元是用不同版本的 System.Types 编译的。多半是因为源码目录里残留了上一个 IDE 生成的 dcu,当前 IDE 按 Lib 路径搜到了旧文件。解决办法很粗暴:把源码目录及输出目录里的 dcu、dpu、dcp、bpl 全部清掉,重新编译。
坑二:BPL 路径缺失。安装完包,IDE 启动时提示找不到某个 bpl 模块。这是 Windows 搜索不到 BPL 所在目录导致的。把输出 BPL 的目录加到系统环境变量的 PATH 里,或者在 IDE 的 Tools > Options 里设置环境变量指向它,重启 IDE 就好。
坑三:Class already exists。多版本 BPL 同时注册时常见,老版本设计时包没卸载干净,新版本安装时类名冲突。处理方式是把旧的已安装包先从 IDE 里移除,并删除对应 BPL 文件,再安装新的。
坑四:装完面板上找不到组件。九成是只编译了运行时包,设计时包没装;还有一成是 Tool Palette 的 Filter 没显示出来。到 Component > Install Packages 确认设计时 BPL 在列表里并且已勾选,再搜一下"Adv"关键字,大部分 TMS 组件都能看到。
3. 从读Excel到ClientDataSet联动:几个高频场景的组件选型实录
3.1 读Excel:网格的导入能力比我预想的强
热搜里"delphi 读取excel"一直居高不下,说明这需求在传统 Delphi 业务系统里有多常见。TMS Component Pack 的 TAdvStringGrid 自带 Excel 数据导入能力,也支持把 Excel 复制到剪贴板的内容直接粘贴到网格里。导入后网格的单元格格式、跨列显示、合并单元格虽然做不到 Excel 编辑器那种完整还原,但把一张二维表读进来做预览、编辑、回写是没问题的。
我用它处理过一个信贷系统里的导入场景:客户把征信表粘到 Excel,系统再把这堆数据批量接到审核界面。以前用 OLE 一个个单元格读,几百行数据能转好几秒;换 TMS 网格的导入方式后,速度明显提升,而且不用在客户机器上依赖 Office 是否安装。要注意的是,如果业务里需要精确处理公式、图表、复杂格式,那就别指望组件包里的网格导入,直接上 FlexCel 这类专门组件更合适。
3.2 ClientDataSet.CloneCursor 和 TMS 网格的联动
有人问 TClientDataSet 里的 CloneCursor 函数是干什么的,我简单解释一下:它能让两个或多个 TClientDataSet 共享同一份数据快照,字段结构、内存数据都是同一份,一个数据集移动游标,另一个也会跟着变。经常用在同一个数据集需要在主窗口和弹窗里同时展示、或者需要保留两个不同视图的场景。第二个参数 Reset 传 False 时保留目标数据集现有的过滤、索引等设置;传 True 时把这些都清掉。
我实际的项目里,主窗体用 TMS 网格展示汇总列表,双击某行弹出一个详情窗体,详情窗体绑定的正是主 CDS 克隆出来的另一个 CDS。同一份数据,主窗体高亮选中,详情窗体只读展示,数据源是一个,不需要重复查询,也不用担心两边数据不一致。TMS 网格只关心 DataSource 有没有数据,这个组合非常顺手。
// 复制一份数据游标,两个 CDS 共享同一份数据 CDSDetail.CloneCursor(CDSMaster, False);3.3 日期控件与周六日判断:别把简单问题复杂化
判断周六日属于典型的"看起来要写一堆、实际一行搞定"的需求。Delphi 的 RTL 里 DayOfWeek 返回 1 到 7,周日是 1,周六是 7。行业里很多项目习惯用周一作为一周起点,那可以直接用 DayOfTheWeek,它返回 1 表示周一,7 表示周日。TMS 的日期时间控件拿到用户选择的 TDateTime 后,把值传给 DayOfWeek 就行。
我之前在排班模块里给 TMS 网格做了周末列底色高亮:在单元格绘制事件里判断当前日期,落在周六周日就换一种背景色。代码不复杂,但效果很直观,业务人员一眼能看到哪些天没人值班。这种小需求其实不需要引入任何重型组件,关键是把日期 API 记准确。
uses System.DateUtils; if DayOfWeek(ADate) in [1, 7] then // 周六或周日3.4 WebBrowser缩放、控制U盘、CAN口这类需求:别指望一套控件包通吃
热搜词里还有 WebBrowser 控制放大缩小、禁用 U 盘、CAN 口编程这类问题,这些和 TMS Component Pack 的关系真的不大。TMS 是业务界面组件,解决的是窗体、表格、输入、日期、导航这些偏业务层的需求;WebBrowser 缩放操作的是浏览器内核的 IOleCommandTarget 命令通道,通过给 IDM_ZOOM / IDM_ZOOM_PERCENT 这类命令传参数就能实现页面按比例缩放。U 盘禁用要么走设备管理/注册表策略,要么做驱动层拦截;CAN 口通信大多走串口或者专用总线库。
我的原则很简单:选型之前先问自己,这个问题是界面层还是系统层。界面层找 TMS 这类控件库,系统层老老实实查 Windows API、写协议、调驱动。拿着锤子找钉子,最后只会把自己搞得很难受。
3.5 局域网SendMessage、UniGUI会话超时这类场景,同样要看清边界
"局域网从一个程序发消息给另一个程序"这类需求,底层用的是 Windows 消息机制,简单场景用 SendMessage / PostMessage 配合 WM_COPYDATA 或自定义消息就能做,复杂场景走命名管道、TCP 都行,这些不属于 TMS 的范畴。UniGUI 的会话超时跳转主页则是 Web 框架自己的配置逻辑,更是和 VCL 组件包八竿子打不着。我的建议是:脑子里先分清"这个功能该由谁负责",再动手,否则容易在错误的框架里找答案。
4. 完整源码版到底多了什么:Debug、改包、学底层
4.1 进入组件内部单步调试
我举一个自己遇到的例子。用 TAdvStringGrid 做一个带图标的树形列表,每次刷新数据时最后一列总会出现零点几秒的残影,肉眼能看出背景没擦干净。dcu 版遇到这种问题只能先怀疑绘制事件里哪里写错,反复检查自己的代码却找不到问题。换成 full source 版之后,我直接在组件内部的自绘代码里设断点,看 Canvas 的裁剪区域保存、恢复顺序,一下就定位到是内部处理选中状态时 ClipRect 的恢复时机不对,导致重绘背景时只画了一半。
这种体验在自绘控件身上特别明显。自绘控件的绘制代码量大,状态多,出了问题如果是自己的代码还好查,一旦是组件内部的逻辑,没有源码就只能靠试。而组件供应商也不一定会为老版本提供持续修复,源码就是最后一道保障。
4.2 改源码做定制
客户经常提一些组件默认不支持的小需求。比如 TMS 网格的过滤下拉框默认是英文,业务方要求全中文。full source 版可以直接改过滤面板的字符串常量,重新编译设计时包,装到 IDE 里项目一起构建。这个能力的价值不在于"改一次很爽",而是能让最终交付物和业务语言完全对齐。
改源码要注意三件事:一,每次 TMS 版本升级后自己的改动会被覆盖,务必用 diff 或补丁方式管理,不要直接改原件后不留痕迹;二,license 协议一般要求保留版权声明,别把原作者的标识删掉;三,定制的代码必须在所有目标 Delphi 版本下重新编译验证,防止条件编译分支没覆盖到。
4.3 学习VCL控件体系的绝佳教材
Delphi 老玩家经常说"读控件源码比读万卷书有用",这话在 TMS 源码上体现得很充分。它能教你自绘控件如何响应 WM_Paint、WM_EraseBkgnd,如何写设计期属性编辑器,如何实现属性流的持久化,以及如何在同一个源码文件里用{$IFDEF}兼容不同编译器版本。这套能力,是普通项目业务代码根本不会涉及到的层次。
如果你正卡在"会写窗体、不太懂控件底层"的阶段,把 TMS 某个组件的核心单元从头到尾读一遍,再配合单步调试走一遍绘制流程,你对 VCL 的理解会上一个台阶。D7 到 XE10.3 的跨度越大,这份源码能展示的环境差异和适配技巧就越多。
5. XE10.3 之后的路怎么走:升级还是留守
5.1 新Delphi版本来了,老包怎么办
值得注意的是,热搜里已经出现 Delphi 13.0 Florence、Delphi 13.1 这类字眼。从 XE10.3 再往后,Delphi 版本号确实又走了一截。TMS Component Pack 8.3.4.0 作为止步于 XE10.3 时期的版本,拿到新 IDE 里很难直接编译通过,大概率会遇到 dcu 版本不符、编译器级功能不识别等问题。如果必须用新 Delphi,通常只有两条路:升级到新版的 TMS 组件包(后续的 VCL UI Pack 或 TMS Component Pack 新版本),或者自己把源码用条件编译适配到新环境。
后者的工作量要看项目用了多少组件。如果只是一两个常用控件,适配成本可控;如果把整个包都背上新版本,维护成本会非常可观。我一般不推荐老项目为了追新 IDE 而勉强适配大包,除非有非升不可的理由。
5.2 我的迁移思路
老项目守着老版本其实是正常的。只要目标系统还能正常编译、部署,组件版本稳定、源码在手,8.3.4.0 完全可以陪着老项目走到退役。新项目则相反,直接上当前最新的 Delphi 和新版 TMS,从起跑线上就不给自己添堵。跨版本迁移的时候,建议先把 TMS 组件封装在 Form 或 Frame 层,别让业务代码到处直接引用组件类。这样将来升级组件或替换实现时,影响面能被控制住,而不是牵一发动全身。
我在实际迁移里,会把项目依赖的组件列一个清单,按使用频率排序,一个一个替换验证。替换之后再跑一遍完整回归,确保绘制、绑定、焦点行为没有变化。这套流程虽然慢,但很稳。
最后再分享一个小技巧:一台机器上装多个 Delphi 版本时,库路径最不容易打架。我给每个版本单独建一个启动脚本,在里面设置好 BDS、BPL、DCP 路径后再启动 IDE。D7 项目归 D7,XE10.3 项目归 XE10.3,谁也碰不到谁的包。TMS Component Pack 8.3.4.0 的 full source 版在我这里服役了很长时间,直到现在,维护那些老项目时仍然依赖这套环境。如果你也在那条"老代码还在跑、新需求不断来"的船上,这套配置思路应该能帮你省下不少折腾时间。
本文还有配套的精品资源,点击获取