ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Delphi 13.1 + TMSWEBCore 实战指南:从头到脚解锁纯 Delphi 前端开发

Delphi 13.1 + TMSWEBCore 实战指南:从头到脚解锁纯 Delphi 前端开发 简介这是一份Delphi 13.1环境下TMS WEB Core v1.9.8.3开发者指南的中文汉化版面向使用Delphi进行Web应用开发的程序员。文档从最基础的TMS WEB Core设置讲起逐步深入到渐进式Web应用PWA、Electron桌面应用、Miletus应用创建以及调试方法并系统讲解了Pascal to JavaScript编译器、RTL、预处理器和命令行工具帮助读者理解Web开发的双向模式、HTML模板同步与实时预览。针对常用UI控件例如TWebLabel、TWebButton、TWebEdit、TWebComboBox、TWebDateTimePicker、TWebMainMenu等均给出属性、方法和事件说明并附有模板标记示例。文档还涵盖数据库与REST API解决方案、运行时动态创建表单以及在控件中承载窗体等进阶主题能有效指导开发者将Delphi知识迁移到Web场景。资源为单份PDF共1个文件压缩包大小13.48MB当前已有55人学习下载。 好久没聊 Delphi 了最近在整理工具链的时候翻到一份有意思的资料Delphi 13.1 搭配 TMSWEBCore 的 v1.9.8.3 开发指南而且是我自己做的汉化版。这玩意儿说冷门也冷门但真搞 Web 前端的 Delphi 开发者应该都知道 TMSWEBCore 这库的分量——它能让纯 Delphi 代码直接编译成 JavaScript 前端应用后端再对接 DataSnap 或 REST 服务一套语言打通全栈。网上关于这库的英文文档不少但系统性的中文指南几乎没有所以当时我花了不少精力整理和翻译这份指南过程中踩了不少坑也摸清了不少门道。这篇就把整个学习路径、开发要点和我个人的实操经验一次说透给正准备入手 Delphi 13.1 TMSWEBCore 的朋友省点时间。1. 内容整体设计与思路拆解1.1 为什么选择 TMSWEBCore 而不是其他 Web 方案Delphi 做 Web 开发老一代人第一反应是 IntraWeb 或者 uniGUI但说实话这两者的页面交互和现代前端框架差距已经拉得很大。TMSWEBCore 的思路完全不同它在编译阶段直接把你写的 Delphi 代码翻译成 JavaScript输出成纯静态页面加 REST 后端的模式。也就是说你的业务逻辑还是 Object Pascal跑在浏览器里的却是标准的前端应用不用写一句 HTML、CSS 或 JavaScript。我选择 TMSWEBCore有几个很实际的原因。一是语言统一团队里如果都是 Delphi 工程师不用再养一个前端组二是组件模型很贴近 VCL 的习惯TWebLabel、TWebEdit、TWebButton这些控件的属性事件和写 Win32 窗体程序几乎一个节奏三是调试链路短编译报错直接指向 Pascal 源码比调 JavaScript 栈省太多时间。当然它也不是银弹交互极复杂的页面、需要重度前端生态的场景还是得老老实实上 React 或 Vue但企业管理系统、数据录入类应用、内部工具这类典型场景TMSWEBCore 的开发效率确实非常能打。1.2 汉化版开发指南的整理逻辑这份 v1.9.8.3 的 DevGuide原版内容其实已经很系统覆盖了从环境配置、组件分层、数据绑定到部署落地的全流程。但我在实际阅读时发现很多细节对刚上手的人并不友好比如组件继承关系只给个图不解释为什么这么分生命周期回调在各个章节里散落得到处都是。汉化整理时我的处理思路不只是翻译文字而是把这几个维度重新梳理了一遍把每个组件的职责边界讲清楚尤其是TWebApplication、TWebSession、TWebComponent这种基础设施类和业务控件的分工。将事件触发的顺序整理成明确的时间线比如页面初始化、数据加载、渲染完成分别该往哪个事件里写逻辑。把原版分布在各个角落的数据库处理、REST 调用、前端交互的示例代码集中归类并且补充我在真实项目里验证过的写法。对所有配置项、属性、方法说明增加了适用场景提示哪些属性只在特定编译模式下生效哪些回调在移动端会被跳过都做了标注。所以这份文档你拿到手不是说看一遍就完事而是当工具书用的写到哪块翻哪块。2. 核心细节解析与实操要点2.1 TMSWEBCore 项目的基本结构一个标准的 TMSWEBCore 项目编译后会生成一组 Web 资源包括 HTML 骨架、JS 文件、CSS 文件和资源文件。但它和普通前端工程最大的区别在于你写的 Delphi 单元会被编译成 JavaScript 类组件的实例化、事件绑定、属性赋值全部由运行时框架生成不需要手写任何浏览器端的胶水代码。实际项目中一个页面对应一个TWebForm派生类这个类里放控件、写事件、管数据。如果你开发过 VCL适应这个模型几乎没有学习成本唯一要注意的是 UI 线程模型。浏览器环境里所有 DOM 操作都在主线程TMSWEBCore 帮你封装了这部分但如果你调用了异步 REST 回调回调回来后想改控件的Caption或Text千万不能直接写得用框架提供的同步上下文切回 UI 线程再操作。这个坑我第一次做登录页的时候就踩过页面直接白屏控制台还看不到明确报错。2.2 组件体系与生命周期TMSWEBCore 的组件体系很庞大但核心可以分成四类基本控件标签、编辑框、按钮等、容器类面板、表单布局等、数据类表格、下拉框数据源等、服务类REST 客户端、定时器等。理解这四类的关键是明白它们最终都会映射到浏览器里的 DOM 元素或 JavaScript 对象所以很多属性的命名和取值跟 VCL 完全不一样比如ElementID对应 DOM 的 idElementClassName对应 CSS class而不是 VCL 里的Name和Hint。生命周期方面TWebForm的创建过程有几个关键节点构造函数、Load事件、AsyncLoad事件、Render完成、Show事件。构造函数阶段控件尚未初始化不能操作控件Load事件里应该做数据加载和界面初始化AsyncLoad用于处理异步数据源。最需要注意的是不要在构造函数里尝试读取控件的尺寸或位置浏览器还没布局拿到的全是默认值。这个跟 VCL 里窗口句柄还没创建是一个道理。2.3 后端衔接REST 与 JSONTMSWEBCore 是纯前端方案它本身不提供后端服务器能力所以必须搭配后端的 REST API。我这边后端用的还是 Delphi 自带的 DataSnap前端通过TWebHttpRequest或TWebHttpClient发请求数据格式统一 JSON。这里有几个经验值得记一下每个请求最好封装成独立的函数或类不要散落在各个 Form 里否则后续接口变更时改到你怀疑人生。JSON 的序列化和反序列化建议用 TMS 的TWebJSONObject包装一下配合TWebJSONMapper可以直接映射到类属性省去手写序列化代码。错误处理一定要统一框架HTTP 状态码非 2xx 时不能只弹 Toast要把错误码、错误信息、调用栈整理后显示同时记录到日志。这个对排查线上问题特别重要。3. 实操过程与核心环节实现3.1 环境准备与工程创建开始之前先把环境备好。我用的是 Delphi 13.1TMSWEBCore 版本 v1.9.8.3安装流程不复杂从 TMS 官网下载安装包双击后一路 Next选择当前 IDE 版本完成后工具栏会多出 TMS Web 相关的组件页签。装完先别急着写代码建议先跑一遍自带的示例工程确认编译器和浏览器端运行时都正常。创建一个新工程时选TMS Web Application (Core)模板。工程创建后会生成一个默认的Form1运行起来就是白页加一行 Hello World。此时在工程目录里你已经能看到编译输出的结构后面所有的页面扩展和控件拖放都是在这个基础上叠加。3.2 从零做一个待办管理页面光说不练假把式拿一个实际案例过一遍全流程。我要做一个简单的待办事项管理页面功能包括加载事项列表、新增一条事项、标记完成、删除。这个功能麻雀虽小但覆盖了列表展示、数据交互、表单提交三大典型场景。第一步设计数据类。定义一个TTodoItem类包含 Id、Title、Done 三个字段。在 Delphi 里我这么写type TTodoItem class private FId: Integer; FTitle: string; FDone: Boolean; published property Id: Integer read FId write FId; property Title: string read FTitle write FTitle; property Done: Boolean read FDone write FDone; end;注意字段必须放在published区段TWebJSONMapper在做序列化映射时才能自动识别这是 TMSWEBCore 的一个硬性要求不写就映射不上。第二步搭界面。拖一个TWebEdit用于输入标题一个TWebButton触发票据新增一个TWebListBox展示列表。再准备一个TWebHttpRequest作为统一的 REST 调用组件。第三步写数据加载逻辑。页面Load事件里调用LoadTodoList方法procedure TForm1.LoadTodoList; var LResponse: string; LTodos: TJSONArray; I: Integer; LItem: TTodoItem; begin try LResponse : FRequest.Get(/api/todos); LTodos : TJSONObject.ParseJSONValue(LResponse) as TJSONArray; try for I : 0 to LTodos.Count - 1 do begin LItem : TTodoItem.Create; try TWebJSONMapper.FromJSON(LItem, LTodos.Items[I].ToJSON); WebListBox1.Items.AddObject(LItem.Title, LItem); except LItem.Free; end; end; finally LTodos.Free; end; except on E: Exception do ShowMessage(加载待办列表失败: E.Message); end; end;第四步新增和删除事件。新增按钮的点击事件里先校验输入非空再封装 JSON 提交成功后刷新列表procedure TForm1.WebButton1Click(Sender: TObject); var LBody: TJSONObject; LResponse: string; begin if WebEdit1.Text.Trim.IsEmpty then begin ShowMessage(标题不能为空); Exit; end; LBody : TJSONObject.Create; try LBody.AddPair(title, WebEdit1.Text.Trim); LBody.AddPair(done, TJSONBool.Create(False)); LResponse : FRequest.Post(/api/todos, LBody.ToJSON); if LResponse.Trim.IsEmpty then ShowMessage(新增成功) else ShowMessage(新增失败: LResponse); WebEdit1.Text : ; WebListBox1.Clear; LoadTodoList; finally LBody.Free; end; end;删除操作更简单取当前WebListBox1.ItemIndex从Items.Objects[ItemIndex]拿到TTodoItem取出 Id 后调 DELETE 接口。有一点必须注意——Items.Objects里存的对象在清除列表时不会自动释放必须在WebListBox1.Clear之前手动循环Free否则就是内存泄漏。我一开始没注意这个问题跑了一下午浏览器标签页直接把内存吃到 1GB 多非常离谱。3.3 数据绑定的进阶用法如果业务场景只是简单列表上面的手写方式完全够用。但一旦字段多了、结构复杂了建议直接用TWebDataSet系列组件做数据绑定。这组控件是 TMSWEBCore 的高阶封装类似 VCL 里的TDataSourceTDBGrid属性赋值后控件自动订阅数据集的增删改事件界面和数据状态能保持同步。不过绑定的方便是拿灵活度换的。比如表格单元格要用不同颜色区分状态这类定制化的渲染需求TWebDataSet默认的列渲染满足不了还是得返回层面用OnColumnRender之类的事件自己画。所以我的建议是能用绑定的场景优先绑定凡是涉及样式和交互定制的列单独写渲染逻辑不要在数据层塞 UI 的状态字段。4. 常见问题与排查技巧实录这部分是干货中的干货都是我在汉化和实际开发中反复踩过的坑挑几个最有代表性的列出来。4.1 编译通过但页面白屏这是 TMSWEBCore 新手最常遇到的问题没有之一。现象是编译顺利浏览器打开只有空白控制台无报错或只有一两条警告。排查思路先在浏览器的 Network 面板看看 JS 文件是否加载完整如果某个 JS 文件报 404 或者加载失败多半是工程路径或输出路径设置不对导致运行时找不到资源。如果 JS 加载正常可能是构造函数或Load事件里抛了异常。TMSWEBCore 在浏览器端的异常会默认被吞掉只在控制台打印一条笼统的错误。处理方法是在构造函数里加一个全局异常挂钩把异常消息弹出来TWebApplication(Application).OnException : procedure(Sender: TObject; E: Exception) begin ShowMessage(全局异常: E.Message); end;第三种情况是异步加载数据时横跨了页面生命周期比如Load里发起请求后页面已关闭才返回导致组件状态非法。这种最简单加一个IsLoading标志位在回调里判断组件是否还存活。4.2 字符串作字典 key 时的编码问题这是我汉化文档时自己写测试程序遇到的。Delphi 的字符串是 Unicode浏览器端 JavaScript 的字符串也是 Unicode但 TMSWEBCore 在中间做 RTTI 序列化时如果不明确指定编码部分特殊字符尤其是中文全角标点和 emoji会被错误转成 HTML 实体。用TDictionarystring,...做 key 时如果 key 含这类字符前后端传参后可能对不上。处理方法很粗暴但也有效统一在序列化和反序列化时显式设置编码转换或者干脆用 Base64 编码做 key。如果你在项目里发现有些数据存得进去但取不出来八成就是这个原因。4.3 运行一个 DOS 命令并等待其结束这个场景严格来说不属于 TMSWEBCore 前端但在 Delphi 桌面端和混合架构里经常用到。命令如下function RunCommandAndWait(const ACmdLine: string): Integer; var LStartupInfo: TStartupInfo; LProcessInfo: TProcessInformation; begin ZeroMemory(LStartupInfo, SizeOf(LStartupInfo)); LStartupInfo.cb : SizeOf(LStartupInfo); if not CreateProcess(nil, PChar(ACmdLine), nil, nil, False, 0, nil, nil, LStartupInfo, LProcessInfo) then RaiseLastOSError; try WaitForSingleObject(LProcessInfo.hProcess, INFINITE); GetExitCodeProcess(LProcessInfo.hProcess, DWORD(Result)); finally CloseHandle(LProcessInfo.hProcess); CloseHandle(LProcessInfo.hThread); end; end;用CreateProcess而不是ShellExecute是因为你需要拿到进程句柄去等待结束ShellExecute是异步的拿到句柄返回后进程未必已经启动完成。记得用完句柄必须 Close否则进程句柄会泄漏反复调用后句柄数会涨到系统上限。4.4 无效的授权说明Delphi 13.1 装完第三方组件之后经常会遇到“invalid license”或者“未找到有效授权”的提示。这个在 TMSWEBCore 上很典型因为它的安装包会检测 IDE 版本和已安装的 Delphi 版本IDE 升级后注册表信息可能没同步。排查思路三步走确认安装包版本和 Delphi 版本匹配v1.9.8.3 对应特定的 IDE 版本过新或过旧都可能识别失败。卸载组件时注意清理注册表里的 TMS 条目直接覆盖安装经常会导致授权信息残留产生冲突。尽量用管理员权限运行安装包避免安装目录写不进 Program Files 导致授权文件路径异常。如果以上都排查完还是提示无效授权大概率是安装包下载不完整重新下载一份最省心。4.5 ITask 和匿名线程的区别Delphi 10.3 之后推出了 ITask 并行编程接口很多人跟匿名的TThread.CreateAnonymousThread搞混。这俩本质都是多线程但应用场景差别很大TThread.CreateAnonymousThread(AProc).Start是经典的匿名线程适合简单后台任务但异常处理、取消机制、任务完成回调都需要自己管理。TTask.Run基于任务调度器支持WaitAll、WaitAny、Cancel并且可以配合Async/Await模式使用写链式异步逻辑更顺手。重要区别TThread.CreateAnonymousThread创建的线程是独立线程TTask在默认线程池中运行线程池线程是复用的大量创建 Task 的系统开销更低。TMSWEBCore 项目里如果你在派生的 Service 层使用多线程建议用TTask而不是裸线程因为任务调度器的生命周期和线程池管理在服务端场景下更稳定不会频繁创建销毁线程造成性能抖动。4.6 OCR 与图像识别在 Delphi 里的集成热词里有人问 Delphi 怎么做 OCR这个和 TMSWEBCore 前台无关但桌面端开发很常见。建议用第三方库比如 Tesseract 的 Delphi 封装或者 Windows 自带的 OCR APIWindows.Media.Ocr。后者在 Win10/Win11 上系统级支持调 WinRT API 即可不需要额外安装识别引擎识别速度和精度都能接受缺陷是只能离线跑、中英文混合识别效果一般。如果需要更精确的身份证、票据类识别还是得接云服务或本地推理框架。5. 这份汉化指南的使用建议与个人心得文档翻译完了用它的人也越来越多我收到最多的提问其实是“这文档该从哪看起”。我的建议是先读组件综述章节大概了解全局然后马上转去示例工程边跑边看代码遇到哪个组件的某个属性不懂再回查对应章节千万别从头往后硬啃不然很容易被大段的事件列表和属性说明劝退。汉化过程中我有一个比较深刻的体会TMSWEBCore 虽然把前端复杂度封装得很好但它毕竟是在浏览器环境跑你用 Delphi 写的时候下意识会沿用 VCL 的窗口程序思维觉得“窗体在就在、控件在就在”可网页的异步性、生命周期、资源释放和桌面端差异非常大。我建议每个从桌面端转过来的开发者上手前提醒自己三点界面刷新不需要手动调Invalidate但异步回调后必须切回 UI 线程才能操作控件。容器里的控件销毁时机不归你管尽量用TWebComponent的高级封装来管理子组件不要手动 Free否则容易触发浏览器端野指针。编译产出物是静态资源部署时可以丢到任意 Web 服务器或 CDN不需要 Delphi 运行时环境所以发布路径和资源引用方式要提前规划好。最后说一个我自己一直沿用的扩展思路把 TMSWEBCore 生成的静态页面当作前端展示层后端用 Delphi 的 REST 服务或 DataSnap 提供接口中间加一层 API 网关做鉴权和限流就能很轻松地做成一个完整的业务系统。如果以后要做移动端适配TMSWEBCore 的响应式组件和 CSS 变量体系也预留了不少空间PC 端的表单界面在手机上虽然不能完全复用但至少不需要推倒重来。这些都是后话先把桌面到 Web 这条线走通你会发现 Delphi 依然是写业务系统相当顺手的工具。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进