十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

D2Bridge Framework:让Delphi VCL控件快速变为Web应用

D2Bridge Framework:让Delphi VCL控件快速变为Web应用 简介一份面向 Delphi 开发者的 D2Bridge Framework 控件包基于 Delphi 13.1 环境用于解决多层应用之间数据传递、组件联动与耦合度高的问题。这套框架通过桥接模式将不同数据源和应用组件连接起来有助于提升大型项目的灵活性与可维护性。压缩包共包含 2000 个文件、容量约 369MB其中含有 522 个 Pascal 单元源码、133 个窗体定义文件、46 个工程文件以及 JavaScript、CSS、SCSS 等前端资源同时带有 DLL 动态库、BPL 运行期包、可执行示例和配套文档。目前已有 71 人学习使用。包内 Demos 文件夹展示了典型应用场景的完整示例Wizard 向导可辅助快速创建项目DOC 与 HTML 文档覆盖 API 参考和架构说明Unit Base 目录存放框架核心基础单元D2Bridge Framework 主目录则集中了库文件、资源和配置文件。对于需要深入理解框架源码、希望在真实项目中应用桥接模式的中高级 Delphi 程序员这份资源提供了从安装、配置到二次开发的完整素材。1. D2Bridge Framework是什么给 Delphi 13.1 控件的 Web 直译方案业务逻辑全写在 VCL 的 OnClick 里界面用的是 TDBGrid、TPanel 这些老控件突然要改成浏览器访问前端排不出人手这是很多 Delphi 团队卡住的地方。D2Bridge Framework 的思路是把 VCL 控件按“控件名”直译成浏览器元素TLabel 变成 divTEdit 变成 inputTDBGrid 变成带分页的表格窗体上的事件自动生成回发脚本后端仍由 Delphi 进程执行业务代码。安装包是一个 zip里面是 bpl、dcu 和 demo 工程解压后在 Delphi 13.1 里配好路径就能编译。这套框架不要求你会 React 或 Vue也不要求重写数据访问层。适合手里有排产、进销存、设备监控这类老项目、准备把界面搬到浏览器交付的团队也适合想用 Delphi 继续做 Web 服务端、又不想碰 HTTP 细节的个人开发者。下面从映射原理讲到三个高频排错点每一步都有可复制的代码和参数。2. D2Bridge Framework 的控件映射与回发原理2.1 三层映射VCL 控件树怎么变成 DOMD2Bridge 渲染一个窗体时不是手动拼接 HTML 字符串而是把 Form 当作一棵控件树递归遍历。每个 VCL 控件按类名找到对应的“渲染器”由渲染器输出 HTML 片段和配套的 CSS 类子控件递归嵌到父容器里。这个映射关系在安装包里通常以组件映射表形式给出常用对应关系如下。VCL/D2Bridge 控件常用属性对应输出的前端元素TaaLabelCaptionspan.d2bridge-labelTaaEditTextinput[typetext]TaaComboBoxItems、ItemIndexselect optionTaaMemoLinestextareaTaaButtonCaption、Enabledbutton[data-actionclick]TaaDBGridColumns、DataSourcetable.d2bridge-gridTaaPanelColor、Aligndiv.d2bridge-panel从表里能看出两个特点。第一控件名基本是“Taa 原 VCL 类名”这是刻意设计方便老代码按名字替换。第二属性不是一对一翻译例如 TaaDBGrid 的 Columns 会被渲染成表头而 DataSource 里的记录会走另一套 JSON 序列化逻辑前端拿到数据后填充表格。理解这层映射后面调列宽、间距、图表刷新时才不会对着 DOM 瞎猜。映射过程本身也支持自定义。常见做法是继承框架的控件渲染器重写Render方法输出自己的 HTML 模板再把新渲染器注册到控件类型上。这样内网系统里的专用控件——比如设备状态灯、工艺流程图的 Canvas 绘制——也能保留原样出现在浏览器里。我一般会把这类自定义渲染器单独放一个单元避免和业务窗体耦合。2.2 请求生命周期控件事件是如何回发的浏览器里的按钮没有 Delphi 的TNotifyEventD2Bridge 的处理方式是让每个可交互控件自动挂一个事件监听器点击后 JS 收集当前页面的表单状态打包成 JSON通过 XMLHttpRequest 或 fetch 发到后端后端根据回调地址找到对应方法执行再把需要更新的局部内容返回。这个流程和传统 AJAX 类似但回调路由是框架在窗体创建时自动注册的。type TLoginForm class(TD2BridgeForm) private procedure DoLogin(Sender: TObject); public procedure OnCreate; override; end; procedure TLoginForm.OnCreate; begin inherited; // 注册浏览器访问路径例如 http://localhost:8080/login RegisterRoute(login, LoginPage); TaaEdit.Create(Self) .SetName(txtUser) .SetPlaceHolder(用户名); TaaEdit.Create(Self) .SetName(txtPwd) .SetInputType(password) .SetPlaceHolder(密码); TaaButton.Create(Self) .SetText(登 录) .SetOnClick(DoLogin); end;这段代码做的事是在窗体初始化时创建一个登录页所需的两个输入框和一个按钮并给按钮绑定后端事件DoLogin。这里的关键点是SetName前端回发时靠控件的 Name 来定位数据所以 Name 必须唯一且不要包含中文。RegisterRoute的第一个参数是浏览器地址后缀第二个参数是页面标题它同时决定了前端路由和日志里看到的路由名。当用户点击登录按钮请求会以 POST 方式发到类似/callback/login/b1的地址D2Bridge 在服务端根据 Session 找到之前的窗体实例把 JSON 里的值填回 TaaEdit 的 Text然后调用DoLogin。你在事件里读写控件的 Text、Visible 这些属性和 Win32 时代的编程习惯一致差别只在事件执行完框架会对比渲染前后的状态差异把变化的部分推给浏览器而不是整页刷新。2.3 与 TWebBroker、uniGUI 的选型差异同样是 Delphi 做 WebTWebBroker 是纯 HTTP 框架没有控件映射所有 HTML 都要自己写 Response 字符串适合接口类应用不适合把现有 VCL 窗体搬过去。uniGUI 是另一种成熟方案它让 VCL 控件常驻在服务器内存里客户端操作实时同步控件状态代价是每个会话占用服务器资源较高Active Form 较多时对内存和锁的调优要求也上来了。对比项TWebBrokeruniGUID2Bridge Framework控件映射无VCL 控件常驻内存请求时重建控件树前端依赖完全手写自带 JS 框架自带轻量 JS 渲染器服务器内存占用很低高中等原有 VCL 代码复用度低高高学习成本需学 HTTP 编程需理解会话锁熟悉 VCL 即可上手D2Bridge 的差异点在于“组件树不常驻”。每次请求到来时框架重建窗体实例从 Session 恢复控件的关键状态执行完事件后立即释放。这样做内存压力小但也意味着你在事件里创建的临时状态不能默认存活到下一次请求需要显式写入 Session。这是从 Win32 转过来最容易踩的坑后面排错章节会专门讲。3. 用 Delphi 13.1 安装 D2Bridge Framework 并跑通最小页面3.1 解压、配置 Library 路径与编译运行时包拿到 D2Bridge Framework.zip 后先解压到一个纯英文目录比如D:\libs\D2Bridge。解压后一般能看到几个子目录Source放源码、Lib放预编译的 dcu、Demo放示例工程、根目录下是安装说明。不建议直接解压到 Delphi 的安装目录否则升级 IDE 时容易被清理掉也不方便多个版本共存。配置 Library 路径是第一步打开 Delphi 13.1菜单 Tools Options Language Delphi Library在 Library path 里加入D:\libs\D2Bridge\Source和D:\libs\D2Bridge\Lib\Win32。加入后 Delphi 才能找到框架的 dcu 文件。接着打开Lib里的运行时包工程通常是.dpk后缀在项目管理器里选择 Build。如果只是自己用可以不安装设计期包想在窗体设计器里直接拖 D2Bridge 控件才需要右键 Install 设计期包。用命令行编译也常见尤其在 CI 环境批量构建时msbuild D2Bridge.dproj /t:Build /p:ConfigRelease /p:PlatformWin32 /p:DCC_DefineDEBUG/t:Build表示执行构建ConfigRelease关闭调试信息PlatformWin32指定生成 32 位 bplDCC_Define传入条件编译符号。注意 32 位和 64 位包不能混用如果你的主程序是 Win64就必须编译对应的 Win64 版本否则报“模块 %1 中找不到入口点”之类的错误。3.2 最小可运行工程宿主程序加一个窗体D2Bridge 应用的结构是“宿主程序 D2Bridge Form”。宿主程序负责启动 HTTP 服务Form 负责页面内容。工程文件里常见做法如下program LoginDemo; uses Vcl.Forms, IvyD2BridgeApp, uMainForm in uMainForm.pas; var App: TD2BridgeApp; begin App : TD2BridgeApp.Create(nil); try App.Title : D2Bridge Login Demo; App.Port : 8080; App.URLBase : app; App.Run; finally App.Free; end; end.这里TD2BridgeApp是框架自己的 Application 宿主Port决定 HTTP 监听端口URLBase是访问路径前缀Run是阻塞监听循环按 CtrlC 或关闭控制台时退出。启动后浏览器访问http://localhost:8080/app/login就能看到登录页。对应窗体单元最精简的骨架如下type TMainForm class(TD2BridgeForm) protected procedure OnCreate; override; end; procedure TMainForm.OnCreate; begin inherited; RegisterRoute(login, 登录页); TaaLabel.Create(Self).SetText(用户名); TaaEdit.Create(Self).SetName(txtUser); TaaButton.Create(Self) .SetText(提交) .SetOnClick( procedure(Sender: TObject) begin TaaShowMessage(欢迎 TaaEdit(Self.FindComponent(txtUser)).Text); end); end;这段代码说明三个要点RegisterRoute必须在 OnCreate 里调用框架依赖它建立路由表TaaButton.SetOnClick支持匿名方法适合简单逻辑事件里通过Self.FindComponent(txtUser)拿到控件和 VCL 里按 Name 查找控件的方式一致区别在于这里查的是当前请求周期重建出来的实例。3.3 三个必调参数Port、SSL 和 Session Timeout初次部署最容易忽略的是参数配置D2Bridge 的官网示例里通常都写在宿主程序里但生产环境我一般建议单独提出来放配置文件。参数一般位置示例值说明App.Port宿主程序8080HTTP 监听端口和反向代理转发端口保持一致App.SSL宿主程序False内网测试可关生产环境在 Nginx 层终结 SSLSessionTimeout窗体或上下文30页面无操作多少分钟后会话失效单位通常为分钟App.ThreadPoolSize宿主程序32处理回调的工作线程数量大时酌情上调App.Port不是随便改改就行。如果部署在 Linux 容器里主机端口映射要和它对应如果前面有 Nginxproxy_pass http://127.0.0.1:8080里的端口也得同步改否则页面能打开但登录回调全部 404。App.SSL我建议默认保持 False证书配置放在 Nginx 或 IIS 上比在 Delphi 进程里管理证书方便得多也能避免浏览器报“证书无效”。SessionTimeout决定用户长时间挂机后是否被踢出。调太小操作员填单填到一半提交会跳回登录页调太大服务器上堆积的会话状态会缓慢增长。我一般先在测试环境用 15 分钟压测再根据平均操作时长放宽到 30 左右。4. 控件改造实战DBGrid、Panel 与 Chart 的 Web 化参数4.1 TaaDBGrid数据网格的列、分页与行点击老系统里最重的控件大概率是 TDBGrid。D2Bridge 提供了 TaaDBGrid用法上保留了 DataSource 绑定但列定义需要在代码里显式声明不再像 VCL 那样在对象监视器里右键增加列。推荐的做法是运行时绑定Grid : TaaDBGrid.Create(Self) .SetName(gridOrder) .SetDataSource(MemTable) .SetReadOnly(True); Grid.AddColumn(cNo, 单号, 120); Grid.AddColumn(cName, 品名, 200); Grid.AddColumn(dDate, 日期, 100); Grid.SetPageSize(20); Grid.OnRowClick : DoRowClick;AddColumn三个参数分别是字段名、表头文字、列宽单位是 CSS 像素。SetPageSize(20)会让前端自动显示分页按钮但后端数据切片要自己做框架只把当前页需要的记录交给前端。常见做法是在表单 OnCreate 里根据 PageIndex 重新查询数据集只取 20 条如果一次性把全量数据塞进 MemTable数据量大时首屏加载会明显变慢。OnRowClick在回调里能拿到行号要拿主键值就不要依赖显示列的序号而是单独加一列隐藏字段或者在数据集里把主键作为第一个字段但设Visible : False。注意行点击事件默认不选中整行需要高亮时要在事件里Grid.SelectedRow : AIndex否则用户看不出当前选了哪一行。4.2 Panel 布局与子控件间距的配置VCL 里调控件位置靠 Left、Top 和 AlignD2Bridge 把这些转换成 CSS 布局。TaaPanel 默认输出块级 div子控件按顺序从上往下排。要改成横向排列需要显式设置样式常见配置如下Panel : TaaPanel.Create(Self) .SetStyle(display, flex) .SetStyle(flex-direction, row) .SetStyle(gap, 12px) .SetStyle(align-items, center); Panel.AddChild(EditName); Panel.AddChild(EditPassword); Panel.AddChild(BtnLogin);这里gap是子控件间距比给每个控件单独设 margin 更省事也不会出现相邻两个控件 margin 叠加导致间隔忽大忽小的问题。align-items: center让输入框和按钮在纵向居中对齐视觉上比默认的 stretch 好看。需要提醒的是Flex 布局下子控件的宽度不再按 VCL 的 Width 百分比展开输入框要限定宽度时用SetStyle(width, 240px)或者用flex-grow控制占比。如果页面包含左右两栏我一般在外层 Panel 上设display: flex里层再套子 Panel里层各自设flex-direction。D2Bridge 会保持父子顺序输出 DOM所以 CSS 的层叠规则和普通网页一致样式排查可以用浏览器开发者工具直接看元素。4.3 Chart 控件Series 数据绑定与重绘时机图表在 VCL 里常用 TeeChartD2Bridge 这类框架通常自带一个轻量图表组件。它的数据填充方式比 VCL 更接近“前端图表库”的思路先创建序列再逐点添加数据最后必须调用渲染方法。Chart : TaaChart.Create(Self) .SetName(chartTrend) .SetTitle(访问量趋势); Series : Chart.AddSeries(pv, 页面访问量); Series.AddPoint(2025-03-01, 128); Series.AddPoint(2025-03-02, 156); Series.AddPoint(2025-03-03, 142); Chart.Options.LegendVisible : True; Chart.Options.YAxisMin : 0; Chart.Render;AddSeries的第一个参数是序列内部名称第二个参数是图例显示名。AddPoint的两个参数分别对应 X 轴分类和 Y 轴数值X 轴传TDateTime或格式化后的字符串都行但同一图表里格式要统一混用2025/03/01和2025-03-01会出现坐标轴刻度错乱。Chart.Options.YAxisMin : 0强制 Y 轴从 0 开始避免数据波动小时图形放大失真。最容易犯的错是忘记调Chart.Render或者数据集重新查询后只调了AddPoint没清空旧序列。重新绑定数据前先执行Chart.ClearSeries再重新 AddSeries 和 AddPoint最后Render。事件回调里如果是在循环中逐条从数据集读数据读完一次立即 Render 一次会造成页面闪烁正确做法是先收集到数组循环结束后一次性填充。5. 验证与排错请求日志、Route 冲突和刷新问题5.1 启用请求日志从回发到响应的完整链路页面能打开但登录没反应这类问题不能靠断点乱试第一步是确认请求有没有到后端。D2Bridge 宿主通常提供请求日志接口常见做法是在TD2BridgeApp上挂日志事件App.OnRequestLog : procedure(AContext: TD2BridgeRequestContext) begin LogEvent(Format(%s %s status%d cost%dms, [AContext.Method, AContext.URL, AContext.StatusCode, AContext.ExecutionMs])); end;AContext.Method是 GET 还是 POSTAContext.URL是带路由的完整地址StatusCode为 404 时说明路由没注册上为 500 时看事件里的异常堆栈。日志文件里如果只有POST /callback/login/b1没有后续记录说明事件方法没有执行如果有 500把异常类型和堆栈贴到搜索引擎里排查效率比肉眼盯代码高得多。5.2 Route 冲突与控件不刷新多个窗体注册了相同的路由后缀启动时不会报错但后注册的会覆盖先注册的访问时永远进的是最后一个窗体。排查方法是打开框架路由表把当前所有注册的路由名打出来看有没有重复。另一个更隐蔽的问题是“控件不刷新”数据源执行过Close; Open;之后页面表格没变化。原因通常是业务代码在回调里改了数据集但没通知前端更新。修复方式Grid.Refresh;Refresh会把新的数据集记录序列化后推送到浏览器。如果数据集是异步查询执行 Refresh 时线程还没返回页面也不会变这时要在数据集的 AfterOpen 事件里再调用一次Grid.Refresh。刷新只推变化部分不是整表单重绘所以日志里看到UPDATE_GRID而不是RERENDER是正常的。5.3 中文乱码从数据库到浏览器的三层检查中文乱码在 Windows 下的老项目里尤其常见尤其是 SQLite 场景。排查按三层顺序来第一层看数据库连接例如 FireDAC 连接 SQLite 时设OpenModeCreateUTF8老库是 ANSI 编码时不要强制转换连接串加StringFormatUnicode更保险。第二层看 Delphi 源文件和 DFM 的保存编码IDE 里把文件编码改为 UTF-8尤其是窗体里有中文字面量的。第三层看 HTTP 响应头浏览器开发者工具里检查Content-Type是否带charsetutf-8。检查层常见症状处理位置数据库连接从 SQLite 读出的中文变成问号连接串加 OpenModeCreateUTF8源文件/IDE代码里的中文字符串乱码文件另存为 UTF-8响应头浏览器显示 UTF-8 乱码框架设置 Response.Charset前端页面个别字变成菱形框检查字体 CSS不用 SimSun 直接用系统 sans-serif设置完这几点再刷新浏览器打开开发者工具的网络面板看/callback/响应体的中文是否正常不出乱码就可以继续下一个排错了。本文还有配套的精品资源点击获取
返回列表