简介:这是TMS VCL UI Pack v13.4.0.1的完整源码包,专门面向使用Delphi 7至12 Athens及C++Builder的程序员,适合在企业级Windows桌面应用开发中快速构建现代界面。整套组件包含界面布局、图表、网格、导航、皮肤、多媒体等常用VCL单元,能够大幅减少控件设计与交互逻辑的重复编码工作。压缩包共2000个文件、123.13MB,其中502个pas源码文件、240个dfm窗体、228个dproj工程、211个dpr项目,配合res、ico、png、jpg等图形资源,以及47个pdf、43个csv、11个mdb等文档与示例数据库,能覆盖从设计期到运行期的各类使用场景,方便逐一对照工程验证功能。已有45人学习下载,适合希望深入组件内部实现、排查版本兼容性或在RAD Studio不同版本间迁移项目的开发者。通过阅读源码和官方示例,可以掌握TMS控件的对象关系与事件机制,并在此之上定制满足自身业务需要的拓展组件。
1. Delphi 12.3 下重新认识 TMS VCL UI Pack Full Source
Delphi 12.3 Athens 发布后,老项目里最尴尬的不是语法升级,而是原来那批控件在新的 IDE 里“失联”。TMS VCL UI Pack v13.4.0.1 Full Source 就是典型例子:标题看起来像资源文件,背后其实是三件事——RAD Studio 12.3 的 IDE 兼容、Delphi 与 C++Builder 两套工程下的包格式差异、以及 Full Source 带来的可调试边界。实际装起来最耗时间的不是控件本身的功能,而是包顺序、Library Path、bpl 冲突这些环境问题。这篇文章按安装、编译、写代码、排错的顺序展开,代码基于 RAD Studio 12.3 的默认工程结构给出,适合正在维护老 VCL 项目的工程师,也适合第一次往 Community Edition 里补控件的人。
2. TMS VCL UI Pack 的包机制与 Full Source 的调试价值
2.1 一套包两个入口:.dpk 与 .cbproj
TMS VCL UI Pack 在发布时会同时准备好 Delphi 和 C++Builder 两套工程文件。Delphi 侧是 .dpk 文件(Package 工程),C++Builder 侧是 .cbproj 或 .bdsproj。它们的职责一样:把 Source 目录下的 .pas 单元编译成 .bpl 动态库,并生成编译器所需的 .dcu(Delphi)或 .lib/.bpi(C++Builder)。一套源码,两套构建入口,这是 VCL 控件包最常见的组织方式。
打开一个典型的 .dpk,能看到这样的结构:
package tmsvcluipack_runtime; {$R *.res} requires rtl, vcl; contains AdvStyle in 'Source\AdvStyle.pas'; AdvGrid in 'Source\AdvGrid.pas'; AdvGlowButton in 'Source\AdvGlowButton.pas'; end.这段代码定义了一个名为tmsvcluipack_runtime的包。requires声明它依赖 RTL 和 VCL 基础包,contains列出要编入包的具体单元。编译成功后生成tmsvcluipack_runtime.bpl,IDE 启动时加载这个 bpl,控件才能出现在组件面板上。C++Builder 侧的过程类似,但链接时用的文件换成了 .lib,运行时仍然依赖同名 .bpl,所以两边共用一套二进制包是可行的。
2.2 Full Source 拿到的不只是源码,还有进入调试的路径
“Full Source” 这个概念经常被低估。它真正改变的是调试方式:F7 按下后,IDE 能跳进控件的 .pas 源文件,而不是停在 CPU 窗口里看汇编。要做到这一点,光有源码不够,还得保证源码路径被编译单元索引到,否则 IDE 只会把源码当成普通文本文件。
验证 Full Source 是否生效,有一个很直接的办法。在窗体代码里写一行:
AdvStringGrid1.RowCount := 10;在它后面打断点,F7 步进。如果跳到了AdvGrid.pas的具体实现,说明源码路径解析正常;如果跳进汇编窗口,问题不在包,在 Library Path。这个检查在新装任何控件包后都值得做一次,半小时能省下后面好几个晚上的排查时间。
2.3 Runtime 包与 Design 包:安装顺序决定能不能看到控件
TMS 的包按作用分两类。Runtime 包提供编译单元,Design 包负责把控件注册到 IDE 组件面板。Design 包编译时依赖 Runtime 包的 bpl,顺序错了就会提示找不到某个包。先编 Runtime,再编 Design,这个顺序基本不会出问题。
| 包类型 | 产物 | 组件面板 | 作用 |
|---|---|---|---|
| Runtime 包 | .bpl + .dcu / .lib | 不出现 | 提供编译和链接所需的单元 |
| Design 包 | .bpl | 出现 | 注册控件,显示属性与事件 |
安装时最容易犯的错误是只装了 Design 包。这样 IDE 面板能看到控件,但编译工程时会报找不到单元,因为 Runtime 包对应的 .dcu 没有进入搜索路径。反过来只装 Runtime 包,工具栏是干净的,代码里能 uses,但设计期看不到图形化控件。
2.4 从 7 到 12:条件编译与 Unicode 迁移
TMS 声称覆盖 Delphi & C++Builder 7-12,这个跨度靠的是条件编译和宏判断。Delphi 7 时代是 AnsiString 为主,2009 年引入 Unicode 后,字符串类型发生了根本变化。控件内部常见的写法是:
{$IFDEF UNICODE} s := Edit1.Text; // UnicodeString {$ELSE} s := Edit1.Text; // AnsiString {$ENDIF}这套机制保证了同一份源码在不同版本下行为一致。对使用者来说,真正要注意的是老工程迁移到 12.3 时,PChar和PAnsiChar的混用会暴露出来。TMS 控件表面上看编译很顺,但自己代码里的AnsiString(控件.Text)强制转换,在 Delphi 12.3 下会得到截断数据。迁移时优先用string,不要反向转 Ansi。C++Builder 侧同理,UnicodeString和std::string之间的转换需要用TEncoding做过桥,直接强制转换在东亚字符集环境下容易出现乱码。
3. 在 RAD Studio 12.3 Athens 里安装 TMS VCL UI Pack
3.1 解压后先看目录结构,再决定装哪些包
拿到 v13.4.0.1 的压缩包,不要直接双击某个 dpk 就开始装。先完整解压到一个单独的组件目录,我一般放在C:\Components\TMSVCLUI。目录结构通常包含这几个部分:
| 目录 | 内容 | 作用 |
|---|---|---|
| Source | .pas 源文件 | 编译包的源码,也是调试时进入的文件 |
| Packages | .dpk 与 .cbproj | 各类控件的包工程文件 |
| Lib | 预编译的 .dcu 和 .bpl | 如果发布版带的话,可以跳过编译直接用 |
| Docs | 帮助文档,CHM/HTML | 查阅具体控件的属性说明 |
解压路径不要含中文,也不要放到C:\Program Files下。Windows UAC 对系统目录有写保护,bpl 注册和 dcu 缓存写入都会失败,报错信息还不直观。
3.2 用 IDE 的 Project Manager 安装 .dpk / .cbproj
安装的标准流程是:打开 RAD Studio 12.3,选择 File > Open Project,定位到 Packages 目录,按名称顺序打开包工程。先选 Runtime 包,右键 Compile,成功后再右键 Install。然后回头打开 Design 包,做相同的两步。
File > Open Project > TMS*.dpk Project Manager > 右键包名 > Compile Project Manager > 右键包名 > InstallInstall 动作会往 IDE 的已知包列表里写注册信息,组件面板随即刷新。对 C++Builder 来说,入口同样是 File > Open Project,但选择 .cbproj 文件。编译选项里注意目标平台的配置,TMS 的包在 32 位和 64 位 Windows 下都要分别编译,只在 Win32 下编译过,切到 Win64 平台时仍会报找不到包。
3.3 Library Path 配错,Full Source 等于白拿
安装完成后还有一个必做步骤:配置 Library Path。路径的位置在 Tools > Options > Delphi Options > Library。把 Source 目录加进搜索路径,IDE 在编译工程时才会用自己的编译器重新编译源码,而不是依赖预编译的 dcu。
C:\Components\TMSVCLUI\Source C:\Components\TMSVCLUI\Packages路径顺序会影响源码冲突时的解析优先级。如果机器上同时还装过旧版本的 TMS,较新的 Source 路径要排前面。C++Builder 用户在 C++ (Shared Options) > Paths and Directories 里配置同一批路径,另外还要把生成的 .hpp 文件目录加进 Include Path,否则#include <AdvGrid.hpp>会直接失败。
3.4 命令行编译 .dpk 的替代路径
运维环境或 CI 机器上没有图形界面时,可以用 msbuild 编译。RAD Studio 12.3 自带的 msbuild 需要先通过rsvars.bat初始化环境变量:
call "C:\Program Files (x86)\Embarcadero\Studio\23.0\bin\rsvars.bat" msbuild "C:\Components\TMSVCLUI\Packages\TMSGridLib.dpk" /t:Build /p:Config=Release /v:m/t:Build对应 IDE 里的 Build 动作,和 Compile 的区别是 Build 会检查所有依赖项,Compile 只编当前包。/p:Config=Release指定 Release 配置,避免把调试符号带进运行时。/v:m把日志级别设为 minimal,失败时切到/v:d能看到完整命令行列出的 dcc32 参数,定位哪个单元编译失败比在 IDE 日志里翻效率高得多。
3.5 安装验证:三分钟检查清单
装完别急着写业务代码,用这个清单确认环境。第一,新建一个 VCL 工程,在组件面板搜索TAdvStringGrid,能拖进窗体算过;第二,往代码里写一行AdvStringGrid1.RowCount := 1,F7 能进入AdvGrid.pas算过;第三,C++Builder 里建一个空工程,#include <AdvGrid.hpp>后编译,LNK 错误为零算过。
| 检查项 | 方法 | 通过标准 |
|---|---|---|
| 组件面板注册 | 搜索 TAdvStringGrid | 能拖入窗体 |
| 源码调试 | 赋值后 F7 步进 | 进入 .pas 源码 |
| C++Builder 链接 | #include 头文件后编译 | 无 LNK 错误 |
三个检查都过,才说明包安装到位。任何一步失败,回头翻 3.2 的包顺序和 3.3 的路径配置,问题基本出在那里。
4. 实战:用 TAdvStringGrid 做一个可跑的网格界面
4.1 设计期放置控件与 DFM 属性
TMS VCL UI Pack 里最常用的是 TAdvStringGrid。它在设计期的表现类似 StringGrid,但对单元格格式化、合并、排序和导出支持更好。从组件面板拖一个到窗面上,把ColCount设为 3,RowCount设为 6,FixedRows保留为 1,用来做表头。对应 DFM 描述大致是:
object AdvStringGrid1: TAdvStringGrid Left = 24 Top = 24 Width = 640 Height = 360 ColCount = 3 RowCount = 6 FixedRows = 1 Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine] ColumnHeaders.Strings = ( '名称' '数量' '备注') endColCount和RowCount决定网格的维度,FixedRows指定顶部冻结行数,被冻结的行不会跟着滚动条移动。ColumnHeaders.Strings按列顺序写入表头文字。这里有个容易看错的地方:Cells数组的下标[行, 列],和视觉上“先列后行”的顺序相反,写数据时容易把行列颠倒。
4.2 运行时动态创建 TAdvStringGrid
设计期拖控件省事,但动态创建更接近真实业务场景,比如从配置文件决定网格的列数。动态创建的代码也不复杂:
procedure TForm1.CreateGrid; var Grid: TAdvStringGrid; begin Grid := TAdvStringGrid.Create(Self); Grid.Parent := Self; Grid.Left := 16; Grid.Top := 16; Grid.Width := 620; Grid.Height := 300; Grid.ColCount := 3; Grid.RowCount := 4; Grid.FixedRows := 1; Grid.Cells[0, 0] := '名称'; Grid.Cells[1, 0] := '数量'; Grid.Cells[2, 0] := '状态'; Grid.Align := alTop; Grid.Visible := True; end;Create(Self)传入 Self 作为 Owner,窗体销毁时控件会自动释放,不需要手动 Free。Parent决定控件显示在哪个容器上,漏掉这一步,控件创建成功但看不见。Align := alTop在设置完位置后再付,避免对齐方式覆盖手动指定的 Left 和 Top。C++Builder 侧的写法结构一样:
TAdvStringGrid* grid = new TAdvStringGrid(this); grid->Parent = this; grid->Width = 620; grid->RowCount = 4; grid->ColCount = 3; grid->FixedRows = 1; grid->Cells[0][0] = "名称";4.3 常用属性与事件参数对照
TAdvStringGrid 的使用频率高,初始化阶段以下几个属性最常调:
| 属性 | 类型 | 作用 | 常见误用 |
|---|---|---|---|
| RowCount | Integer | 总行数 | 忘记加表头行 |
| ColCount | Integer | 总列数 | 数据行写入越界 |
| FixedRows | Integer | 冻结表头行数 | 设为 0 后表头滚动 |
| Options | TGridOptions | 是否可编辑、拖拽等 | 全部打开后误触编辑态 |
事件侧最常用的是OnClickCell,它的参数里带Col和Row,在单元格点击时做联动操作。还有OnGetAlignment,可以针对单列动态返回对齐方式,比在设计期逐列设置Alignment灵活。需要注意OnClickCell的触发条件是鼠标按下弹起,拖拽选区的过程中不会触发,需要拖拽场景时改用OnMouseDown补判断。
4.4 SQLite 中文乱码与 UTF-8 转换
Delphi 12.3 是 Unicode 体系,TMS 控件内部用的是UnicodeString,本身不会乱码。乱码通常出在数据接入层:SQLite 默认以 UTF-8 存储文本,如果用AnsiString直接接收,中文会变成“锟斤拷”。常见做法是把 SQLite 接口取到的TBytes显式转成 UTF-8 字符串:
var Raw: TBytes; Value: string; begin Raw := SQLite3GetColumnBytes(Stmt, 0); Value := TEncoding.UTF8.GetString(Raw); AdvStringGrid1.Cells[0, 1] := Value; end;TEncoding.UTF8.GetString把原始字节数组按 UTF-8 规则解码为UnicodeString,这样再写入网格就不会变形。如果数据源本身是 UTF-16(例如从 .NET 导出的数据),直接取字符串赋值即可,不用做转换。判断依据是数据源的导出声明,SQLite 默认按 UTF-8 处理,MySQL 旧版本则按连接字符集走,不能一概而论。
4.5 C++Builder 侧的使用差异
C++Builder 调用 TMS 控件时,需要注意.hpp头文件的引入时机。#include <Vcl.AdvGrid.hpp>要放在窗体头文件之前,否则窗体类里声明 TAdvStringGrid 成员时编译器会报未定义类型。动态创建时,new TAdvStringGrid(this)后同样要先->Parent = this,再设置其他属性。编译命令里#pragma link "tmsvcluipack_runtime.lib"通常不需要手写,因为 .cbproj 里已经包含了链接库路径,手写反而容易因为路径硬编码导致换机器后编译失败。
5. 三个典型坑与一个验证技巧
5.1 找不到 .bpl:先查 Runtime 包和系统目录
Delphi 12.3 下最常见的运行时错误是Cannot load package ... .bpl或... is not a valid package。先确认一件事实:报错对应的包是 Runtime 还是 Design。Design 包找不到,说明 IDE 注册信息指向了不存在或被移动的 bpl,重装 Design 包即可。Runtime 包找不到,可能是系统 PATH 环境变量里没有组件目录。TMS 的安装说明一般要求把 bpl 所在目录加入 PATH,我习惯的做法是直接把C:\Components\TMSVCLUI\Packages加进去,而不是把 bpl 复制到 System32,后者会在版本升级时留下旧文件,导致 IDE 加载到一个不匹配的旧包,错误信息反而更扭曲。
5.2 WebView 导航无反应与 WebView2 依赖
TMS 的高版本控件里,依赖 WebView2 的组件越来越多。遇到导航无反应,先检查两个点:系统有没有安装 WebView2 Runtime,以及控件有没有指定可写的用户数据目录。用户数据目录不设置时,控件用默认位置,如果被安全软件锁住,导航调用会静默失败。设置一个独立的目录能避开大部分问题:
TEdgeBrowser1.UserDataFolder := ExtractFilePath(ParamStr(0)) + 'EdgeData';ParamStr(0)取当前程序目录,加上EdgeData子目录。目录不存在时 WebView2 会自己创建。导航前用InitializeAsync确认初始化状态,返回值非零时不要调用 Navigate。
5.3 HTTPS 证书无效的托底处理
TMS 的 HTTP 控件在请求 HTTPS 接口时,如果服务器证书链不完整或证书过期,会直接抛证书无效的异常。这个问题的处理应该在完成了正常的证书校验逻辑之后,作为内网联调环境的托底手段:
IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmClient; IdSSLIOHandlerSocketOpenSSL1.SSLOptions.VerifyMode := [];VerifyMode置空表示跳过证书链验证,代码能正常握手。这只能用于内网测试或调试阶段,外部生产环境关闭所有校验会让数据完整性和保密性完全失效,属于硬性风险。留作联调期间的临时方案没问题,上线前务必恢复成默认校验或补充正确的根证书。
5.4 用 F7 验证安装结果的 30 秒检查
装完 TMS 后,最快验证调试链路的做法是:打开安装时创建的测试工程,给AdvStringGrid1的RowCount赋一个新值,在当前行打断点,按 F7。正常情况下会直接进入AdvGrid.pas的SetRowCount方法。如果停在一个只写mov dword ptr [rax], 5的汇编块上,说明 Library Path 里的源码目录没有生效。处理方法是清掉工程目录下的_dcu缓存,重新编译;大多数情况下是旧 dcu 残留盖住了新源码路径。这个检查动作每次装完控件后只花 30 秒,但能确认编译器是否真的在用 Source 目录,调试环节的稳定就取决于这一步。
本文还有配套的精品资源,点击获取