数据绑定专题 - BindUI / BindData / SetData
📖 目录
概述
cWebView2Host 的数据绑定系统是一种声明式的 VB6-Web 双向通信机制,无需手动编写 JSON 消息解析、DOM 操作或事件监听胶水代码。
✨ 核心特点
- 🔗 双向绑定 - BindUI (UI->宿主) + BindData (宿主->UI) 覆盖双向通信
- 📝 声明式 - 用一行绑定声明替代大量胶水代码
- 🔄 自动重建 - 页面导航后自动重新注入绑定逻辑
- 🎯 CSS 选择器 - 使用标准 CSS 选择器定位 DOM 元素
- 📦 批量操作 - SetDataBatch 一次推送多个数据
架构原理
系统组成
cWebView2Host.BindUI() / BindData() / SetData()
│
▼
WebView2BindUIProxy (COM 对象,暴露为 JS 全局对象 bindUIProxy)
│
├── BindUI: JS addEventListener → bindUIProxy.onEvent(bindId, eventName, detail)
│ └── CallByName(HostInst, MethodName, vbMethod, EventName, Detail)
│
├── BindData: SetData → JS 循环遍历绑定列表 → element[attr] = value
│
└── 导航重建: DOMContentLoaded → 注入重新绑定 JS → 恢复所有 BindUI + BindData + DataValue工作流程
1. VB6 调用 BindUI/BindData → 注册绑定到 WebView2BindUIProxy 内部集合
2. GetJsCode() → 从嵌入资源加载 ProxyBindUI.js / ProxyMouseEvents.js
3. GetBindAddJs() → 生成 addEventListener 的 JS 代码
4. ExecuteScript() → 在 WebView2 中注入并执行绑定代码
5. DOM 事件触发 → JS 调用 bindUIProxy.onEvent() → CallByName → VB6 方法回调
6. SetData 调用 → 生成 JS 代码 → 遍历绑定列表 → 更新 DOM 属性BindUI - 事件绑定
基本用法
' 将 #submit-btn 的 click 事件绑定到 Me.OnButtonClick
wv.BindUI Me, "OnButtonClick", "#submit-btn"声明后,当用户在 WebView2 中点击 #submit-btn 元素时,VB6 自动调用 OnButtonClick 方法。
宿主方法签名
所有 BindUI 回调方法必须遵循以下签名:
Public Sub OnButtonClick(ByVal EventName As String, ByVal Detail As String)
' EventName: 触发的 DOM 事件名(如 "click", "input", "change")
' Detail: JSON 格式的事件详情
End SubDetail 参数解析
Detail 是 JSON 字符串,内容取决于 DOM 事件类型。常见事件的 Detail 结构:
| 事件 | Detail 示例 |
|---|---|
| click | {"type":"click","target":"#btn"} |
| input | {"type":"input","value":"用户输入的文本"} |
| change | {"type":"change","value":"新值","checked":true} |
由于 VB6 没有内置 JSON 解析,通常使用简单的字符串处理:
Public Sub OnNameInput(ByVal EventName As String, ByVal Detail As String)
' 简单提取 value 字段
Dim val As String
val = JsonValue(Detail, "value")
wv.SetData "name", val
End Sub
' 简易 JSON 值提取辅助函数
Private Function JsonValue(ByVal Json As String, ByVal Key As String) As String
Dim pos As Long
pos = InStr(Json, """" & Key & """:""")
If pos > 0 Then
pos = pos + Len(Key) + 3
Dim endPos As Long
endPos = InStr(pos, Json, """")
If endPos > 0 Then
JsonValue = Mid(Json, pos, endPos - pos)
End If
End If
End Function自定义事件名
默认绑定 click 事件,可通过 EventName 参数指定其他事件:
' 绑定 input 事件(实时响应文本输入)
wv.BindUI Me, "OnNameInput", "#name-input", EventName:="input"
' 绑定 change 事件(选择框、复选框值变化)
wv.BindUI Me, "OnToggle", "#toggle-enabled", EventName:="change"
' 绑定 submit 事件
wv.BindUI Me, "OnFormSubmit", "#my-form", EventName:="submit"多元素绑定
' 多个按钮绑定到不同方法
wv.BindUI Me, "OnSave", "#btn-save"
wv.BindUI Me, "OnCancel", "#btn-cancel"
wv.BindUI Me, "OnReset", "#btn-reset"
' 同一元素的不同事件绑定到不同方法
wv.BindUI Me, "OnFocus", "#input", EventName:="focus"
wv.BindUI Me, "OnBlur", "#input", EventName:="blur"移除绑定
' 移除指定元素的所有事件绑定
wv.UnbindUI "#submit-btn"
' 仅移除指定元素的特定事件绑定
wv.UnbindUI "#name-input", "input"BindData - 数据绑定
原理
BindData 将一个数据键名绑定到DOM 元素的特定属性。当 SetData 更新该键名时,所有绑定了该键名的 DOM 元素属性会自动更新。
支持的 DOM 属性
| 属性 | 说明 | 示例 |
|---|---|---|
textContent | 元素文本内容(默认) | 标签、段落 |
value | 表单元素值 | input、textarea、select |
innerHTML | 元素内部 HTML | 动态渲染 HTML 片段 |
src | 资源来源 | img、video、iframe |
class | CSS 类名 | 状态样式切换 |
checked | 选中状态 | checkbox、radio |
visible | 显示/隐藏(自定义) | 任意元素 |
| 其他属性 | 直接设置 DOM 属性 | href, disabled, style 等 |
基本用法
' 绑定数据键到元素属性
wv.BindData "name", "#name-input", "value"
wv.BindData "message", "#msg-display", "textContent"
' 同一键绑定到多个元素/属性
wv.BindData "name", "#name-input", "value"
wv.BindData "name", "#name-preview", "textContent" ' 同一数据,不同属性
' 绑定到特殊属性
wv.BindData "enabled", "#toggle", "checked" ' checkbox
wv.BindData "enabled", "#panel", "visible" ' 显示/隐藏
wv.BindData "avatar", "#avatar-img", "src" ' 图片地址
wv.BindData "status", "#status-dot", "class" ' CSS 类名
wv.BindData "items", "#list", "innerHTML" ' HTML 片段visible 属性
visible 是 cWebView2Host 自定义的虚拟属性,控制 DOM 元素的显示/隐藏:
wv.BindData "showPanel", "#settings-panel", "visible"
' 当 showPanel = True 时,JS 设置 element.style.display = ''
' 当 showPanel = False 时,JS 设置 element.style.display = 'none'class 属性
class 属性替换元素的全部 CSS 类名:
wv.BindData "statusOnline", "#status-dot", "class"
wv.SetData "statusOnline", "dot online" ' 绿色在线状态
wv.SetData "statusOnline", "dot offline" ' 灰色离线状态SetData - 数据推送
SetData
推送单个数据值。支持 String、Boolean、Number 类型:
wv.SetData "name", "张伟" ' 字符串
wv.SetData "enabled", True ' 布尔值
wv.SetData "count", 42 ' 数值
wv.SetData "avatar", "https://example.com/a.png" ' URL 字符串类型转换规则:
- VB6
String→ JS 字符串(自动加引号) - VB6
Boolean→ JS boolean(true/false) - VB6 数值类型 → JS number(直接传递)
SetDataBatch
批量推送多个数据值,参数为 JSON 对象字符串:
wv.SetDataBatch "{""name"":""张伟"",""enabled"":true,""count"":42}"适用于页面初始化时一次性推送所有数据。
导航后自动重建
当 WebView2 导航到新页面时,之前注入的 JavaScript 绑定代码会被销毁。cWebView2Host 自动处理此问题:
重建机制
- 每次导航导致 DOMContentLoaded 后,自动触发重建
GetReattachJs()生成重建所有绑定的 JS 代码- 重建包括:BindUI 事件监听 + BindData 声明 + DataValue 当前值
DataValue 持久化
SetData 推送的值会自动存储在 m_DataValues 集合中。页面导航后,自动将这些值重新应用到 DOM:
' 首次导航
wv.SetData "name", "张伟"
' 用户点击链接导航到新页面
' → DOMContentLoaded 触发自动重建
' → "name" 键的值 "张伟" 自动重新应用完整示例
以下示例展示 BindUI/BindData/SetData 的完整协作:
VB6 代码
Dim WithEvents wv As cWebView2Host
Private Sub Form_Load()
Set wv = New cWebView2Host
wv.Initialize Me.hWnd, App.Path & "\www"
End Sub
Private Sub wv_Ready()
' === 宿主 -> UI 数据绑定 ===
wv.BindData "name", "#name-input", "value"
wv.BindData "name", "#name-preview", "textContent"
wv.BindData "enabled", "#toggle-enabled", "checked"
wv.BindData "enabled", "#settings-panel", "visible"
wv.BindData "avatar", "#avatar", "src"
wv.BindData "count", "#msg-count", "textContent"
wv.BindData "statusOnline", "#status-dot", "class"
' === UI -> 宿主 事件绑定 ===
wv.BindUI Me, "OnNameInput", "#name-input", EventName:="input"
wv.BindUI Me, "OnToggle", "#toggle-enabled", EventName:="change"
wv.BindUI Me, "OnIncMsg", "#btn-inc"
wv.BindUI Me, "OnResetMsg", "#btn-reset"
' === 推送初始数据 ===
wv.SetData "name", "张伟"
wv.SetData "enabled", True
wv.SetData "count", 0
Me.Caption = wv.DocumentTitle
End Sub
' === UI 回调方法 ===
Public Sub OnNameInput(ByVal EventName As String, ByVal Detail As String)
Dim val As String
val = JsonValue(Detail, "value")
wv.SetData "name", val ' 回推到 UI
End Sub
Public Sub OnToggle(ByVal EventName As String, ByVal Detail As String)
' Detail 示例: {"type":"change","checked":true}
Dim checked As Boolean
checked = (InStr(Detail, """checked"":true") > 0)
wv.SetData "enabled", checked
End Sub
Public Sub OnIncMsg(ByVal EventName As String, ByVal Detail As String)
Dim count As Long
count = Val(wv.JsProp("document.querySelector('#msg-count').textContent"))
wv.SetData "count", count + 1
End Sub
Public Sub OnResetMsg(ByVal EventName As String, ByVal Detail As String)
wv.SetData "count", 0
End SubHTML 页面
<div>
<input id="name-input" type="text" />
<p>Hello, <span id="name-preview">-</span>!</p>
</div>
<div>
<label><input id="toggle-enabled" type="checkbox" /> Enable</label>
<div id="settings-panel">
<p>Settings content here</p>
</div>
</div>
<div>
<span id="msg-count">0</span> messages
<button id="btn-inc">+</button>
<button id="btn-reset">Reset</button>
</div>常见问题
❓ Q1: BindUI 回调没有被触发?
现象: 绑定了事件但 VB6 方法没有被调用。
原因:
- 宿主方法必须声明为
Public(不能用 Private) - 宿主方法签名必须完全匹配
(ByVal EventName As String, ByVal Detail As String) - 绑定必须在
wv_Ready事件或之后执行,不能在Form_Load中
解决方案:
' 正确
Public Sub OnClick(ByVal EventName As String, ByVal Detail As String)
' 错误
Private Sub OnClick(ByVal EventName As String, ByVal Detail As String) ' 不能是 Private
Public Sub OnClick() ' 参数签名不匹配❓ Q2: BindData 设置 class 属性时原有样式丢失?
现象: 使用 BindData "key", "#element", "class" 后元素原有的 CSS 类被覆盖。
原因: class 属性直接替换元素的全部 className,不是追加。
解决方案: 将完整的类名组合后 SetData:
wv.SetData "statusClass", "card " & IIf(isActive, "active", "inactive")❓ Q3: 页面导航后绑定失效?
现象: 点击链接导航到新页面后,绑定不再工作。
原因: cWebView2Host 在 DOMContentLoaded 时自动重建绑定,但如果新页面的 DOM 结构不同(选择器匹配不到元素),绑定将静默失败。
解决方案:
- 确保所有页面的 DOM ID/选择器一致
- 或在导航后重新执行绑定
❓ Q4: SetData 如何传递复杂对象?
现象: 需要将数组或对象传给 JS。
原因: SetData 的 Value 是 Variant,内部转换为 JS 字面量。复杂类型需要序列化为 JSON 字符串并使用 innerHTML 或自定义 JS 处理。
解决方案:
' 方式1:使用 innerHTML 渲染列表
wv.BindData "items", "#item-list", "innerHTML"
wv.SetData "items", "<li>Item 1</li><li>Item 2</li>"
' 方式2:直接调用 JS 函数处理 JSON
wv.JsRun "renderItems(" & jsonString & ")"最后更新: 2026-06-24