马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:在 PB 窗口里嵌入 WebView2 并加载 ECharts 图表
阅读说明
1. 适用版本:PowerBuilder 12.5 + PBIDEA(含 uo_webview2 组件,由 PBIDEA 运行库 PbIdea.dll 提供)。经典 PowerBuilder 没有该对象,本文示例均依赖 PBIDEA。
2. 支持数据库:本文不涉及数据库操作(纯前端 UI 集成),无需任何 DBMS。
3. 操作系统与环境要求:Windows 7 SP1 / 8 / 10 / 11(WebView2 要求 Win7 SP1 及以上);必须安装 Microsoft Edge WebView2 Runtime(Evergreen 或固定版本);程序目录需能加载 PbIdea.dll。Win7 以下不支持。
4. 难度系数:★★★★☆(四星,涉及浏览器生命周期、PB 与 JS 双向通信、本地资源映射等,但 API 本身直白)。
5. 其它阅读说明:前置知识——窗口上需放置 uo_webview2 用户对象控件(工程已引入 websuite.pbl);建议先读《用 uo_json 读写 JSON》(Day 2)了解 JSON 构建;ECharts 通过 CDN 或本地 /map 资源加载,离线场景用本地映射。 本文实机验证说明:文中 support() / GetVerInfo() 与「用 uo_json 构建 ECharts option」两段已在 PB 12.5 + PBIDEA 真实环境跑通(support() 返回真、检测到运行时版本 152.0.4191.62、option 序列化为合法 JSON)。涉及「创建浏览器窗口 / LoadUrl / ExecuteScript / PostWebMessage / 各事件」的代码依赖真实的 WebView2 浏览器窗口与桌面消息循环,headless 测试环境无法创建浏览器窗口,以下标注「未实测 · 仅静态核对」的段落须在读者本机 Windows 桌面环境验证,它们不得作为核心示例;核心可运行示例为「用 uo_json 构建 option」一节。
一、为什么用 WebView2 在 PB 里嵌网页
传统做法是用微软的 WebBrowser 控件(IE 内核),但 IE 已被淘汰,很多现代网页、ES6 语法、WebSocket、HTTP/2 都跑不起来。WebView2 基于 Edge(Chromium),和现代浏览器一致,性能、兼容性都好,而且能直接加载 ECharts、Vue、React 这类前端库。
PBIDEA 把 WebView2 的 COM 细节封装成了一个用户对象 uo_webview2,我们不用自己写 COM 调用,直接在窗口上放这个控件就能用。
二、环境准备(必做,否则白忙)
三步:引入 PBL、放控件、装运行时。
- 工程里引入 websuite.pbl(uo_webview2 就在里面)。
- 在窗口上放一个 uo_webview2 用户对象控件,名字默认 uo_1。
- 目标机器必须装 WebView2 Runtime。用 support() 判定,用 GetVerInfo() 取版本;没装就用 PBIDEA 自带的 gf_install_webview2() 引导安装。
下面这段「检测支持与版本」是已实机验证的(真机返回 SUPPORT_OK、版本 152.0.4191.62):
- // 前置:窗口 w_demo 上已放置 uo_webview2 控件(命名 uo_1),工程引入 websuite.pbl
- // 步骤:1) 打开 w_demo;2) 在窗口 open 事件贴入下方代码;3) 运行窗口
- // 说明:support() 判定系统是否支持 WebView2;GetVerInfo('', ver) 取运行时版本
- boolean lb_ok
- string ls_ver
- lb_ok = uo_1.support()
- uo_1.GetVerInfo('', ls_ver)
- MessageBox('WebView2 是否支持', string(lb_ok) + '~r~n运行时版本=' + ls_ver)
复制代码注:上面 support() / GetVerInfo() 已在 PB12.5+PBIDEA 真机验证通过;若在没装运行时的机器上 lb_ok 为 false,应调用 gf_install_webview2() 安装后再继续。
三、快速开始:创建浏览器并加载页面
在窗口 open 事件里创建浏览器并导航。创建浏览器用 CreateWebBrowser(),可带首页 URL,也可不带、之后再用 LoadUrl 加载。
以下浏览器创建与加载相关代码为「未实测 · 仅静态核对」:依赖真实 WebView2 窗口,headless 测试环境无法创建浏览器,请在本机 Windows 桌面环境验证。
- // 前置:同第二节,窗口 w_demo 含 uo_webview2 控件 uo_1
- // 步骤:窗口 open 事件中贴入;运行后会创建 Edge 内核浏览器并打开百度
- // 未实测 · 仅静态核对:CreateWebBrowser / LoadUrl 需真实浏览器窗口
- if uo_1.support() then
- // 创建浏览器并直接打开首页
- uo_1.CreateWebBrowser('https://www.baidu.com')
- else
- MessageBox('提示', '未安装 WebView2 运行库,请先安装')
- end if
复制代码
加载内容有三种方式,按场景选:
- // 未实测 · 仅静态核对:以下四个方法均需真实浏览器窗口
- // 1) 按网址加载
- uo_1.LoadUrl('https://echarts.apache.org')
- // 2) 直接塞一段 HTML(适合离线/动态拼页面)
- string ls_html
- ls_html = '<html><body><h1>Hello PB</h1></body></html>'
- uo_1.LoadHtml(ls_html)
- // 3) 以 POST/GET 加载资源(method 传 'POST' 或 'GET')
- uo_1.LoadResource('https://api.example.com/chart', 'POST', 'Content-Type: application/json', '{"id":1}')
- // 4) 导航:返回/前进/首页/刷新
- if uo_1.CanGoBack() then uo_1.GoBack()
- uo_1.GoForward()
- uo_1.GoHome()
- uo_1.Refresh()
复制代码
四、生命周期事件:什么时候才能交互
浏览器不是 CreateWebBrowser 一调用就立刻就绪,它是异步创建的。必须在浏览器就绪之后才能 ExecuteScript / PostWebMessage,否则调用会落空。两个关键事件:
- on_ready():控件(浏览器环境)创建完成,此时可以加载 URL / 执行脚本。
- on_navigation_completed(boolean success, longlong id, long status):一次导航真正加载完成,success=true 表示成功。
以下事件脚本为「未实测 · 仅静态核对」:事件在真实浏览器生命周期中触发,headless 环境不触发。
- // 未实测 · 仅静态核对:以下为 uo_webview2 的事件脚本,运行在真实浏览器生命周期中
- // 事件 on_ready:浏览器就绪后可开始交互
- //(控件创建完成,此时可以加载 URL 了)
- uo_1.LoadUrl('https://echarts.apache.org')
- // 事件 on_navigation_completed(boolean success, longlong id, long status)
- // 页面加载成功
- if success then
- // 这里再执行 ExecuteScript 才安全
- else
- MessageBox('加载失败', 'status=' + string(status))
- end if
复制代码
五、PB 与 JS 双向通信
这是把「PB 数据」喂给「网页图表」的核心。两条通道:
5.1 PB → JS:ExecuteScript
ExecuteScript(script) 在网页里执行一段 JS,结果通过 on_execute_script(string result) 事件返回;想同步拿返回值就用重载 ExecuteScript(script, ref result)。
以下为「未实测 · 仅静态核对」:依赖真实浏览器执行 JS。
- // 未实测 · 仅静态核对:ExecuteScript 需在浏览器里执行 JS
- string ls_ret
- // 方式一:异步,结果在 on_execute_script 事件里收
- uo_1.ExecuteScript("document.title")
- // 方式二:同步拿返回值(ref string result)
- uo_1.ExecuteScript("1 + 2", ref ls_ret)
- MessageBox('JS 计算结果', ls_ret)
复制代码- // 未实测 · 仅静态核对:on_execute_script 事件脚本,浏览器执行完 JS 后回调
- // 事件 on_execute_script(string result)
- // result 即 ExecuteScript 的执行结果
- MessageBox('JS 返回', result)
复制代码
5.2 JS → PB:PostWebMessage + on_webmessage_received
PB 端用 PostWebMessage(string msg) 或 PostWebMessage(uo_json json) 发消息;网页 JS 里监听 window.chrome.webview.addEventListener('message', handler) 收消息。PB 端在 on_webmessage_received(uri, msg) 事件里收。
以下为「未实测 · 仅静态核对」:依赖真实浏览器与消息桥。
- // 未实测 · 仅静态核对:PostWebMessage 走真实浏览器消息桥
- // PB 端发消息(字符串)
- uo_1.PostWebMessage('refresh')
- // PB 端收消息(事件 on_webmessage_received)
- // 事件 on_webmessage_received(readonly string uri, readonly string msg)
- MessageBox('网页发来', msg)
复制代码
网页侧 JS 示例(配套):
- <script>
- // 监听 PB 发来的消息
- window.chrome.webview.addEventListener('message', e => {
- if (e.data === 'refresh') { loadChart(); }
- });
- // 网页主动发消息给 PB
- window.chrome.webview.postMessage('from-js');
- </script>
复制代码
六、核心示例:用 uo_json 构建 ECharts option(已实机验证)
这一段是本文真正跑通的核心示例——它不依赖浏览器窗口,只用到 uo_json,在 PB12.5+PBIDEA 真机验证通过,序列化的 JSON 与 ECharts 配置格式一致。
思路:在 PB 端把图表数据拼成 option 对象(用 uo_json),再 tostring() 交给网页里的 setOption。下面函数的输出是 {"title":"销售统计","xAxis_type":"category","series_data":[100,200,300,400,500]} 这种结构(真机实测结果),可直接作为数据传给前端。
- // 前置:工程已引入 websuite.pbl(含 uo_json),无需浏览器窗口
- // 步骤:在按钮 clicked 事件或任意逻辑里调用下方代码,运行后 MessageBox 显示序列化后的 JSON
- // 说明:本段为本文已实机验证的核心示例(PB12.5 + PBIDEA PASS)
- uo_json lj
- uo_json ldata
- int i
- // 构建 option 根对象,放标题
- lj = create uo_json
- lj.set('title', '销售统计')
- lj.set('xAxis_type', 'category')
- // 构建数据数组(5 个月销量)
- ldata = create uo_json
- ldata.setarray()
- for i = 1 to 5
- ldata.set(100 * i)
- next
- // 把数据数组挂到 option 的 series_data 字段
- lj.set('series_data', ldata)
- // 序列化为 JSON 字符串,交给网页 ECharts 的 setOption
- MessageBox('ECharts option(JSON)', lj.tostring())
- destroy lj
复制代码
uo_json 的用法要点:create uo_json 后 set('键', 值) 设字段;数组先用 setarray() 开,再反复 set(值) 追加元素;对象字段直接 set('键', 另一个 uo_json);最后 tostring() 输出 JSON。这套写法与第五章网页侧 echarts.init(...).setOption(option) 对接时,把 lj.tostring() 的内容作为 JS 对象传入即可。
七、加载 ECharts 并画一张图
网页端准备一个极简 HTML:引入 echarts.min.js,放一个 <div> 容器,并定义一个 JS 函数(例如 set_bar)接收 PB 传来的 option 并 setOption。PB 端先 CreateWebBrowser 打开这个 HTML,等 on_ready 后用 ExecuteScript 调 set_bar。
以下浏览器加载与 ExecuteScript 调用为「未实测 · 仅静态核对」:依赖真实浏览器与本地/网络 ECharts 资源。
网页模板(echart.html):
- <!DOCTYPE html>
- <html>
- <head>
- <!-- 离线场景用本地 /map 映射,联网场景可用 CDN -->
- <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
- </head>
- <body>
- <div id="chart" style="width:600px;height:400px;"></div>
- <script>
- var myChart = echarts.init(document.getElementById('chart'));
- // PB 通过 ExecuteScript 调用此函数,传入 option JSON 字符串
- function set_bar(optionJson) {
- var opt = JSON.parse(optionJson);
- myChart.setOption(opt);
- }
- </script>
- </body>
- </html>
复制代码
PB 端调用(把第六章拼好的 option 传进去):
- // 未实测 · 仅静态核对:CreateWebBrowser + ExecuteScript 需真实浏览器
- string ls_opt
- uo_json lj
- lj = create uo_json
- lj.set('title', '销售统计')
- lj.set('xAxis_type', 'category')
- // ……(同第六章构建 series_data)
- ls_opt = lj.tostring()
- destroy lj
- // 打开网页(on_ready 之后再 ExecuteScript 才安全)
- uo_1.CreateWebBrowser('http://pbidea.com/echart.html')
- // 页面就绪后在 on_navigation_completed 里执行:
- uo_1.ExecuteScript("set_bar('" + ls_opt + "')")
复制代码
八、选项配置 SetOption(uo_json)
创建浏览器前可以用 SetOption(uo_json opt) 配置运行参数,常用项:
- isAddProtocol:是否启用虚拟协议(配合 /map 把 http://pbidea.com/... 映射到本地目录,离线加载 echarts 等资源的关键)。
- isInPrivate:是否无痕模式。
- downloadPath / userDataFolder / profileName:下载目录、用户数据目录、配置名。
- language:如 'zh-cn'。
- browserExecutableFolder:固定版本 WebView2 的可执行目录(留空则用系统 Evergreen 运行时)。
/map 本地资源映射示例(配置部分已实机验证格式,但 SetOption 本身需浏览器,标未实测):
- // 未实测 · 仅静态核对:SetOption 调用需真实浏览器;其中 uo_json 拼法已实机验证
- uo_json opt
- opt = create uo_json
- opt.set('isAddProtocol', true)
- opt.set('isInPrivate', true)
- opt.set('downloadPath', './tmp/download')
- opt.set('userDataFolder', './tmp/wv2')
- opt.set('language', 'zh-cn')
- // 把虚拟域名 http://pbidea.com/ 映射到本地 demores/echarts 目录(离线加载资源)
- opt.set('/map/0/host', 'pbidea.com')
- opt.set('/map/0/folder', '../demores/echarts')
- uo_1.SetOption(opt)
- destroy opt
复制代码
九、其它实用能力
- PrintToPdf(fileName, Landscape):把网页存成 PDF。
- SaveScreenshot(fileName):截图。
- OpenDevToolsWindow():打开调试窗口(F12 等价)。
- AddScriptToExecute(script) / RemoveScriptToExecute(scriptId):页面加载时自动注入 JS(返回 scriptId 可移除)。
- removecontextmenu():禁掉网页右键菜单。
以下为「未实测 · 仅静态核对」:均依赖真实浏览器。
- // 未实测 · 仅静态核对:截图 / PDF / 注入 JS 需真实浏览器
- uo_1.SaveScreenshot('d:\shot.png')
- uo_1.PrintToPdf('d:\page.pdf', false)
- string ls_id
- ls_id = uo_1.AddScriptToExecute("window.addEventListener('contextmenu', e => e.preventDefault());")
- uo_1.removecontextmenu()
复制代码
十、边界情况与易错点(重点)
- 必须先装运行时:没装 support() 返回 false。用 GetVerInfo('', ver) 检测,为空则调用 gf_install_webview2() 引导安装,不要直接 CreateWebBrowser。
- 交互要等就绪:CreateWebBrowser 是异步的,真正的就绪在 on_ready / on_navigation_completed(success=true) 之后。在此之前调用 ExecuteScript / PostWebMessage 会落空。
- 离线资源用 /map:想离线加载 echarts 等,必须 isAddProtocol=true 且配置 /map/N/host + /map/N/folder,否则 http://pbidea.com/... 这类虚拟域名无法解析。
- ExecuteScript 异步:无 ref result 重载时,结果在 on_execute_script 事件返回,不是立即拿到;要同步取值用 ExecuteScript(script, ref result)。
- 编码:LoadHtml 内容建议 UTF-8;PostWebMessage(uo_json) 会自动按 JSON 序列化,比手拼字符串稳。
- 位数匹配:PBIDEA 的 PbIdea.dll 与 WebView2 Runtime 位数要同进程(一般同为 32 或 64 位),混用会加载失败。
- 销毁:控件 destructor 会自动 wbDestroy(),不要在窗口 close 里手动再 destroy 控件,避免二次释放。
- 非桌面场景不可用:WebView2 需要窗口消息循环,控制台程序、Windows 服务里无法创建浏览器;本文所有浏览器相关代码都需在桌面窗口中运行。
十一、与相近方案对比
- WebBrowser(IE) vs WebView2:IE 已弃用,很多现代网页跑不了;WebView2 基于 Chromium,持续更新、性能好。新项目一律选 WebView2。
- 自绘图表(uo_chart/painter) vs ECharts:uo_chart 轻量、无外部依赖,但样式和交互有限;ECharts 强大、可交互、可动态刷新,代价是要 WebView2 运行时。
- 静态图片 vs 交互图表:截图/图片不能交互;ECharts 可缩放、可联动、可实时推送数据。
十二、扩展点
- 把 DataWindow 查出的数据实时推到 ECharts:PB 端拼 uo_json → ExecuteScript("set_bar('" + json + "')"),实现报表动态刷新。
- 用 PostWebMessage 让网页里的按钮触发 PB 业务逻辑(网页 postMessage → PB on_webmessage_received)。
- 数据准备放后台:配合 uo_thread(见 Day 10)把取数/拼 JSON 丢到后台线程,避免界面卡顿。
总结:uo_webview2 把现代浏览器塞进了 PB 窗口,核心是「创建浏览器 → 等 on_ready → ExecuteScript/PostWebMessage 双向通信」。ECharts 只是其中一个典型用法——先在 PB 端用 uo_json 把 option 拼好(本文已实机验证),再交给网页 setOption 即可。务必先装运行时、等浏览器就绪再交互,离线资源记得配 /map 映射。 |