news 2026/10/15 15:51:03

Delphi嵌入ECharts实现工业软件数据可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Delphi嵌入ECharts实现工业软件数据可视化

简介:本资源是一个面向Delphi开发者的数据可视化实践项目,聚焦于在原生Windows桌面应用中集成百度ECharts图表库,解决传统Delphi程序缺乏现代交互式图表能力的痛点,适用于数据分析、监控系统、报表工具等需动态展示数据的中高级开发场景。压缩包为7z格式,共9个文件,涵盖核心源码(Unit1.pas、Project1.dpr)、界面定义(Unit1.dfm)、项目配置(Project1.dproj、.local)、可执行文件(Project1.exe)、Web容器页(echartsdemo1.htm)、资源文件(Project1.res、echarts.gif)及平台标识(Win32目录),完整呈现从Pascal逻辑调用JavaScript图表的全链路实现。目前已有789人学习下载。读者可直接运行exe查看效果,通过dfm与pas代码掌握WebBrowser组件加载HTML/JS、动态注入ECharts配置、绑定JSON数据及响应用户点击事件的关键技巧,是少有的Delphi与前端可视化深度结合的可运行范例。

1. Delphi 调用百度 ECharts Demo:为什么一个“前端图表库”要在 Delphi 里跑?

这不是在折腾——而是真实产线上的刚需。某高校实验室做设备状态监控系统,后端用 Delphi 写服务调度与硬件通信(串口/PLC 协议稳定、COM 组件封装成熟),但客户突然要求加实时趋势图、多维度热力图、带缩放拖拽的时序曲线。团队试过 TChart,发现交互弱、主题定制难、移动端适配差;也试过 WebBrowser 嵌入 HTML,结果发现 IE 内核在 Win10/11 上默认禁用、ActiveX 权限策略收紧、JS 调用 Delphi 对象时跨域报错频发,上线前两周全卡在“图表打不开”。最后落地方案是:Delphi 主进程不渲染图表,只做数据供给与事件桥接;ECharts 在嵌入式 Chromium 内核中运行,通过 JSBridge 双向通信。这个 Demo 就是该方案最小可运行闭环——它不依赖外部 Web 服务器,不走 HTTP 请求,所有 HTML/JS/CSS 打包进资源文件,启动即用,且支持 Delphi 10.4~11.3 全版本。适合正在维护传统 Win32 工业软件、又急需现代化数据可视化的工程师。你不需要会写 Vue 或 React,只要懂TWebBrowser和IDispatch的基本调用逻辑,就能把 ECharts 接进现有项目。


2. 用 TWebBrowser + 内置 HTML 资源实现零依赖加载

2.1 为什么不用 WebView2?先说清选型硬约束

Delphi 官方对 WebView2 的原生支持直到 11.2 才初步稳定(TWebView2组件仍标记为 Preview),而大量存量项目还在 10.4.2(Sydney)或 10.3.3(Rio)上跑。某跨平台系统升级 WebView2 后,在部分工控机(Intel Atom + Windows IoT LTSC 2019)上出现WebView2Loader.dll初始化失败、GPU 渲染黑屏、内存泄漏无法回收等问题,回滚耗时 3 天。反观TWebBrowser(基于 MSHTML/Trident)虽老旧,但胜在:

  • 全版本 Delphi 原生支持,无需额外 DLL;
  • 本地res://协议可直接读取.rc资源中的 HTML/JS,规避文件路径权限问题;
  • IDispatch接口调用 JS 函数稳定,无跨线程 COM 套接字阻塞风险。

提示:本方案不是“技术怀旧”,而是面向交付的务实选择——当你的客户不允许装 .NET Runtime、拒绝签发自定义证书、且部署机禁止联网时,TWebBrowser 是唯一能 100% 控制的渲染层。

2.2 把 ECharts 打包进 Delphi 资源文件(.rc)

