VB网页编辑器源码:WebBrowser与designMode富文本实现

发布时间:2026/10/30 3:19:45
VB网页编辑器源码:WebBrowser与designMode富文本实现 简介面向Visual Basic开发者的一款网页编辑器控件适合需要在桌面应用中嵌入富文本编辑能力的程序员可用于内容管理系统、论坛、博客等场景的HTML录入与格式化。其核心依托DHTML与IE渲染引擎工具栏涵盖字体、字号、颜色、对齐、插入图片和链接等常用操作让不熟悉HTML语法的用户也能生成带格式的网页内容。压缩包共99个文件约197KB包含gif界面素材、cs与工程文件、dll组件、xml/js/css资源以及aspx示例页面结构兼顾控件源码、资源与演示程序。现有1046人学习附带的演示工程展示了控件初始化、编辑数据处理与内容保存等关键环节可作为VB富文本编辑组件的入门参考与二次开发起点也便于理解DHTML控件在传统Win应用中的集成方式。读者可据此对照控件属性与事件处理快速搭建自己的富文本输入界面源码与资源分层清晰方便按需查阅。1. 一个 VB 网页编辑器能干到哪一步先把预期摆正接手一个老后台的时候最别扭的往往不是数据库而是内容录入运营只想要一个能加粗、能插图、能实时看见样式的框你却在几个前端富文本库的版本依赖里打转。这时候一份 VB 网页编辑器的源码就很有吸引力——它不拉任何 npm 包一个 WebBrowser 控件加上designMode属性编译出来的 exe 双击就能改网页特别适合内网工具、教学演示、老系统临时补一个「所见即所得」录入页这类场景。这份资源给的是一个能跑起来的最小完整工程不是讲原理的 PPT窗体、工具栏、源码视图、文件读写都在缺什么自己往里加。它适合两种人一种是想给 VB 老系统塞进「富文本编辑」能力、又不想引入浏览器内核依赖的维护者另一种是想搞明白execCommand这套机制到底怎么运转、准备自己写个轻量编辑器的开发者。不适合指望它替代现代编辑器的人——内核是旧的CSS 支持有限这是选型时就该接受的代价。2. 技术选型与骨架WebBrowser 控件加 designMode 这套组合2.1 为什么不自己写渲染而是借 IE 的内核VB6 里做富文本编辑常见有三条路RichTextBox 控件、自己解析 HTML 用 PictureBox 画、以及塞一个 WebBrowser 控件进去。前两条我都试过RichTextBox 只能处理 RTF导出 HTML 时那堆\rtf1\ansi转换能把人逼疯自己画渲染等于重写一个排版引擎不现实。WebBrowser 控件背后是 MSHTML也就是 IE 的排版内核它天生就带两个东西真实的 CSS 渲染以及document.execCommand这一整套编辑命令。加粗、斜体、缩进、插入链接、插入表格、撤销重做几十条命令全是内核白送的你只要负责把按钮和命令名对上。这套方案的血泪经验是省下来的工作量巨大但你必须接受内核版本老、部分现代 CSS 不生效。内网工具、教学场景完全够用对外发布的站点就别想了。一个容易被忽略的细节是designMode和contentEditable的区别。前者是把整篇文档切成可编辑态适合「整个编辑区就是一个页面」的场景后者是让某个元素可编辑适合嵌在复杂页面里。这个工程走的是designMode路线因为它要做的是一个完整的网页编辑器不是页面里的一个小输入框。2.2 工程结构与模块职责VB 工程的模块划分不能太随意否则三个月后自己都找不到代码在哪。这个工程的目录大致是这样组织的文件职责改它的频率WebEditor.vbp工程文件记录窗体、模块、引用几乎不动frmMain.frm主窗体菜单、工具栏、状态栏、编辑区高UI 都在这里frmSource.frm源码视图子窗体一个多行 TextBox中mdlEditor.bas编辑器核心命令下发、状态同步、视图切换最高逻辑都在这mdlFile.bas文件打开保存、编码处理、路径拼装中mdlAssets.bas图片等资源拷贝与目录维护低clsHistory.cls撤销栈的备用实现内核 Undo 不够用时启用低res/工具栏位图与图标低这么分的核心原则是所有跟 DOM 打交道的方法只能出现在mdlEditor.bas里。窗体代码里全是Call Exec(WebEditor, Bold)这样的调用不直接碰WebEditor.Document。好处是以后要换内核比如换成 WebView2 或者某个 ActiveX 壳只需要改一个模块窗体一行不动。我见过太多 VB 工程把WebEditor.Document.body.innerHTML散落在十几个按钮的 Click 事件里最后想改都没法改。2.3 启动骨架从 Navigate 到 designMode 的正确顺序新手最容易翻车的地方在这里Form_Load里刚Navigate完就去取WebBrowser.Document结果拿到Nothing报「对象变量或 With 块变量未设置」。原因是导航是异步的文档还没加载完。正确的做法是把初始化拆成两步第二步挂在DocumentComplete事件上。 frmMain.frm —— 第一步只发起导航不碰 Document Private Sub Form_Load() WebEditor.Silent True 屏蔽脚本错误弹窗否则内网页面一报错就卡住 WebEditor.Navigate about:blank 先给个空白文档DocumentComplete 里再初始化 End Sub 第二步文档就绪后才允许进入编辑态 Private Sub WebEditor_DocumentComplete(ByVal pDisp As Object, URL As Variant) 只有顶层框架的完成事件才算数iframe 的完成事件会重复触发 If pDisp Is WebEditor.Object Then mdlEditor.EnterDesignMode WebEditor mdlEditor.BindSelection WebEditor End If End SubEnterDesignMode是编辑器真正诞生的一刻它做三件事把文档切成可编辑、清掉默认内容、定下基础字号字体。 mdlEditor.bas —— 进入编辑态 Public Sub EnterDesignMode(ByRef wb As WebBrowser) Dim doc As Object Set doc wb.Document doc.designMode On 关键开关字符串 On不是 True doc.body.innerHTML 清掉 about:blank 的残留 doc.body.style.fontFamily 宋体 doc.body.style.fontSize 14px doc.body.style.margin 8px 给编辑区一点呼吸空间 End Sub参数说明designMode只认On和Off这两个字符串写成True在某些 IE 版本上会静默失败这是老版本内核的一个玄学点。Silent True必须设否则页面里任何一个 JS 报错都会弹出 IE 的错误对话框编辑体验直接崩掉。body.style.margin只是让光标不贴着边框值可以按自己审美改。BindSelection负责绑定光标移动事件用来刷新工具栏状态下一章展开。3. 核心编辑器模块拆解一次命令从按钮到 DOM 的完整链路3.1 命令分发一张表管住所有工具栏按钮二三十个工具栏按钮如果每个都写一遍 Click 事件代码量会失控。我一般用一个「控件数组 命令映射表」的做法所有按钮共用一个 Click 事件通过Index反查对应的命令名。 mdlEditor.bas —— 按钮索引到 execCommand 命令名的映射 Public Function CommandOf(ByVal idx As Integer) As String Select Case idx Case 0: CommandOf Bold Case 1: CommandOf Italic Case 2: CommandOf Underline Case 3: CommandOf JustifyLeft Case 4: CommandOf JustifyCenter Case 5: CommandOf JustifyRight Case 6: CommandOf InsertOrderedList Case 7: CommandOf InsertUnorderedList Case 8: CommandOf Outdent Case 9: CommandOf Indent Case 10: CommandOf Undo Case 11: CommandOf Redo Case 12: CommandOf RemoveFormat Case Else: CommandOf End Select End Function frmMain.frm —— 所有工具栏按钮共用的入口 Private Sub tbMain_ButtonClick(ByVal Button As MSComctlLib.Button) Dim cmd As String cmd mdlEditor.CommandOf(Button.Index) If Len(cmd) 0 Then mdlEditor.Exec WebEditor, cmd End Sub这样加一个新按钮只在CommandOf里补一行、在工具栏设计器里加一个按钮就完事剩下的逻辑不用碰。参数说明Button.Index是控件数组的下标必须和CommandOf的Case一一对应一旦在工具栏前面插了新按钮所有索引都会后移这是这套写法唯一的脆弱点所以我会在CommandOf里留Case Else兜底返回空串让调用方直接跳过。3.2 execCommand 的三个参数与焦点陷阱真正下发命令的函数必须只有一个出口所有异常、状态同步都在这里统一处理。 mdlEditor.bas —— 唯一的命令下发出口 Public Sub Exec(ByRef wb As WebBrowser, ByVal cmd As String, _ Optional ByVal arg As Variant) Dim doc As Object On Error GoTo Fail Set doc wb.Document doc.focus 焦点不在文档里命令会静默失败这是最常见的坑 If IsMissing(arg) Then doc.execCommand cmd, False, Empty Else doc.execCommand cmd, False, arg End If SyncToolbar wb 命令执行完立刻回显状态 Exit Sub Fail: MsgBox 命令执行失败 cmd vbCrLf Err.Description, vbExclamation End SubexecCommand(command, showUI, value)三个参数的含义第一个是命令名大小写不敏感第二个是是否弹出内核自带的 UI比如CreateLink传True会弹 IE 的输入框样式丑且不可控所以一律传False自己用 VB 窗体收集参数第三个是命令的参数值Bold这类不需要参数InsertImage需要图片地址FontSize需要字号。doc.focus这行是保命的。用户点工具栏按钮时焦点会从文档跑到按钮上此时光标位置虽然还在但execCommand找不到活动选区表现就是「点了没反应也不报错」属于最难查的那类问题。3.3 设计视图与源码视图的往返同步编辑器必须能切到源码视图手改 HTML否则遇到内核渲染不出来的标签就没辙了。两个视图之间的同步方向不同代价也不同。 mdlEditor.bas —— 设计视图 - 源码只取 body简单可靠 Public Function GetHtml(ByRef wb As WebBrowser) As String GetHtml wb.Document.body.innerHTML End Function mdlEditor.bas —— 源码 - 设计视图还原时要防止脚本被执行 Public Sub SetHtml(ByRef wb As WebBrowser, ByVal html As String) Dim doc As Object Set doc wb.Document doc.body.innerHTML html doc.body.focus End Sub参数说明innerHTML拿到的是 body 内部的片段不含head所以切源码视图时要把文档头单独拼上切回来时再剥掉这部分逻辑放在mdlFile.bas的WrapDocument和UnwrapDocument里。SetHtml里没有做script标签过滤是因为这个编辑器面向的是自己人但如果要做成给别人用的工具务必在回写前把script、iframe、on*属性清洗掉否则源码视图就是一个能执行任意脚本的后门入口。另一个坑是body.innerHTML回写会丢掉head里的样式和 meta如果你在源码里手写了一段style切回来再切过去就没了。我的做法是切到源码视图时把整篇文档存进一个String变量模块级不落盘切回来时判断源码里有没有html标签决定用整篇还是只取 body。3.4 状态回显让按钮跟着光标亮起来用户把光标挪到一段加粗的文字里加粗按钮应该是按下状态。这个交互靠queryCommandState实现触发时机是文档的onselectionchange。 mdlEditor.bas —— 把光标所在位置的格式状态同步到工具栏 Public Sub SyncToolbar(ByRef wb As WebBrowser) Dim doc As Object On Error Resume Next 文档未就绪时这里会报错忽略即可 Set doc wb.Document 1 打开0 关闭其他值表示混合状态 frmMain.tbMain.Buttons(Bold).Value IIf(doc.queryCommandState(Bold), tbrPressed, tbrUnpressed) frmMain.tbMain.Buttons(Italic).Value IIf(doc.queryCommandState(Italic), tbrPressed, tbrUnpressed) frmMain.tbMain.Buttons(Underline).Value IIf(doc.queryCommandState(Underline), tbrPressed, tbrUnpressed) frmMain.tbMain.Buttons(Undo).Enabled doc.queryCommandEnabled(Undo) End Sub frmMain.frm —— 挂接光标变化事件 Private Sub WebEditor_OnSelectionChange() mdlEditor.SyncToolbar WebEditor End SubqueryCommandState返回布尔值queryCommandEnabled返回命令当前是否可用比如撤销栈空了Undo 就该置灰。这两个查询在光标没落在文档里时会抛异常所以整个过程包在On Error Resume Next里路由失败就当没这回事别让用户看见报错框。4. 文件与资源管理保存、打开与相对路径这条线4.1 目录约定为什么必须用相对路径和 assets 目录新手版编辑器最常见的做法是执行InsertImage时直接把C:\Users\xxx\Desktop\a.jpg塞进src。本机预览一切正常把 html 拷给别人就全是裂图。这类「在我这儿是好的」问题排查起来能让人后悔没早点定规范。我一般强制一个目录约定保存 html 时同目录下自动建一个assets/子目录所有插入的图片、附件都拷进去src一律写成assets/文件名。好处是拷走整个文件夹就能用不用打包。文件名冲突的处理方式是给重名文件加时间戳后缀而不是覆盖——用户往往意识不到自己插了两张同名图。 mdlAssets.bas —— 拷贝资源并返回相对路径 Public Function CopyIntoAssets(ByVal srcPath As String, ByVal htmlDir As String) As String Dim fileName As String, destDir As String, destPath As String destDir htmlDir \assets\ If Dir(destDir, vbDirectory) Then MkDir destDir 首次保存时自动建目录 fileName GetBaseName(srcPath) destPath destDir fileName 重名不覆盖加时间戳后缀避免用户误插两张同名图丢文件 If Dir(destPath) Then fileName StripExt(fileName) _ Format(Now, yyyymmddhhnnss) . GetExt(fileName) destPath destDir fileName End If FileCopy srcPath, destPath CopyIntoAssets assets/ fileName 正斜杠浏览器不挑Windows 路径也行但容易混 End Function参数说明htmlDir是当前 html 所在目录未保存过的新文档要给一个默认值比如App.Path \draft。FileCopy在目标文件已存在时会抛错所以重名判断必须放在前面这一步漏了就是一个运行时错误。返回值用正斜杠是习惯问题assets/a.jpg和assets\a.jpg浏览器都认但正斜杠在后续做路径拼接时更省心。4.2 保存用 ADODB.Stream 写 UTF-8 才不乱码VB6 原生的Open ... For Output默认按 ANSI 写文件中文在别人的机器上打开就是一堆问号。这个坑几乎每个 VB 项目都会踩一次。稳的做法是用ADODB.Stream指定 charset。 mdlFile.bas —— 保存 htmlUTF-8 无 BOM 之外的编码话题都在这 Public Sub SaveHtml(ByVal path As String, ByVal bodyHtml As String, ByVal title As String) Dim st As Object Set st CreateObject(ADODB.Stream) st.Type 2 1 二进制2 文本写 html 用 2 st.Charset utf-8 st.Open st.WriteText WrapDocument(bodyHtml, title) st.SaveToFile path, 2 2 adSaveCreateOverWrite直接覆盖 st.Close Set st Nothing End Sub 把 body 片段补成一份完整、带编码声明的文档 Private Function WrapDocument(ByVal bodyHtml As String, ByVal title As String) As String WrapDocument !DOCTYPE html vbCrLf _ htmlheadmeta charsetutf-8 vbCrLf _ title title /title/head vbCrLf _ body bodyHtml /body/html End Function参数说明Type 2是文本模式配Charset utf-8SaveToFile的第二个参数2对应adSaveCreateOverWrite1是遇到同名文件就报错。meta charsetutf-8这行不能省否则浏览器打开时可能按本地编码猜中文又乱。注意一点ADODB.Stream写出的 UTF-8 通常带 BOM 头。对现代浏览器没影响但如果你的 html 要被别的程序按纯文本解析BOM 会变成文件开头的三个不可见字符引发莫名其妙的「第一行多了个怪符号」。介意的话就改用二进制模式手写字节数组或者读回来时手动裁掉前三个字节。4.3 打开把 head 一起读回来打开比保存麻烦一点因为要处理「打开一个别人写的完整 html」和「打开自己存的草稿」两种情况。 mdlFile.bas —— 读取 html 并拆成 head 与 body 两部分 Public Function LoadHtml(ByVal path As String, ByRef bodyOut As String, _ ByRef titleOut As String) As Boolean Dim st As Object, all As String Dim p1 As Long, p2 As Long On Error GoTo Fail Set st CreateObject(ADODB.Stream) st.Type 2 st.Charset utf-8 st.Open st.LoadFromFile path all st.ReadText(-1) -1 adReadAll一次读完 st.Close Set st Nothing p1 InStr(1, all, body, vbTextCompare) If p1 0 Then 没有 body 标签整篇当片段处理 bodyOut all Else p1 InStr(p1, all, ) 1 p2 InStr(p1, all, /body, vbTextCompare) If p2 0 Then p2 Len(all) 1 bodyOut Mid$(all, p1, p2 - p1) End If titleOut ExtractTitle(all) 从 title 里抠抠不到就用文件名 LoadHtml True Exit Function Fail: LoadHtml False End Function参数说明ReadText(-1)里的-1是adReadAll读到流末尾Mid$的起始位置要 1 跳过长度用p2 - p1而不是p2 - p1 1因为结束位置指向/body的第一个字符。这套字符串切分当然不如正则严谨但对自家产出的 html 够用如果用户会打开外部页面建议把body的查找范围限制在/head之后否则注释里出现body就会切错位置。5. 常见问题排查五个把我按在椅子上半天的问题5.1 点按钮完全没反应也不报错现象程序跑起来编辑区能打字光标也闪但点「加粗」没有任何效果断点打进去Exec也确实执行了execCommand也没抛异常。原因焦点被按钮抢走了。工具栏按钮点击后成为活动控件document里没有活动选区execCommand找不到作用对象直接静默返回。解决Exec函数第一行加doc.focus把焦点抢回文档。如果还不行检查是不是在Exec里先做了MsgBox之类会再次夺焦点的操作MsgBox关掉之后焦点不会自动回到文档必须再补一次focus。5.2 一打开就弹「对象变量未设置」现象Form_Load里写WebEditor.Document.body.innerHTML 程序一启动就弹错误框。原因Navigate是异步的Form_Load执行完时文档还没加载好Document是Nothing。解决把所有跟Document相关的初始化搬到WebEditor_DocumentComplete事件里并且用If pDisp Is WebEditor.Object Then判断是不是顶层框架完成。漏了这个判断的话页面里每有一个 iframe初始化就会重复跑一次表现是编辑区内容被清空好几次。5.3 保存的中文网页在别人电脑上全是乱码现象自己机器上打开正常发到同事那边标题和正文全是问号或者方块。原因用Open path For Output As #1这种方式写文件编码跟随系统区域设置是 ANSI对方区域不同就解不出来。解决统一用ADODB.Stream配Charset utf-8同时在 html 的head里写死meta charsetutf-8。两边都对了才稳只改一边有时候看着正常换个浏览器又乱。5.4 源码视图改完切回来样式全丢现象在源码视图里加了一段style或者link relstylesheet切回设计视图发现样式没生效再切回源码视图那段代码已经不见了。原因剪切同步只用body.innerHTML只在 body 内部搬运head 里的东西根本没参与往返。解决切到源码视图时保存整篇文档字符串含 head切回来时先判断源码里有没有html标签有就整篇写回用document.write或者重建文档没有就当片段塞进body.innerHTML。整篇写回会触发DocumentComplete重新进入编辑态注意别在事件里递归调用自己。5.5 插入的图片换台机器就裂了现象本机编辑的时候图片显示正常把 html 拷到 U 盘或者发出去图片全变成裂图占位。原因InsertImage的src是绝对路径file:///C:/...指向的是本机磁盘。解决插入时先FileCopy到 html 同级的assets/目录再把相对路径assets/xxx.jpg传给execCommand。顺带说一句execCommand(InsertImage, ...)在某些内核版本下会自作主张加上file:///前缀所以插完最好再从 DOM 里把src读出来校正一次这个属于内核行为不一致只能兼容不能讲理。6. 二次开发把它改成你能真正用起来的编辑器拿到这份工程之后我建议先别急着加功能先做两件事把内核版本顶上去把命令表抽出来。第一件事是绕过 IE7 兼容模式。WebBrowser 控件默认可能跑在兼容视图里flex、grid、border-radius全废编辑出来的页面到自己手里看着还行一上线就散架。做法是往注册表的FEATURE_BROWSER_EMULATION里给自己 exe 名字登记一个模式值11001对应 IE11 标准模式。 mdlEditor.bas —— 只在程序启动时跑一次写 HKCU 不需要管理员权限 Public Sub SetBrowserEmulation(ByVal exeName As String, Optional ByVal mode As Long 11001) Dim sh As Object Set sh CreateObject(WScript.Shell) sh.RegWrite HKCU\Software\Microsoft\Internet Explorer\Main\FeatureControl\ _ FEATURE_BROWSER_EMULATION\ exeName, mode, REG_DWORD End Sub关键在于exeName必须是带.exe后缀的完整程序名写在 IDE 里调试时用的是vb6.exe写错了完全没效果而且不报错——这个坑我踩过一次对着注册表找了两个小时。第二件事是把CommandOf那张Select Case换成配置文件或者常量数组。命令表一旦可配置加按钮、改快捷键、做自定义工具栏就是纯数据操作不用重新编译。我一般把命令名、显示文字、图标索引、快捷键、是否需要参数这五个字段放进一个Type数组里初始化时遍历生成工具栏比在设计器里一个个拖控件快得多。改完之后怎么验证不要只看「点得动」。我的检查清单是四步新建一篇文档输入中英混排加三段不同格式保存成 utf-8关掉程序双击那个 html看中文和样式是否都在把 html 连同assets/目录拷到另一台机器打开看图是否裂最后在源码视图里手写一个table和一段style来回切两次看有没有被吃掉。四步都过了才算这次改造没白做。顺手能加的小改进还有两个一是给SetHtml加 sanitize把script、iframe、on*属性过滤掉避免源码视图成为注入入口二是把撤销从内核的Undo换成自己用clsHistory维护的栈内核的撤销栈在切换视图时会被清空用户改了一大段再切源码回来想撤销就来不及了。从那以后我每次改这类老工程的编辑器模块都强制先跑一遍上面那四步验证尤其是「拷到另一台机器」这一步因为它总能替我把路径和编码的问题提前抖出来。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询