2026/9/16 15:51:00

Delphi 13.1 D2Bridge实战:VCL组件到Web页面的快速迁移指南

Delphi 13.1 D2Bridge实战:VCL组件到Web页面的快速迁移指南 简介面向 Delphi 13.1 开发者的 D2Bridge Framework 控件完整工具包用于解决跨层数据传递、组件桥接与复杂界面快速构建等问题适合中高级 Delphi 程序员与需要集成 Web/REST 场景的桌面应用开发者。压缩包共 2000 个文件、约 369.18MB核心包含 4 个 dll、4 个 bpl 运行时库以及 522 个 pas、133 个 dfm 等源代码与界面文件同时附带 209 个 js、89 个 css 和 scss/ts 等前端资源便于理解桥接页面与样式Wizard 向导、Demos 示例、DOC 文档及 HTML 说明则覆盖安装、配置到二次开发全流程。已有 71 人学习关注。除框架本体外还提供 ReadMe.md、最新版本下载指引、Lazarus/LRS 编译脚本和备份数据库可辅助环境搭建与版本升级。全包按 Unit Base、DLL、Wizard、Demo 等目录组织适合希望高效掌握 D2Bridge 桥接原理并快速落地的开发者。1. D2Bridge 在 Delphi 13.1 为什么值得重新看一遍升级到 Delphi 13.1 之后组件面板里 D2Bridge 那一页很容易被忽略里面的一堆按钮、编辑框、表格名字和 VCL 老组件区别不大。实际上这些控件运行后已经完全不碰 Windows 窗口句柄浏览器收到的是语义化的 HTML 和少量 JavaScript。D2Bridge Framework 真正想解决的是一批 Delphi 工程师做网页版时最痛苦的迁移成本问题不用先学 Vue 或 React也能把 TForm 设计器当页面设计器用把按钮的 OnClick 当服务端事件继续写。它的使用场景通常集中在企业内部系统、设备管理后台、以及需要快速把已有 Delphi 业务逻辑输出成响应式界面的项目。相比 uniGUI for Delphi 那套组件D2Bridge 生成的页面默认不带重型前端框架局域网内首开速度更容易做快。下面从安装开始把建项目、写页面、部署和最后瘦身的细节完整串一遍。2. 在 Delphi 13.1 里搭建 D2Bridge 项目并跑通首页2.1 确认 D2Bridge 控件是否已安装打开 Delphi 13.1 后先做一次环境检查不要在编译到一半才发现包没有加载。D2Bridge 在较新的版本里已经是官方随包组件但老升级工程仍然有漏装的可能。在主菜单Component Install Packages里搜 “D2Bridge”能看到dclD2Bridge相关的 bpl 就说明设计期包已注册。在 Tool Palette 搜索框输入 D2Bridge如果出现单独的 “D2Bridge” 页签里面包含一批以 T 开头、和 VCL 同名的组件说明运行期包也正常。如果你拿到的是标题那种 .zip 安装包常见做法是把 zip 解开将D2Bridge.Core目录加入Tools Options Library的 Library path再把dclD2Bridge*.bpl加入 Install Packages。检查项 操作路径 确认结果 设计期包 Component Install Packages 列表里出现 D2Bridge 设计时 bpl 运行期包 Tools Options Library 系统能找到 D2Bridge.Core 源码或 dcu 目录 组件模板 Tool Palette 搜索 D2Bridge 出现 D2Bridge 独立页签如果只缺少设计期 bpl可以单独点 Install 添加但编译到一半很可能报File not found: D2Bridge.Forms.dcu。这种问题基本和控件本身无关纯粹是 Library path 没指向源码目录把 D2Bridge 的源码根目录加进去再重编译一次就好。2.2 用向导生成最小工程在File New Other中找到 D2Bridge 分类选择 Web Application 向导。模板会问你要 Standalone 还是 ISAPI 模式第一趟建议选 Standalone它生成的是一个带内置 HTTP 服务的可执行文件双击就能看效果后续调试最方便。向导生成的结构大致如下D2BridgeDemo ├─ Server.pas // 服务端入口负责会话与路由 ├─ MainPage.pas // 首页对应的 TForm 单元 ├─ MainPage.dfm // 页面布局文件和 VCL 表单一致 ├─ D2BridgeDemo.dpr └─ wwwroot/ // 静态资源目录CSS/JS/图片放这里MainPage 的骨架代码会类似这样unit MainPage; interface uses VCL.Forms, VCL.StdCtrls; type TMainPage class(TForm) Button1: TButton; Edit1: TEdit; procedure Button1Click(Sender: TObject); end; implementation {$R *.dfm} procedure TMainPage.Button1Click(Sender: TObject); begin Edit1.Text : Hello, D2Bridge; end; end.这段代码看起来和普通 VCL 完全一样但它编译进 D2Bridge 应用后TForm 就不再对应窗口而是对应一个 URL 页面。Edit1会成为浏览器里的 input 输入框Button1会成为带点击事件的 button。D2Bridge 在服务端为这个表单维护一棵对象树页面请求到达时它把按钮、标签、面板逐个翻译成 HTML 结构再把事件通过 AJAX 映射回 Delphi 的方法名。需要第一时间关注的参数只有端口。项目属性或 Server.pas 里会有Port设置演示环境常用 8080正式环境建议改成 80 或 443 后面挂 Nginx 入口。wwwroot目录的作用和普通 Web 项目相同放进去的静态文件会按原路径被客户端访问适合放登录背景图、公司 Logo 这类资源。D2Bridge 模板生成后直接编译通常不会报错。唯一需要留意的是 Windows 防火墙第一次运行如果弹出端口监听提示要选择允许否则局域网其他机器访问不到。开发机上只验证 localhost 没有意义至少要确认本机 IP 也能打开不然之后配置 IIS 或 Nginx 时很容易把问题混淆成转发配置错误。2.3 编译运行并验证响应按 F9 运行项目后控制台会输出当前监听地址。浏览器打开对应端口就能看到表单页面。这里建议用命令行验证一次避免被浏览器缓存误导curl -I http://localhost:8080/ HTTP/1.1 200 OK Content-Type: text/html; charsetutf-8返回 200 且 Content-Type 是 text/html说明 D2Bridge 已经接管请求。如果返回 404先检查 URL 是否带上了注册的路由前缀。向导默认把 MainPage 注册在根路径/如果注册在/main就要访问http://localhost:8080/main。再打开浏览器开发者工具的 Network 面板看响应体里Edit1是否已经变成input idEdit1 typetext、Button1是否变成button idButton1 typebutton。记住这套对应关系后面遇到“样式不生效”问题多半是要按这个 HTML 结构去写 CSS。3. 理解 D2Bridge 控件VCL 到 Web 的映射关系3.1 TForm 在这里不是一个窗口用 Delphi 写 Web 的第一心理障碍就是D2Bridge 里的 TForm 只是一个页面容器。它在服务端被创建、执行事件、销毁客户端拿到的是一份渲染完成的结构化 HTML。这个设计带来一个明显好处D2Bridge 能复用 VCL 的父子关系。你在表单上放一个 TPanel再在 Panel 里放两个 Edit它会自动翻译成嵌套的div不需要去写层级代码。子控件间距沿用Margins和Align属性输出时映射到 CSS 的 margin 和 flex 相关样式这也是很多初学者觉得比手工生成 HTML 省力的地方。坏处也很直接你在服务端写的实例字段不能像桌面程序那样跨方法保存长期状态。每次请求结束页面上的控件对象可能被释放或复用重要数据要放到 D2Bridge 的 Session 中而不是存成一个全局变量。3.2 常用控件对照表与属性设置把拖到窗体上的组件做一次映射心里就有底了VCL 控件浏览器端元素常用设置TButtonbuttonCaption 显示文字Enabled 控制禁用TEditinputMaxLength 限制长度PasswordChar 变密码框TLabelspanCaption 直接作为文本TPaneldivColor 映射背景色Align 控制布局TMemotextareaLines 作为内容ScrollBars 被忽略TCheckBoxinput typecheckboxChecked 控制选中状态TComboBoxselectItems 作为下拉项TDBGridtableDataSource 绑定数据集Panel 是使用频率最高的控件。D2Bridge 会把它渲染成 div同时保留 VCL 的Margins、Padding属性子控件间距可以直接在设计器里拉开不需要额外写 CSS。遇到圆角这类 VCL 原生没有的属性可以在Style事件里补border-radius常见做法是给 Panel 的Style.Border设具体值运行期再根据状态动态调整。3.3 按钮事件回传的完整链路按钮是页面里最重要的交互入口。点击一个 TButton实际发生的过程是浏览器把按钮的 id、事件名和当前页面表单数据打包成 JSON发到服务端D2Bridge 找到对应的 TForm 实例执行Button1Click方法修改了控件属性后D2Bridge 返回被改动控件的新属性片段浏览器用这段片段更新 DOM整个页面不刷新。procedure TMainPage.Button1Click(Sender: TObject); begin if Edit1.Text then Edit1.Font.Color : clRed else Edit1.Font.Color : clBlack; end;逻辑上这和在桌面事件里写代码没有区别但执行位置完全不同。事件是在服务器进程里跑的因此要注意两点。第一不要在事件里做超过两三秒的操作浏览器会显示等待状态用户刷新页面离开后服务端操作不会自动取消。第二事件回调默认会禁用按钮防止连续点击对于一键审批这类动作这个默认行为正好合适。提示如果某个按钮故意允许快速多次点击比如“暂停”“继续”这种状态切换可以在事件开头重新启用自身但服务端要做好防重复进入的判断。3.4 用 Visible 和 Style 控制显隐控件的显示隐藏在 D2Bridge 中会变成 DOM 的display或visibility。Visible : False对应display: none元素不占空间如果想要“占位但隐藏”需要自己改样式。Panel1.Visible : False; Panel1.Style.CSS : opacity: 0; pointer-events: none;;第一行在输出 HTML 时直接不渲染 Panel 的内部区块第二行则是把面板画在页面上但挡住点击。两者的回传行为也不一样Visible : False后面板里的按钮不会再被 D2Bridge 序列化前端不会触发对应事件而只设置 CSS 时事件仍然可能被触发服务端需要自己判断状态。实际项目中我一般把“编辑态/查看态”切换写成这样先整体把编辑按钮的Visible置为 False再把提交按钮置为 True。D2Bridge 会把两个控件的状态合并到一次响应里返回避免出现中间态闪屏。4. 用 D2Bridge 实现列表、详情与登录4.1 让 TDBGrid 显示真实业务数据D2Bridge 的 TDBGrid 用法与桌面版几乎相同只是不需要再关心绘制细节。经典写法是放一个 TFDConnection、TFDQuery、TDataSource 和 TDBGrid运行期打开数据集即可。procedure TMainPage.LoadData; begin FDQuery1.Close; FDQuery1.SQL.Text : SELECT id, name, phone, created_at FROM customers ORDER BY id DESC; FDQuery1.Open; DBGrid1.DataSource : DataSource1; end;这段代码会让 D2Bridge 从 TFDQuery 的字段元数据中读取列名、数据类型然后生成一张可排序的 HTML 表格。created_at这类日期字段在浏览器端默认按 ISO 8601 输出如果想显示成yyyy-mm-dd hh:nn要在查询里先转好字符串。如果连接的是 SQLite记得把 FDConnection 的 DriverID 设为 SQLite并确认数据库文件编码是 UTF-8否则中文很容易显示成乱码。值得留意的三个参数DBGrid1.Columns[i].Width对应表格列宽按像素赋值ReadOnly : True会让表格拒绝编辑Options里的dgRowSelect决定整行选中还是单元格选中。dgMultiSelect不要轻易开它的客户端选中状态和列表滚动后的服务端状态可能不一致。另外注意FDQuery1.Close不能省否则重复执行这个方法时会遇到常见的cannot perform this operation on an open dataset。4.2 列表到详情的参数传递从列表页跳到详情页是 Web 应用的基本操作。D2Bridge 不推荐像桌面程序那样直接 CreateForm更常见的做法是跳转到一个带查询参数的新 URL。procedure TMainPage.ButtonDetailClick(Sender: TObject); begin D2Bridge.Redirect(/detail?id FDQuery1.FieldByName(id).AsString); end;在详情页的 Page 单元里读取参数的方式与其他 Web 框架保持一致var CustomerID: Integer; begin CustomerID : StrToIntDef(D2Bridge.Params[id], 0); FDQuery2.SQL.Text : SELECT * FROM customers WHERE id :id; FDQuery2.ParamByName(id).AsInteger : CustomerID; FDQuery2.Open; end;注意D2Bridge.Params在同一次请求内可以重复读取不会消耗流。参数不存在时返回的是空字符串所以要用StrToIntDef给默认值防止详情页因为一个缺失参数直接 500。4.3 Session 与登录状态Web 程序里每个页面请求都是独立的。D2Bridge 通过 Cookie 中的会话 ID 把多次请求关联到同一份 Session 上下文开发人员只需要在合适的位置写入数据。常见做法是在 Server.pas 里处理会话创建事件登录成功后把用户主键写入 Session// 登录按钮事件中验证通过后写入当前用户 Session.UserData.AsInteger : FDQuery1.FieldByName(id).AsInteger; Session.UserName : FDQuery1.FieldByName(name).AsString;随后在需要权限的页面开头加一个守卫if Session.UserData.AsInteger 0 then begin D2Bridge.Redirect(/login); Exit; end;这样即使是浏览器直接输入详情页地址只要 Cookie 里没有合法会话也会被弹回登录页。Session 的过期时间放在哪配置取决于你选择的部署模式独立 EXE 一般是服务器参数ISAPI 模式则可能由 IIS 的应用程序池空闲超时接管。这一点要在部署阶段统一调否则容易被用户投诉“页面隔一晚就掉线”。4.4 批量更新数据时的事务边界列表页常见的批量操作如批量冻结、批量改价在 D2Bridge 里也是循环处理数据集再统一提交。FDConnection1.StartTransaction; try for i : 0 to DBGrid1.SelectedRows.Count - 1 do begin Bookmark : DBGrid1.SelectedRows[i]; FDQuery1.GotoBookmark(Bookmark); FDQuery1.Edit; FDQuery1.FieldByName(status).AsInteger : 2; FDQuery1.Post; end; FDConnection1.Commit; except FDConnection1.Rollback; raise; end;事务的意义在于“全部成功或全部失败”。如果不用事务循环中间有一条记录违反唯一索引前面的修改都会留在库里页面上又只显示一个错误提示用户很难核对到底哪些成功。业务上还要注意DBGrid1.SelectedRows在翻页后可能残留上次选中的行批量操作前建议先统计选中数量再进入事务。5. 部署 D2Bridge 应用EXE、IIS 与流量入口5.1 三种运行模式的取舍D2Bridge 应用可以按三种常见方式发布选型依据主要是团队现有运维习惯和并发规模。模式优点缺点适用场景独立 EXE开发调试最方便日志直接输出控制台需要自己管理进程守护内网工具、中小并发ISAPI DLL由 IIS 托管崩溃自动回收部署路径和权限配置繁琐已有 Windows 服务器和 IIS 环境Nginx 入口 容器可镜像复制滚动升级方便Windows 镜像体积偏大需要横向扩缩容的平台第一次接触 D2Bridge 的话建议先用独立 EXE 把业务流程跑通等需要长期挂机再切到另外两种模式。独立 EXE 模式下进程守护可以用 Windows 的计划任务或 NSSM至少在崩溃后能自动拉起来。5.2 在 IIS 上挂载 ISAPI 版本如果决定用 IIS编译前要把项目类型切换成 ISAPI DLL然后按下面步骤操作。把生成的 DLL 复制到站点目录例如C:\inetpub\wwwroot\d2bridge\app.dll在 IIS 里为这个目录新建应用程序应用程序池选择“经典”托管管道模式确认应用程序池的“启用 32 位应用程序”设置与编译目标架构一致给应用程序池账号授予对 DLL 所在目录的读取权限以及一个临时目录的写入权限。如果 IIS 版本较新还需要在处理程序映射里把 DLL 请求交给通配符脚本处理器。实际上 D2Bridge 生成的 ISAPI DLL 是原生代码直接把它配置为脚本映射即可。启动失败时先看 Windows 事件查看器的应用程序日志错误代码0x80070005通常是权限问题0x80004005则要查 DLL 依赖的第三方库是否放进了系统 PATH。5.3 必须调的三个并发与超时参数部署到真实服务器后问题往往不在代码而在几个容易忽略的参数。参数默认值参考建议值影响Session Timeout20 分钟30~60 分钟用户闲置多久后会话失效Max Connections无限制按瓶颈估算超出连接数的请求排队或拒绝应用程序池空闲超时20 分钟设为 0IIS 回收后首个请求会变慢ISAPI 模式下IIS 的空闲超时如果设置太短非高峰时段应用程序池会被回收用户再访问时第一次点击会经历 DLL 加载和初始化表现就是“页面能开但第一次业务查询特别慢”。独立 EXE 模式则要关注服务端线程池大小连接数上去后CPU 可能并不是瓶颈线程上下文切换会先出现。5.4 用 Nginx 做流量入口如果你的交付环境允许把 Linux 作为入口我通常会在最前面放一个 Nginx把 D2Bridge 的 EXE 或 IIS 藏在后面。server { listen 80; server_name demo.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 60s; } }这样做的好处是D2Bridge 自身不用处理 HTTPS 证书、gzip 压缩和客户端真实 IP。浏览器到 Nginx 走 HTTPSNginx 到 Delphi 走内网 HTTP证书更新只在 Nginx 层操作。proxy_read_timeout设到 60 秒是给 D2Bridge 服务端事件留出正常处理时间如果业务里有超过 60 秒的报表查询再单独放宽这个值。6. 让 D2Bridge 页面更快静态缓存、gzip 与控件裁剪页面能跑通之后反馈最多的就是两个词慢、卡。D2Bridge 由于在服务端渲染 HTML首屏天然比分前后端分离架构稍重一点但绝大多数速度问题都能用下面的方法解决。先给静态资源加上长缓存。D2Bridge 生成的页面里可能包含一些框架脚本和样式文件这些文件在版本不变时不需要反复下载。在服务端响应事件里判断路径前缀让浏览器对这些资源做一年缓存procedure TServer.D2BridgeResponse(var Response: TD2BridgeResponseData); begin if Response.Path.StartsWith(/assets/) then Response.Headers[Cache-Control] : public, max-age31536000, immutable else Response.Headers[Cache-Control] : no-cache; end;no-cache并不是禁用缓存而是让浏览器每次先拿着本地副本向服务端确认适合 HTML 页面静态资源用immutable则完全跳过重新验证适合带版本号的 CSS、JS 文件。如果你的资源文件名不带 hash就不要开immutable否则升级后浏览器会继续用旧文件。再交给 Nginx 做 gzip。D2Bridge 返回的 HTML 和 JSON 文本压缩率通常在 70% 以上开启 gzip 后传输体积能明显下降location / { proxy_pass http://127.0.0.1:8080; gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1024; }gzip_min_length 1024的意思是小于 1KB 的文件不压缩避免为几个字节多花压缩时间。正式环境如果前端已经做了长缓存通常不需要再上 Brotligzip 已能把文件中重复的 HTML 结构和动态 JSON 压到理想状态。还有一类慢不在网络传输而是控件本身太重。D2Bridge 允许声明页面使用哪些组件集没用到的高级表格就不要在页面初始化时挂载。常见做法是在发布模式下关闭调试会话信息减少注入到 HTML 里的元数据。你可以做一个简单实验用浏览器开发者工具对比页面在 Debug 和 Release 两种配置下的传输字节数Release 往往能少掉 20% 到 30%。最后一个容易被忽略的做法是清理隐藏控件的渲染。若某个 Panel 在 OnCreate 时被设为Visible : FalseD2Bridge 还是会为它生成内部结构只是最终用display:none包起来。对完全不需要输出到客户端的隐藏块可以在渲染前从页面树中移除而不是只改 Visible。这个动作配合 gzip首屏时间通常还能再降 15%。本文还有配套的精品资源点击获取