ECharts 官方提供echarts.min.js单文件(v5.4.3,约 820KB),无需 Node.js 构建。关键动作是将其转为 Delphi 资源:

  1. 新建文本文件echarts_js.rc,内容如下:
ECHARTS_JS RCDATA "echarts.min.js"
  1. 用brcc32.exe编译(Delphi 安装目录下):
brcc32 echarts_js.rc

生成echarts_js.res;
3. 在 Delphi 工程.dpr文件顶部加入:

{$R 'echarts_js.res'}

逻辑说明:RCDATA类型资源可被FindResource/LoadResourceAPI 直接读取为内存块,比从磁盘读取快 3~5 倍(实测 10ms → 2ms),且避免杀毒软件扫描 JS 文件触发误报。echarts.min.js必须用官方 CDN 下载(https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js),切勿用本地构建版——后者依赖zrender模块,需额外打包,增加资源体积与加载失败概率。

2.3 构建内嵌 HTML 页面并注入 ECharts 初始化脚本

新建chart.html,核心结构如下(精简版,去除非必要 meta):

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>ECharts for Delphi</title> <style>html, body { margin:0; padding:0; height:100%; overflow:hidden; }</style> </head> <body> <div id="main" style="width:100%;height:100%;"></div> <!-- 从资源加载 ECharts --> <script> // 步骤1:用 res:// 协议读取资源 var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'res://echarts_demo.exe/ECHARTS_JS'; document.head.appendChild(script); // 步骤2:等待 ECharts 加载完成 script.onload = function() { // 初始化图表容器 var chartDom = document.getElementById('main'); var myChart = echarts.init(chartDom); // 步骤3:绑定 Delphi 暴露的全局函数(见 3.1) window.delphiCallback = function(data) { myChart.setOption(data); }; // 步骤4:触发 Delphi 端首次数据推送 if (typeof window.external !== 'undefined' && typeof window.external.OnReady === 'function') { window.external.OnReady(); } }; </script> </body> </html>

将chart.html同样编译进资源(CHART_HTML RCDATA "chart.html"),并在窗体创建时用Navigate加载:

WebBrowser1.Navigate('res://echarts_demo.exe/CHART_HTML');

参数说明:res://协议格式为res://<exe名>/<资源名>,Delphi 资源名区分大小写,CHART_HTML必须与.rc中定义完全一致;window.external是TWebBrowser暴露给 JS 的 COM 对象,用于 JS 主动调用 Delphi 方法(如通知“页面已就绪”),而非 Delphi 被动轮询。


3. 实现 Delphi 与 JS 的双向通信:IDispatch 是关键

3.1 让 JS 能调用 Delphi:注册 IDispatch 对象

Delphi 不能直接响应 JS 的window.external.xxx(),必须实现IDispatch接口并挂载到WebBrowser的Application属性。核心类如下:

type TWebBridge = class(TAutoObject, IDispatch) private FOnReady: TNotifyEvent; public procedure OnReady; safecall; property OnReadyEvent: TNotifyEvent read FOnReady write FOnReady; end; procedure TWebBridge.OnReady; begin if Assigned(FOnReady) then FOnReady(Self); end; // 注册对象到 WebBrowser procedure TForm1.FormCreate(Sender: TObject); var Dispatch: IDispatch; begin Dispatch := TWebBridge.Create as IDispatch; TWebBridge(Dispatch).OnReadyEvent := ChartReadyHandler; WebBrowser1.Application := Dispatch; end;

逻辑说明:TAutoObject是 Delphi RTL 提供的轻量 COM 对象基类,自动处理AddRef/Release;safecall关键字确保 JS 调用时异常被转换为 HRESULT 返回(否则 JS 会静默失败);WebBrowser1.Application是唯一可写入IDispatch的属性,其他如Document或ParentWindow均无效。

3.2 让 Delphi 能调用 JS:执行字符串脚本并传参

ECharts 的setOption需要 JSON 格式数据,Delphi 用System.JSON构造后,通过IHTMLWindow2.execScript注入:

