
简介面向 Delphi 与 CBuilder 开发者的 TMS VCL 控件包完整源码版覆盖从 7 到 13 的 Delphi 版本及对应 CBuilder 环境并针对 64 位 IDE 和高 DPI 显示做了稳定性与渲染优化。控件库包含数据网格、日程排程、富文本编辑器、功能区、图表统计、自动更新及可视化过滤面板等众多模块适合需要深度定制界面、研究高级控件实现原理或构建企业级桌面应用的开发者。包体共两千个文件以 Pascal 源码、窗体定义和工程文件为主体另含图标、图片、PDF 文档、示例数据与数据库文件压缩后约 105.53MB目录结构清晰完整便于按需查阅和编译。目前已有七十六人参与学习下载。通过这套源码使用者可以获得全部控件单元实现、官方演示工程和配套资源既能对照学习高级 VCL 组件的绘制渲染、事件模型与设计期机制也能直接移植常用功能模块到自身项目显著缩短二次开发周期。1. 从 v13.6.1.0 谈起老 Delphi 项目为什么会卡在这套 UI 包上做 Delphi 或 CBuilder 的老开发大概率都在某个项目里见过 TMS VCL UI Pack 的安装界面。这个控件包在 Windows 桌面端的统治力不是靠一两篇文章吹出来的而是靠几十个可商用控件把活干完。v13.6.1.0 这个版本号处在 TMS 组件从传统 VCL 向现代 UI 风格过渡的节点上既有老客户熟悉的 TAdvStringGrid、TAdvMemo也有后来被反复使用的 TAdvGlowButton、TAdvOfficePager 这些皮肤型控件。而 FullSource 完整源码版的意思不是给你一份能看见源码的只读副本而是连控件内部的绘制逻辑、自绘代码、属性实现都摊开在你面前允许你在自己的项目里调试到 VCL 底层。我遇到过不止一个项目组卡在表格控件重绘闪烁、打印预览样式错乱这类问题上最后翻源码才定位到是 TMS 控件内部缓存了一个设备上下文没有释放。这种问题闭源版根本没法查。所以这套完整源码版吸引人的地方不是代码本身有多神秘而是它把“黑匣子”拆开了你可以给控件的内部方法打断点可以看到它绘制表头时到底调用了哪些 GDI 函数可以把它封装好的行为按自己项目的习惯改掉。对于维护老系统、需要长时间和 VCL 共存的团队来说这个价值比新特性列表更实在。接下来我会从安装落地开始逐步拆到控件的接入顺序、常用控件的参数设置再讲到源码级调试和几条我实际踩过的坑。2. 从 .7z 到 IDE 工具栏出现源码版的安装和解包顺序拿到TMS VCL UI Pack v13.6.1.0 FullSource 完整源码版.7z之后第一步不是急着解压而是先确认你本机的 IDE 版本。TMS VCL 组件按 Delphi 版本分目录编译不同版本的 RTL 和 VCL 接口有差异包文件.dpk也是按版本区分的。你装的是 Delphi 10.3 Rio 还是 Delphi 11 Alexandria决定了你需要编译哪个 .dpk硬来只会得到一堆“找不到 System.Win.AppleAPI”之类的报错。我一般先把压缩包解压到一个纯英文路径下避免中文目录和空格引起编译器的“玄学”问题然后按下面的顺序操作。2.1 先看目录结构再决定编译哪些运行时包解压后不要急着双击 .dpk。打开根目录你会看到packages、source、help、samples四个核心目录。source目录里是按控件分类的源码单元比如AdvGrid、AdvOffice、AdvMemo、AdvEdit这些目录名直接对应控件库名称packages目录里是按 IDE 版本组织的工程文件里面有Delphi10.3、Delphi10.4、Delphi11这样的子目录samples里则是官方示例工程是验证编译是否成功最有用的参照物。# Windows 命令行下把压缩包解压到纯英文路径 # 注意最后不要带尾随斜杠的路径习惯tar 在 Windows 下对路径分隔符有要求 tar -xf TMS.VCL.UI.Pack.v13.6.1.0.FullSource.7z -C C:\TMS\src这段命令用了 Windows 10 1803 以后自带的 tar 工具来解压 7z 文件。-C参数指定解压目标目录我这里建议用C:\TMS\src这样短且没有空格的路径可以避免后面编译时 IDE 传参过长导致的奇怪问题。如果你的系统没有 tar也可以用 Bandizip 或 7-Zip 解压但注意不要选“解压到文件名相同的文件夹”那种嵌套结构不然路径会深一层后续打开 .dpk 时容易造成 IDE 误判包名。解压完成之后先打开packages目录看看里面有哪些 .dpk。TMS 的编译顺序有讲究必须先编译运行时包RunTime packages文件名通常是TMS*RunTime.dpk再编译设计时包DesignTime packages文件名通常是TMS*DesignTime.dpk。运行时包会被编译成bpl文件放进 IDE 的bin目录或系统的SysPath设计时包则负责把控件图标注册到 IDE 的工具面板上。顺序反了或者只编译设计时包面板上能看到控件一拖到窗体上就报“Cant load package”的错误。2.2 在 IDE 里编译 dpk 包的三个步骤在 RAD Studio 里打开 .dpk 文件的方法不复杂但有几个细节需要留意。打开后右键点击 Project Manager 里的包节点选择 Build这一步会把当前包及其依赖项全部编译一次。但需要注意的是TMS 很多控件之间存在交叉依赖比如TAdvStringGrid依赖TMSCompGrid的基类所以编译前最好先确认依赖顺序。// 打开某个示例工程前先把搜索路径配好 // Tools - Options - Delphi Options - Library - Library path 中添加 // C:\TMS\src\source\Common // C:\TMS\src\source\AdvGrid // C:\TMS\src\source\AdvEdit这是源码版和闭源版最大体验差异的地方。闭源版安装后 IDE 会自动把编译好的 .bpl 注册进去但源码版需要你先告诉 IDE 到哪里找 .dcu 文件。上面三行路径是 TMS 控件编译时的最小依赖集合Common里有所有控件共享的基础工具类和自绘结构AdvGrid是表格控件的实现单元AdvEdit是编辑框系列的实现单元。如果你的项目只用到了 Office 系列控件把AdvEdit换成AdvOffice目录即可但这些路径是必须加进 Library path 的否则编译示例工程时会有“Unit not found”的中断。路径配好后建议从samples里的GridDemo工程开始试编译。这个工程用了 TAdvStringGrid 的绝大部分常规功能是快速验证包是否完整的试金石。如果这个工程能编译通过说明运行时包基本没问题。如果编译过程中弹出找不到System.NetEncoding这类单元别急着怀疑源码不完整先检查你的 IDE 是否打过 Update 补丁——TMS v13 的某些版本明确要求 Delphi 10.3 以上且安装了最新的 RTL 补丁这个坑和控件本身无关。2.3 编译报错时的三类常见现象和处理手段编译源码包时最影响心情的是报错信息反复指向同一个单元名让人误以为文件缺失。我遇到过几次比较典型的报错第一类是“E2004 Identifier redeclared: TBitmap”这通常是源码里引用了不同版本的System.UITypes和 IDE 自带的类型定义冲突。解决手段是在包工程文件的requires列表里确认是否引用了rtl和vcl这两个基础包并在 Library path 里把你加的 TMS 源码路径顺序放在 Delphi 默认路径之后。第二类是“F2613 Unit TMSCommon not found”这纯粹是 Library path 没配好TMSCommon单元在source\Common目录下路径没指到那里就会报这个错。第三类是“E2225 Never-build package”之类提示意思是某个 .dpk 引用的依赖包从未编译过这时回到packages目录把对应的依赖包按名称顺序编译一遍。注意编译时弹出的“Warning: Unit AdvGrid is not a framework unit”这类警告是正常的不需要处理。TMS 控件大量调用 Windows API 的 GDI 部分这些 API 被警告为缺少平台抽象但不影响 Win32 目标平台的使用。3. 接入旧项目的正确姿势先用 Grid 和 Edit 包把界面跑起来源码版安装好之后最容易犯的错是把几十个 TMS 包全部加到项目的requires列表里。这样做的直接后果是最终生成的 exe 体积暴增几十 MB启动时还要加载一堆你用不到的包内存占用也跟着涨。TMS VCL UI Pack 不是一个大一统的包它里面每个控件系列是独立成包的所以正确的做法是“按需引用”。对于一个典型的进销存或桌面管理软件最常用到的是 TAdvStringGrid表格、TAdvEdit输入框、TAdvGlowButton按钮这三个系列。3.1 用一个空窗体验证最基本的控件可用性先创建一个新的 VCL 应用程序然后在窗体上放一个 TAdvStringGrid 和一个 TAdvEdit。这两个控件如果能在设计期正常显示、能调整列宽、能在属性编辑器里看到各自特有的属性那说明运行时包和设计时包都已经生效。接下来写一段最基础的数据填充代码验证运行时表现。procedure TForm1.FormCreate(Sender: TObject); begin // 设置表格为 3 列分别表示编号、名称、数量 AdvStringGrid1.ColumnCount : 3; AdvStringGrid1.FixedCols : 0; // 固定列设 0让三列都能横向滚动 AdvStringGrid1.RowCount : 2; AdvStringGrid1.Cells[0, 0] : No.; AdvStringGrid1.Cells[1, 0] : Name; AdvStringGrid1.Cells[2, 0] : Qty; // 设置列宽单位是像素不是字符 AdvStringGrid1.ColWidths[0] : 80; AdvStringGrid1.ColWidths[1] : 200; AdvStringGrid1.ColWidths[2] : 80; // 用行插入方式追加一条数据 AdvStringGrid1.AddRow; AdvStringGrid1.Cells[0, 1] : A001; AdvStringGrid1.Cells[1, 1] : Widget; AdvStringGrid1.Cells[2, 1] : 12; // 把第 2 列设为只读避免用户误改 AdvStringGrid1.ReadOnly : False; AdvStringGrid1.Columns[1].ReadOnly : True; end;这段代码演示的是 TAdvStringGrid 最基础的用法。ColumnCount直接指定列数注意和ColCount不同ColumnCount是 TAdvStringGrid 特有的属性它和内部的 TAdvGridColumn 集合绑定能让你设置每列的独立属性FixedCols控制左侧固定列的数量设为 0 可以让所有列跟随横向滚动条滚动AddRow会自动把RowCount加 1同时保留列结构。Columns[1].ReadOnly是针对单列设置只读比设置整个网格只读更灵活这在 BOM 表维护场景里很实用——数量列允许编辑名称列锁定。跑通这段代码后你就有了一个可以扩展的基础后续的排序、筛选、合并单元格都是在这个网格上叠加行为。不要一上来就追求所有高级特性先确认控件能在你的项目里稳定运行再谈功能增强。3.2 TAdvEdit 的数据校验和输入控制参数TAdvEdit 相比原生 TEdit 的优势在于它自带一组数据感知类属性不需要额外写 OnExit 事件就能做输入范围校验。我通常会在库存录入界面里用它来约束数字输入。// 配置一个只允许输入 0 到 9999 的整数编辑框 AdvEdit1.NumericOnly : True; AdvEdit1.MinValue : 0; AdvEdit1.MaxValue : 9999; AdvEdit1.ValidationType : vtInteger; AdvEdit1.CheckOnExit : True; // 焦点离开时触发校验 AdvEdit1.Alignment : taRightJustify; // 数字右对齐这里ValidationType : vtInteger定义了校验类型是整数MinValue和MaxValue一起构成了范围限制。CheckOnExit设为 True 后当用户把焦点从编辑框移走时TMS 会自动检查当前值是否在范围内超出就弹默认提示不需要你自己写if then判断。把Alignment设成taRightJustify是财务单据界面的常见习惯数字右对齐方便纵向比较。这些属性在闭源版里也一样能用但源码版能让你看到CheckOnExit内部调用的是哪个 Windows 消息在特殊控件嵌套比如放在 TFrame 里时更容易排查焦点移交问题。3.3 不要在项目里一股脑引用所有包的三个理由很多第一次用源码版的人看到packages目录里几十个 dpk 就产生一种“全编了才不亏”的错觉。我建议克制住这个冲动理由有三点第一TMS 每个包都有独立的初始化逻辑全量引用会让程序启动时多出几百毫秒的加载时间对老机器上的客户端软件来说体感很明显第二包的依赖关系不是线性的某些包会触发对第三方组件如 Devart 或 FastReport的依赖不装这些第三方库时编译直接失败第三维护视角上全量引用后如果某个包需要升级或打补丁你要重新编译的包数量会翻倍。我自己的习惯是维护一个“白名单”列表只保留当前项目用到的包名新需求来的时候先看看能否用已有控件实现实在不行才把新包加进来。提示TMS VCL UI Pack 里的 TAdvStringGrid 和标准 TStringGrid 在内存模型上不是一回事前者内部用 TList 管理行对象后者用二维数组。如果你在代码里把 AdvStringGrid 当普通 StringGrid 用比如直接调Rows[1].Add会发现在某些版本里这个方法不存在需要改用AddRow或InsertRows。4. 从“能跑”到“好用”TMS 控件批量接入时的布局与配置顺序把单个控件放到窗体上验证功能这个环节人人都能完成。真正让项目进度拉开差距的是几十个窗体成批迁移到 TMS 控件时布局策略、属性统一配置、资源复用这些系统性问题。我见过一个项目组把所有 TAdvEdit 的Text属性清空然后跑到 FormShow 里一个个赋值代码写了几百行后来发现 TMS 的TAdvEdit.Text在TextChanged事件里会触发格式化操作批量赋值时性能很差。这类问题不是控件本身的 bug而是使用方式没有利用好它的事件机制。4.1 控件命名规范和事件分发别让 OnChange 到处散落当一个窗体上有四五个 TAdvEdit 时每个编辑框都挂一个 OnChange 事件处理器代码维护成本会快速上升。我一般会用一个统一的OnEditorChange事件分发方法在事件参数里判断是哪个编辑器发出来的。TMS 的 TAdvEdit 继承了TEdit的消息机制所以它的Sender参数是有效的可以直接用TComponent(Sender).Name判断来源。procedure TForm1.OnEditorChange(Sender: TObject); var Edt: TAdvEdit; FieldName: string; begin if not (Sender is TAdvEdit) then Exit; Edt : Sender as TAdvEdit; // 根据编辑框的名称映射业务字段 FieldName : Edt.Tag.ToString; // 我在设计期把 Tag 设为字段序号 case FieldName.ToInteger of 1: FOrderData.Qty : Edt.FloatValue; // FloatValue 返回数值比 StrToFloat 安全 2: FOrderData.Price : Edt.FloatValue; 3: FOrderData.Amount : Edt.FloatValue; end; end;Tag属性是 Delphi 所有组件自带的整数标签。我在这里让它在运行时充当字段序号这样多个 TAdvEdit 可以共用一个事件处理入口。在窗体上有大量输入框时这种写法的可维护性比到处挂独立事件高得多。注意这里用的是Edt.FloatValue而不是StrToFloat(Edt.Text)因为 TAdvEdit 内置了数值转换逻辑当NumericOnly或者ValidationType配置好之后它返回的值一定是可以安全参与计算的数字不会因为用户输入空字符串或千分位格式而抛异常。4.2 表格列配置的三种状态可编辑、只读、通过下拉选择TAdvStringGrid 的列状态管理是我见过项目组最容易用混的地方。Columns[].ReadOnly只是控制用户是否能直接输入不控制下拉框是否能弹出而Columns[].Editor才是决定编辑器的类型。要通过下拉列表选择数据需要为列指定TAdvEditColumn或者TAdvComboBoxColumn这类的编辑器对象。var ComboCol: TAdvComboBoxColumn; begin // 在第三列加入下拉选择编辑器 ComboCol : TAdvComboBoxColumn.Create(AdvStringGrid1); ComboCol.Name : StatusCol; ComboCol.Items.Add(Pending); ComboCol.Items.Add(Approved); ComboCol.Items.Add(Rejected); ComboCol.DropCount : 3; // 下拉列表显示三行超过则需要滚动 AdvStringGrid1.Columns.Insert(2, ComboCol); end;这一段代码的关键点是TAdvComboBoxColumn是作为一个独立的列对象插入到Columns集合中的而不是通过设置某个属性完成的。创建后必须调用Columns.Insert把它挂到指定索引位置否则它只是一个孤立对象表格不会显示下拉效果。DropCount控制下拉区域一次性显示的行数条目超过这个值会出滚动条。这个用法的好处是下拉框的数据源可以动态增减不需要像传统 VCL 那样在单元格里拼字符串。4.3 按窗体用途划分三类页面查询页、编辑页、报表页另外一个容易让项目后期变得难维护的原因是不管什么窗体都用同一套 TMS 控件属性配置。查询页的重点是快速出数据和列排序编辑页的重点是输入约束和联动校验报表页的重点是打印样式和列宽自适应。TMS 里的 TAdvStringGrid 针对这三类场景有各自的属性组。查询页建议开启Filter相关的功能并关闭 cell 编辑编辑页关闭列排序和筛选避免用户误点表头触发数据错位报表页则需要配置PrintSettings和AutoSize相关选项。我一般会写三个基类窗体把每种场景的默认属性预置好业务窗体只继承对应的基类这样一来新窗体从创建到控件行为正确时间能从半天压缩到一两个小时。5. 避坑手册从编译器报错到运行期异常的 5 条踩坑记录源码版控件安装后最大优势是能深挖问题但代价是有更多机会踩到底层地雷。这些坑看起来各自独立根源往往都指向控件的内部资源管理和 IDE 版本差异。这里整理我在不同项目中实际遇到过的 5 类代表性故障每条都按现象、原因、解决三个步骤来写。5.1 新增一个 TAdvStringGrid 后 IDE 直接崩溃现象在新窗体上放置控件时点一下工具栏图标 RAD Studio 就发生 Access Violation窗体编辑器打不开删掉该控件依赖的包后在别的机器上又复现。原因设计方案时TMS 包在Register过程中调用了Classes.RegisterClass对内部类做全局注册而这个注册过程在 IDE 启动时和其他插件冲突。解决先确认安装版本和 IDE 的 Update 级别匹配其次检查 Tools Options VCL Designer 里的“Enable runtime theme”设置这个选项被 TMS 自绘引擎依赖关闭后会导致控件在 IDE 中初始化崩溃最后如果仍然复现按Win R输入%APPDATA%\Embarcadero找到对应版本目录下的.dsk文件备份后删除再重开 IDE。5.2 编译项目时提示“Unit AdvGrid is not a framework unit仍继续”警告后程序界面空白现象编译通过运行也启动但窗体上的区域一块空白点击按钮无响应。原因这是 TMS 的智能链接机制在起作用AdvGrid单元被链接器排除因为它在项目中没有任何显式引用的位置。源码版和闭源版在.bpl包的Package声明上有一个细微差异源码版的运行时包默认不强制绑定所有单元当你的项目不是通过包文件引用 TMS而是通过uses引用某个.pas文件时链接器会认为控件没有引用到完整依赖树。解决在项目选项的Runtime Packages里把 TMS 的运行时包名加进requires列表例如TMSGridRunTime让工具知道它需要把整套网格单元链接进 exe。5.3 TAdvEdit 设置了 MinValue 和 MaxValue 但校验不生效现象运行界面里用户可以输入超过 MaxValue 的数字焦点移走时没有报错。原因CheckOnExit : True只是触发了校验流程的前半段后半段是ValidationType必须与控制类型一致当ValidationType : vtInteger而用户输入了12.5TMS 会先尝试字符串转整数失败后直接放弃校验而不是提示。解决把ValidationType改成vtFloat或保留vtInteger但把EditMask也加好比如EditMask : 9999这样非法字符在输入阶段就被拦截。不要依赖MinValue单独工作这两个属性必须配合。5.4 AdvStringGrid 导出 Excel 时中文表头变成乱码现象调用SaveToXLS导出后打开 Excel表头是乱码数据单元格内容正常。原因TMS 的 XLS 导出路径内部使用 GBK 编码生成字符串而新版 Excel 默认按 UTF-8 解析sharedStrings.xml。这个现象在闭源版和源码版里都存在但源码版里你能直接定位到XLSExport单元中的TStringList.SaveToFile调用。解决用源码版时可以在SaveToFile前手动改编码var SL: TStringList; begin SL : TStringList.Create; try SL.LoadFromFile(export.tmp); // 让 TMS 把内容写入临时文件 SL.SaveToFile(final.xls, TEncoding.UTF8); // 重新按 UTF-8 保存 finally SL.Free; end; end;这里的核心点是先让 TMS 的导出逻辑跑完再用 TStringList 读回来做一次编码转写。直接用TEncoding.UTF8覆盖保存会把 BOM 头也写入Excel 识别 BOM 后会正确处理中文。如果你不想引入这个额外步骤在 TMS 官方的数据导出组件分支里强制指定XMLEncoding也可以但代码侵入会更大。5.5 用 FullSource 编译示例工程时出现 “Fatal: File not found: System.NetEncoding.dcu”现象无论怎么调整 Library path示例工程都编译不过报错定位到System.NetEncoding。原因这个不是 TMS 工程本身的问题而是 Delphi 安装时没有勾选对应平台的 RTL 源码。TMS v13 部分示例工程引用了System.NetEncoding来处理 JSON 和网络流而该单元在 Delphi 10.3 的 RTL 中是有条件编译的只有安装了Windows SDK或启用了System.Net相关单元时才生成.dcu。解决打开Tools Options Environment Variables确认PLATFORM是Win32然后在Tools Options IDE Rebuild里重新编译 RTL。如果还不行直接在示例工程里把System.NetEncoding从uses中移除——示例工程里对它的引用通常只是为了 JSON 序列化演示删掉后控件的核心功能不受影响。6. 源码版的核心利润点翻源码找边界再用断点验证把“FullSource 完整源码版”和普通二进制版区分开来的不是代码文件的下载体积而是你在后续开发和维护时能拿它干什么。普通版遇到问题只能通过属性和事件配置去规避源码版则允许你直接给TAdvGridBase.ValidateEditCell这类内部方法下断点看清它在用户输入时的完整调用链。但要提醒一句源码版不是拿来改源码的——除非你想永久维护一个内部 fork否则更明智的做法是通过源码读懂边界再把配置写在事件里。具体到实际项目源码版最有价值的功能体现在三方面第一定位绘制闪烁和自定义绘制的解锁点。比如 TAdvStringGrid 的自绘表头你可以翻到DrawColumnHeader方法看到它内部对Canvas.Brush和Canvas.Font的处理次序然后就能解释为什么在某些缩放比例下表头文字发虚。第二搞懂控件的资源生命周期。TMS 源码里大量使用TComponent的Owner机制如果你在运行时去掉窗体上的控件比如FreeAndNil了一个 AdvGrid但没有把子编辑器对象释放源码里能看到TAdvGridColumnList的析构函数里是否有遍历释放代码这是排查内存泄漏的硬依据。第三属性修改后能不能 hot patch。在编译后的 exe 里你改不了属性默认值但源码版能在 IDE 中临时修改某个属性的 Default 数组再编译快速验证一个新方案而不影响整体使用。验证源码包是否完整的最直接方法是在 IDE 的Project Manager里右键点击包名选择View Source。如果能打开一个包含Register过程和类声明列表的.pas文件基本说明这是真正的完整源码分发版如果只能看到.bpl类型的二进制包那就不是。对于长期维护 Windows 客户端项目的团队来说源码版的价值不是让你去重写控件而是让你在遇到问题时有资格问“它内部到底干了什么”——这对于缩短排查时间和提高技术判断的可信度非常关键。希望这份从安装到排障的路径对你有用也让你的 TMS 项目少走几趟弯路。本文还有配套的精品资源点击获取