uses System.JSON, Vcl.OleCtrls, SHDocVw, MSHTML; procedure TForm1.UpdateChart(const AOption: TJSONObject); var Doc: IHTMLDocument2; Win: IHTMLWindow2; Script: string; begin Doc := WebBrowser1.Document as IHTMLDocument2; Win := Doc.parentWindow; // 将 JSON 对象转为 JS 字符串(注意单引号包裹) Script := Format('delphiCallback(%s);', [AOption.ToJSON]); Win.execScript(Script, 'JavaScript'); end; // 调用示例:绘制折线图 procedure TForm1.DrawLineChart; var Option: TJSONObject; begin Option := TJSONObject.Create; Option.AddPair('tooltip', TJSONObject.Create); Option.AddPair('xAxis', TJSONObject.Create.AddPair('type', 'category') .AddPair('data', TJSONArray.Create(['Mon', 'Tue', 'Wed', 'Thu', 'Fri']))); Option.AddPair('yAxis', TJSONObject.Create.AddPair('type', 'value')); Option.AddPair('series', TJSONArray.Create( TJSONObject.Create.AddPair('name', 'Sales') .AddPair('type', 'line') .AddPair('data', TJSONArray.Create([120, 200, 150, 80, 70])) )); UpdateChart(Option); end;

参数说明:execScript第二个参数必须是'JavaScript'(大小写敏感),传'JScript'会失败;JSON 字符串中双引号需被转义(TJSONObject.ToJSON自动处理),但最外层必须用单引号包裹,否则 JS 解析报错;TJSONArray.Create的参数列表不能有尾随逗号,否则 IE 内核解析失败(这是血泪经验)。

3.3 传递复杂数据:绕过 JSON 序列化的二进制方案

当图表数据量大(如 10 万点时序数据)时,JSON 序列化/反序列化 CPU 占用高,且execScript有字符串长度限制(IE 内核约 64KB)。此时改用window.external的SetData方法传内存地址:

// Delphi 端:申请共享内存,写入原始字节 var DataPtr: Pointer; DataSize: Integer; SharedMem: THandle; begin DataSize := Length(RawDataBytes); SharedMem := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, DataSize, 'EChartsData'); DataPtr := MapViewOfFile(SharedMem, FILE_MAP_ALL_ACCESS, 0, 0, DataSize); Move(RawDataBytes[0], DataPtr^, DataSize); // 通知 JS 读取 Win.execScript(Format('window.external.SetData("%s", %d);', ['EChartsData', DataSize]), 'JavaScript'); end;

JS 端用window.external.GetData读取(需 Delphi 实现对应方法),再用Uint8Array解析。此方案将 50MB 数据传输耗时从 1200ms 降至 80ms。


4. 避坑:TWebBrowser 调用 ECharts 的 4 个高频翻车点

4.1 现象:页面白屏,F12 开发者工具报echarts is not defined

原因:res://协议加载 JS 时,IE 内核默认启用“增强保护模式”(Enhanced Protected Mode),阻止res://协议访问本地资源。即使关闭 IE 的“启用保护模式”,Delphi 进程仍继承系统策略。
解决:在工程.dpr中添加注册表写入(仅首次运行):

uses Winapi.Windows, Winapi.WinReg; procedure DisableEPMForResProtocol; var Reg: HKEY; begin if RegCreateKeyEx(HKEY_CURRENT_USER, 'Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_DISABLE_MK_PROTOCOL_RESTRICTIONS', 0, '', REG_OPTION_NON_VOLATILE, KEY_WRITE, nil, Reg, nil) = ERROR_SUCCESS then begin RegSetValueEx(Reg, 'echarts_demo.exe', 0, REG_DWORD, @DWORD(1), SizeOf(DWORD)); RegCloseKey(Reg); end; end;

注意:此操作需管理员权限,建议在安装程序中执行,而非运行时弹窗提权。

4.2 现象:window.external.OnReady()调用无响应,OnReadyEvent从未触发

原因:TWebBrowser的Application属性只能在DocumentComplete事件之后设置。若在FormCreate中直接赋值,此时Document为空,COM 对象注册失败。
解决:监听OnDocumentComplete事件,在回调中设置:

procedure TForm1.WebBrowser1DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant); begin if URL = 'res://echarts_demo.exe/CHART_HTML' then begin WebBrowser1.Application := TWebBridge.Create as IDispatch; end; end;

4.3 现象:图表渲染后,鼠标悬停提示框(tooltip)位置偏移、跟随鼠标错位

原因:TWebBrowser容器设置了Align := alClient,但其父窗体启用了DoubleBuffered := True,导致 IE 内核获取的坐标系与实际渲染区域不一致。
解决:禁用父窗体双缓冲,或改用TPanel作为 WebBrowser 容器,并设置Panel1.DoubleBuffered := False;同时在 HTML 中强制重置 CSS:

#main { transform: translateZ(0); /* 触发硬件加速 */ }

4.4 现象:调用myChart.resize()后图表变形,宽高变为 0

原因:TWebBrowser的Width/Height属性在窗体缩放(DPI-aware)时未实时更新,JS 获取的offsetWidth为 0。
解决:在窗体OnResize事件中,主动触发 JS resize:

procedure TForm1.FormResize(Sender: TObject); begin if Assigned(WebBrowser1.Document) then WebBrowser1.OleObject.document.parentWindow.execScript('if(myChart) myChart.resize();', 'JavaScript'); end;

5. 进阶技巧:用 Delphi 动态生成 ECharts 配置,避开手写 JSON 的玄学

手写TJSONObject构造配置易出错(括号匹配、引号转义、类型混淆),尤其面对visualMap、geo、graph等复杂组件时。我一般会用配置模板 + 占位符替换方案,既保持可读性,又杜绝语法错误。

5.1 定义 JSON 模板(字符串常量)

在单元中声明:

const CHART_LINE_TEMPLATE = '{' + '"tooltip": {"trigger": "axis"},' + '"legend": {"data": ["#SERIES_NAME#"]},' + '"grid": {"left": "3%", "right": "4%", "bottom": "3%", "containLabel": true},' + '"toolbox": {"feature": {"saveAsImage": {}}},' + '"xAxis": {"type": "category", "boundaryGap": false, "data": #XAXIS_DATA#},' + '"yAxis": {"type": "value"},' + '"series": [{' + '"name": "#SERIES_NAME#", "type": "line", "stack": "Total", "data": #YAXIS_DATA#' + '}]}';

5.2 实现安全替换函数(防注入、保类型)

function FillChartTemplate(const Template: string; const Params: array of const): string; var i: Integer; Key, Value: string; JSONValue: TJSONValue; begin Result := Template; for i := 0 to High(Params) do begin case Params[i].VType of vtString: begin Key := '#' + Params[i].VString + '#'; Value := '"' + StringReplace(Params[i].VString, '"', '\"', [rfReplaceAll]) + '"'; Result := StringReplace(Result, Key, Value, [rfReplaceAll]); end; vtAnsiString: begin Key := '#' + string(Params[i].VAnsiString) + '#'; JSONValue := TJSONObject.ParseJSONValue(string(Params[i].VAnsiString)); try Value := JSONValue.ToJSON; Result := StringReplace(Result, Key, Value, [rfReplaceAll]); finally JSONValue.Free; end; end; end; end; end;

5.3 调用示例:一行代码生成完整配置

procedure TForm1.DrawDynamicLine; var XData, YData: string; FinalJSON: string; begin // 构造数组字符串(JSON 格式) XData := '["Jan","Feb","Mar","Apr","May"]'; YData := '[120,200,150,80,70]'; FinalJSON := FillChartTemplate(CHART_LINE_TEMPLATE, [#SERIES_NAME#, 'Monthly Sales', #XAXIS_DATA#, XData, #YAXIS_DATA#, YData]); // 直接执行 WebBrowser1.OleObject.document.parentWindow.execScript( Format('delphiCallback(%s);', [FinalJSON]), 'JavaScript'); end;

表格:模板 vs 手写 JSON 的对比
| 维度 | 手写 TJSONObject | 模板占位符方案 | |--------------|----------------------------------------|-----------------------------------| | 开发速度 | 15 分钟(反复调试括号和引号) | 2 分钟(填空式) | | 错误率 | 高(JSON 语法错误占调试时间 70%) | 极低(编译期字符串检查) | | 可维护性 | 修改字段需定位多层嵌套对象 | 全局搜索占位符名即可 | | 性能 | 构造对象开销略高(~0.3ms) | 字符串操作,平均 0.05ms | | 安全性 | 无注入风险 | 需校验vtAnsiString输入是否为合法 JSON |

这个模板法我在模拟项目 X 的仪表盘模块中用了三年,迭代 27 个图表类型,没出现一次 JSON 解析失败。它不炫技,但省下的调试时间够你喝三杯咖啡。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:50:30

Abp vNext多租户API登录改造实战指南

1. 为什么Abp vNext的租户API登录不是“开箱即用”的功能在某跨平台系统重构项目中&#xff0c;我们团队接手了一个基于Abp vNext构建的多租户SaaS后台。初期需求很明确&#xff1a;前端Web端走常规MVC登录流程&#xff0c;而移动端App、IoT设备管理后台、第三方系统集成模块&a…

作者头像 李华
网站建设 2026/10/15 15:50:19

eve 部署指南:从 Vercel 托管到自建 Node 服务的完整生产化方案

人工智能AI AgentAgent 框架后端工具调用 【免费下载链接】eve The Open Framework for Building Agents 项目地址&#xff1a; https://gitcode.com/gh_mirrors/eve28/eve 点击查看 免费下载 本指南围绕 eve 官方部署概览展开&#xff0c;系统讲解在 Vercel 与自建基础设施两…

作者头像 李华
网站建设 2026/10/15 15:48:54

基于HMS ML Kit的Android运动App姿势抓拍实现

前阵子帮某健身团队做一款跟练App的Demo&#xff0c;需求听起来不复杂&#xff1a;用户对着手机摄像头做动作&#xff0c;App要能识别出“现在摆的是什么姿势”&#xff0c;并且在动作到位的那一刻自动抓拍一张照片。说白了就是很多运动类产品里的“动作瞬间捕获”功能。可真要…

作者头像 李华
网站建设 2026/10/15 15:48:43

世界上不是没有公平

人机协作&#xff0c;仅供参考。生活中常有人感叹世界不公&#xff0c;但公平并非不存在。它藏在日常事物与制度规则里&#xff0c;等待我们看见和珍惜。时间最公平。无论贫富忙闲&#xff0c;每个人每天都拥有二十四小时&#xff0c;怎样利用&#xff0c;决定了不同的人生。空…

作者头像 李华
网站建设 2026/10/15 15:48:03

ChatGPT技术演进四层跃迁:从Transformer到RLHF的工程化落地路径

简介&#xff1a;本资源是一份面向AI研究者、技术管理者与高校师生的《ChatGPT研究框架》深度解析PPT&#xff0c;系统梳理生成式人工智能&#xff08;AIGC&#xff09;从技术演进到商业落地的完整逻辑链。内容覆盖ChatGPT爆发的底层动因&#xff08;Transformer架构成熟、GPT系…

作者头像 李华
网站建设 2026/10/15 15:47:42

Windows CMD下用curl操作Elasticsearch 7.7.0:索引、查询与排坑全指南

我到现在都记得第一次在Windows的CMD里折腾Elasticsearch 7.7.0时&#xff0c;被双引号转义和JSON格式折磨到怀疑人生的样子。这个版本虽然已经不算新&#xff0c;但在内网离线环境、课程实验、临时数据验证这些场景里依然大量存在——尤其当手边没有Kibana、没有图形界面工具的…

作者头像 李华