简介:网页编辑器VB版是一套面向Visual Basic开发者的富文本编辑控件及配套示例,用于在桌面应用中嵌入类似在线记事本的HTML创建、编辑与格式化能力。它基于DHTML技术,提供字体、字号、文本颜色、对齐、插入图片与链接等工具栏功能,可服务于内容管理系统、论坛、博客等需要用户输入富文本的场景。压缩包为rar格式,共99个文件,约197KB,主要由gif界面素材、cs源代码、dll组件以及xml、js、css、aspx等配置和网页资源构成,兼顾示例运行与二次开发参考。已有1046人学习下载。其中的WebTextPaneDemo演示项目展示了控件初始化、HTML输入处理及内容保存或发送等环节,便于开发者理解DHTML控件与IE渲染引擎的配合方式,并据此在VB程序中快速搭建可交互的网页编辑界面。
1. 一个能点进去打字的空白页,才是 VB 网页编辑器的入口
很多人拿到 VB 网页编辑器源码的第一反应是双击工程文件,窗体确实弹出来了,编辑区却像一张冻结的图:点不进去,光标不闪,工具栏按钮按下去毫无反应。这时候很容易误判成"代码不全"或"控件没注册"。它本质上不是浏览器外壳,而是把 IE 的编辑模式借出来,让你像用文字处理软件一样排 HTML 的桌面工具——加粗斜体、插入图片和表格、切换源码、保存成 .html,功能全藏在工具栏那几十个按钮后面。适合两类人:手上还有 VB6 或 WinForms 老项目、需要给内部系统塞一个富文本编辑能力的维护者;以及想弄明白 designMode、execCommand 这套老接口到底怎么跑起来的开发者。把它跑起来之后你会发现,按钮点下去真正生效的代码,往往只有一行。
2. 技术选型与工程结构:WebBrowser 控件加 MSHTML 的这套组合
2.1 为什么是 WebBrowser 而不是 RichTextBox
VB 里能编辑文本的控件不多,RichTextBox 算一个,但它活在 RTF 世界。存出来的内容是{\rtf1\ansi...}这种结构,想变成 HTML 得再挂一个转换器,转换出来的标签又长又脏,<span style="...">能套三四层,后续想用 CSS 统一改样式几乎无从下手。
WebBrowser 走的是另一条路。它本质上是 IE 的渲染引擎加一份 COM 自动化接口(mshtml),页面加载完之后把document.designMode设成"On",整个文档立刻变成可编辑区域,光标、选区、撤销栈、拖拽选字全都是浏览器原生给的,不用自己实现一遍。工具栏上的按钮要做的事,也只是调document.execCommand,一行命令对应一个动作。
这个选择的代价也很明确:渲染内核老。默认状态下 WebBrowser 控件会把自己伪装成 IE7,flex、grid、部分 CSS3 选择器全不认,页面上出现莫名其妙的间距时,不要先怀疑自己的样式写错了。解决办法是给进程写FEATURE_BROWSER_EMULATION注册表值,把内核拉到 IE11 模式。这段代码要放在主窗体创建、任何 WebBrowser 实例出现之前。
' VB.NET:启动时调用一次,位置放在 Sub Main 或主窗体 Load 的第一行 Private Sub EnableModernIE() Dim exeName As String = Process.GetCurrentProcess().ProcessName & ".exe" Using key As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey( "Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION") ' 11001 = IE11 边缘模式;11000 = 跟随页面里的 X-UA-Compatible key.SetValue(exeName, 11001, Microsoft.Win32.RegistryValueKind.DWord) End Using End Sub参数说明:写的是 HKCU,不需要管理员权限;键名必须和 exe 文件名完全一致,带.exe后缀。在 IDE 里调试时进程名是vb6.exe或devenv.exe,这个值对调试进程同样生效,所以如果你在 IDE 里改了这一项又发现别的 VB 程序排版变样,记得回来删掉。VB6 那边没有Process类,一般用GetModuleFileName拿到自身路径再拆出文件名,然后走RegSetValueEx,逻辑一致。
还有一种思路是干脆在 WebBrowser 里加载一个现成的 JS 富文本组件,让组件去管工具栏和 HTML 生成。这条路适合已经有前端资源的团队,但对纯 VB 项目来说等于引入了第二套技术栈,调试时要在 VB 和 JS 之间来回看,出问题定位成本翻倍。这份源码选的是原生 execCommand 路线,代码量小、依赖少,缺点是可定制的粒度粗,后面二次开发那一章会讲怎么在它上面补。
2.2 工程文件清单与各自职责
VB6 和 VB.NET 两个版本的目录组织差不多,下面按 VB6 的 .vbp 工程列,括号里是 VB.NET 版本的对应文件。
| 文件 | 类型 | 职责 |
|---|---|---|
| ProjEditor.vbp(.vbproj) | 工程文件 | 引用列表、启动对象、编译配置 |
| frmMain.frm / .frx(Form1.vb) | 窗体 | 工具栏、WebBrowser 编辑区、状态栏、源码文本框 |
| modEditor.bas(EditorModule.vb) | 标准模块 | execCommand 统一封装、选区操作、HTML 片段处理 |
| modFile.bas(FileModule.vb) | 标准模块 | 文件读写、编码转换、图片落盘与相对路径 |
| clsDoc.cls(DocWrapper.vb) | 类模块 | 对 IHTMLDocument2 的薄封装,收拢 COM 调用 |
| template/default.html | 资源 | 新建文档的初始模板,含基础 CSS |
| res/toolbar.bmp | 资源 | 工具栏按钮图,ImageList 使用 |
| config.ini | 配置 | 默认保存目录、模板路径、默认字体字号 |
关键引用有三个:Microsoft Internet Controls(WebBrowser 控件本体)、Microsoft HTML Object Library(mshtml,提供 IHTMLDocument2、IHTMLTxtRange 这些接口)、Microsoft Windows Common Controls 6.0(工具栏和状态栏)。少勾一个,代码里Dim doc As IHTMLDocument2立刻报"用户定义类型未定义"。VB.NET 版本对应的是在项目属性里勾 COM 引用,等价于在 .vbproj 里加<COMReference Include="MSHTML" />。
clsDoc 这个类模块看着多余,实际是把散落在各个按钮事件里的WebBrowser1.Document.body.innerHTML收拢成Doc.BodyHtml这样的属性。改到第三天你就会感谢这个决定,因为一旦要加"编辑前先做一次脏标签清理",只需要改一个方法,不用去工具栏的二十个 Click 事件里翻。原始工程里如果没这个类,二次开发时补一个也不亏。
资源文件的位置有个常见坑:template/default.html和config.ini一般用App.Path拼路径读取,在 IDE 里跑时App.Path指向工程目录,编译后指向 exe 目录。第一次编译出来发现模板读不到、编辑区空白,八成是忘了把资源目录一起复制到输出目录,或者干脆写了一个绝对路径,换机器就崩。
2.3 编译与第一次启动
VB6 直接打开 .vbp,把 frmMain 设为启动对象,F5 就能跑。VB.NET 版本用 msbuild 或者 IDE 编译,平台目标建议设成 x86:
# 32 位输出更稳,少数 64 位环境下 mshtml 的 COM 互操作会抛类型不匹配 msbuild ProjEditor.vbproj /p:Configuration=Release /p:Platform=x86编译成功后,输出目录里应该能看到 ProjEditor.exe、template 目录和 config.ini。双击 exe,窗口出现后停一两秒,编辑区里会出现模板里的占位文字,鼠标点进去能直接打字,按钮区变灰的那几个说明命令封装里还没接上对应方法。
判断"是否真的进入编辑模式"有个非常快的土办法:在编辑区里按 Ctrl+A 再按 Delete,如果内容被清空且光标还在,说明 designMode 生效了;如果整个页面被选中、光标消失,说明还在只读模式,去看DocumentComplete有没有执行到设置 designMode 那一行。这个动作后面排查章节还会提到,因为它是区分"初始化失败"和"命令失败"的分水岭。
3. 核心链路拆解:从 designMode 打开到保存出第一份 HTML
3.1 初始化:把一个只读页面变成可编辑文档
整个程序最关键的一次状态切换就在这两行代码里,位置在窗体的 DocumentComplete 事件。注意这个事件会因为 iframe、about:blank的多次导航而被触发不止一次,所以判断条件要写全。
' frmMain.frm —— 窗体加载,模板先读进内存 Private m_strTemplate As String Private m_blnInited As Boolean Private Sub Form_Load() m_strTemplate = LoadHtml(App.Path & "\template\default.html") ' about:blank 是最轻的初始文档,不产生网络请求 WebBrowser1.Navigate "about:blank" End Sub Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant) ' 只处理顶层文档,页面里的 iframe 完成时也会触发这个事件 If Not (pDisp Is WebBrowser1.Object) Then Exit Sub If m_blnInited Then Exit Sub m_blnInited = True Dim doc As MSHTML.IHTMLDocument2 Set doc = WebBrowser1.Document ' 这一行之前文档是只读的,之后才有光标和选区 doc.designMode = "On" ' 用 innerHTML 灌内容而不是 document.write,避免再触发一次导航 If Len(m_strTemplate) > 0 Then doc.body.innerHTML = m_strTemplate End If ' 默认样式挂在 body 上,编辑时新插入节点的继承来源就在这里 doc.body.style.fontFamily = "微软雅黑, sans-serif" doc.body.style.fontSize = "14px" doc.body.style.padding = "12px" ' 没有这一句,工具栏第一个命令很可能静默失败 WebBrowser1.Document.body.focus End Sub参数与逻辑说明:designMode有三个取值,"On"、"Off"、"Inherit",设置成"Inherit"时会跟随父文档,在顶层文档里等同于 Off,别被这个名字骗了。pDisp Is WebBrowser1.Object判断的是这次完成事件是不是顶层浏览器对象发出的,漏掉这一句,页面里只要有个 iframe,初始化逻辑就会跑两次,第二次会把用户刚输入的内容覆盖成模板。m_blnInited是第二道保险,因为某些 IE 内核版本对about:blank会连发两次完成事件。
Microsoft HTML Object Library里focus方法挂在 body 元素上,对 IHTMLDocument2 本身调用没有意义。这一句看起来可有可无,实际是后面 execCommand 能不能生效的前置条件——焦点在工具栏按钮上时,命令会作用到空选区,表现就是"点了没反应,也不报错"。
VB.NET 版本的事件签名不同,判断方式换成If sender IsNot WebBrowser1 Then Exit Sub,其余逻辑一样。如果工程里把 WebBrowser 放在 TabControl 或 Panel 里且默认隐藏,DocumentComplete 可能在控件还没显示时就触发,这时doc.body会是 Nothing,加一句If doc Is Nothing Then Exit Sub兜底。
3.2 命令层:每个按钮背后都是一条 execCommand
工具栏的每个按钮最终都落到同一个封装方法上,这样做的好处是错误处理、焦点检查只写一遍。
' modEditor.bas —— 所有无参数命令的公共入口 Public Sub ExecCmd(ByVal strCmd As String) On Error GoTo EH ' 命令发出去之前先确认编辑区有焦点 frmMain.WebBrowser1.SetFocus If Not frmMain.WebBrowser1.Document Is Nothing Then frmMain.WebBrowser1.Document.body.focus End If ' 第三个参数必须占位,部分命令对参数个数敏感 frmMain.WebBrowser1.Document.execCommand strCmd, False, vbNullString Exit Sub EH: MsgBox "命令执行失败:" & strCmd & vbCrLf & Err.Description, vbExclamation End Sub ' 带参数的命令单独开一个入口,避免调用方传错位置 Public Sub ExecCmdEx(ByVal strCmd As String, ByVal varValue As Variant) On Error GoTo EH frmMain.WebBrowser1.SetFocus frmMain.WebBrowser1.Document.body.focus frmMain.WebBrowser1.Document.execCommand strCmd, False, varValue Exit Sub EH: MsgBox "命令执行失败:" & strCmd & vbCrLf & Err.Description, vbExclamation End Sub按钮事件因此变得极薄,加一个按钮只需要一行:
Private Sub cmdBold_Click() ExecCmd "Bold" End Sub Private Sub cmdItalic_Click() ExecCmd "Italic" End Sub Private Sub cmdUnderline_Click() ExecCmd "Underline" End Sub常用命令和它们的产出标签对照如下,处理保存内容和做 HTML 清洗时按这张表来认标签最省事。
| 按钮 | execCommand | 第三个参数 | 典型产出 |
|---|---|---|---|
| 加粗 | Bold | 空 | <b>或<strong> |
| 斜体 | Italic | 空 | <i> |
| 下划线 | Underline | 空 | <u> |
| 超链接 | CreateLink | URL 字符串 | <a href="..."> |
| 插入图片 | InsertImage | 图片路径 | <img src="..."> |
| 插入表格 | InsertTable | 空 | <table>,行列数需自行补 |
| 有序列表 | InsertOrderedList | 空 | <ol><li> |
| 水平线 | InsertHorizontalRule | 空 | <hr> |
| 字号 | FontSize | 1 到 7 的字符串 | <font size="n"> |
| 前景色 | ForeColor | #RRGGBB | <font color="..."> |
| 格式块 | FormatBlock | 带尖括号的标签名 | <h2>、<pre>等 |
这张表里藏了两个容易翻车的点。第一个是字号:FontSize只吃 1 到 7 这七个刻度,产出的是<font size>标签,不是像素值。想要 14px、16px 这种精确字号,得先打开 CSS 输出模式:
' 让后续命令优先产出 style 属性,而不是 font 这类表现层标签 Private Sub cmdUseCss_Click() frmMain.WebBrowser1.Document.execCommand "StyleWithCSS", False, "True" End Sub打开之后ForeColor产出的是<span style="color:...">,而不再是<font color>,页面更干净,但标签嵌套层级会变多。这个开关是进程内状态,切换源码视图、重新 write 文档之后会复位,需要在初始化里重新打开一次。
第二个坑是FormatBlock的参数格式:标签名要带尖括号,写成"<h2>"才生效,写成"h2"命令返回 False 且没有任何提示。这类"返回 False 但不报错"的情况在 execCommand 里很常见,调试阶段建议在封装里加一句Debug.Print strCmd, 返回值,把返回值打出来。命令是否真的执行了、选区是否为空、标签名是否合法,全靠这个布尔值分辨,光看界面不变根本分不清是哪一种。
还有一个视觉细节要注意:连续点击加粗两次是"打开再关闭"的语义,不是叠加两层<b>。如果用户先选中一段已经加粗的文字再点加粗,浏览器会拆掉标签。做撤销功能时不能用自己记录的点击次数来推算状态,得用document.queryCommandState("Bold")去查当前选区的真实状态,按钮的高亮状态也应该跟着这个方法走。
3.3 源码视图:innerHTML、outerHTML 与 document.write 的取舍
源码视图的切换看着简单,实际是整份代码里最容易出玄学行为的地方,因为三种取内容的方式返回的东西完全不同。
' 编辑区片段,适合做自动保存草稿、字数统计 Public Function GetBodyHtml() As String GetBodyHtml = frmMain.WebBrowser1.Document.body.innerHTML End Function ' 完整文档,保存成独立 .html 文件时用这个 Public Function GetFullHtml() As String Dim doc As MSHTML.IHTMLDocument2 Set doc = frmMain.WebBrowser1.Document GetFullHtml = "<!DOCTYPE html>" & vbCrLf & doc.documentElement.outerHTML End Functionbody.innerHTML不含<head>、不含样式声明、不含文档类型,拿它保存出来的文件在浏览器里打开会丢掉所有页面级样式。documentElement.outerHTML会带上<html>和<head>,但 IE 内核输出的属性大小写、自闭合标签写法和现代浏览器有差异,<br>可能写成<br/>,属性可能全大写。做源码视图就展示这个内容,用户改完再灌回去,MSHTML 还会按自己的规则再修正一遍,改乱的标签会被它悄悄整理成合法结构。这是它的"黑匣子"特性之一,也是很多人对着源码视图改了半天发现保存出来不一样的原因。
从源码回灌设计视图,不要用body.innerHTML = 新内容,那只会替换 body 内部,head 里的样式改动全部丢失。正确做法是重建整个文档:
Private Sub ApplySource() Dim doc As MSHTML.IHTMLDocument2 Set doc = frmMain.WebBrowser1.Document ' open/write/close 会重建文档,designMode 会被重置,必须重新打开 doc.open doc.write txtSource.Text doc.close doc.designMode = "On" ' write 之后可能再触发 DocumentComplete,用标志位挡住模板灌入 m_blnInited = True WebBrowser1.Document.body.focus End Subdoc.open、doc.write、doc.close这三步是 IE 时代的文档重建三件套,效果等同于导航到一个新页面,所以designMode必然复位,不补那一行用户回到设计视图就点不进去了。doc.close之后有可能再触发一次 DocumentComplete,如果不把m_blnInited置回 True,初始化逻辑会把模板又灌一遍,用户在源码里改的内容直接蒸发。这个现象一开始表现为"切回设计视图内容变成默认模板",查起来很费时间。
如果源码里包含<script>,document.write会真的执行它。做单机编辑器问题不大,但如果这个工具将来要打开别人发来的 HTML,建议在 write 之前把 script 标签剥掉,或者把编辑区的安全级别调高。这是最容易被忽略的一处,因为编辑自己的文件时永远不会触发,只有在打开外部文件时才会暴露。
3.4 保存与读取:UTF-8 编码和 meta 声明
VB6 自带的Open ... For Output加Print #是按系统 ANSI 代码页写文件的,中文在 UTF-8 声明的页面里打开就是一片乱码。这份源码用的是 ADODB.Stream,虽然绕,但能精确控制编码。
' modFile.bas —— 统一用 ADODB.Stream 处理文本读写 Public Sub SaveHtml(ByVal strPath As String, ByVal strHtml As String) Dim stm As Object Set stm = CreateObject("ADODB.Stream") stm.Type = 2 ' adTypeText,文本模式 stm.Charset = "utf-8" ' 必须在 Open 之前设好 stm.Open stm.WriteText strHtml stm.SaveToFile strPath, 2 ' 2 = adSaveCreateOverWrite,覆盖已有文件 stm.Close Set stm = Nothing End Sub Public Function LoadHtml(ByVal strPath As String) As String Dim stm As Object Set stm = CreateObject("ADODB.Stream") stm.Type = 2 stm.Charset = "utf-8" stm.Open stm.LoadFromFile strPath LoadHtml = stm.ReadText stm.Close Set stm = Nothing End Function参数说明:Type = 2是文本模式,1是二进制模式,二进制模式下Charset无效。SaveToFile的第二个参数2表示覆盖,1表示文件已存在就报错。这套 API 在 VB6 和 VBScript 里都能用,不需要额外引用,靠 CreateObject 后期绑定即可,代价是没有智能提示、拼错属性名只在运行时暴露。
只改写入编码还不够,文件里必须有对应的 meta 声明,否则浏览器拿到 UTF-8 字节流也会按本地编码猜。保存前补一道检查:
Public Function EnsureCharset(ByVal strHtml As String) As String If InStr(1, strHtml, "charset", vbTextCompare) > 0 Then EnsureCharset = strHtml Exit Function End If ' 只在第一个 head 后面插一次,避免重复 EnsureCharset = Replace(strHtml, "<head>", _ "<head>" & vbCrLf & _ "<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"">", _ 1, 1, vbTextCompare) End FunctionVB.NET 版本不需要这么绕,File.WriteAllText(strPath, html, New UTF8Encoding(False))一行搞定,False表示不写 BOM。不带 BOM 更干净,因为某些老版本浏览器会把 BOM 当成正文内容,页面顶部莫名多出一行空白,查半天以为是 CSS 的锅。VB6 那边 ADODB.Stream 写 UTF-8 默认也不带 BOM,两边行为一致。
保存前还有一道容易被跳过的清理:把编辑过程中产生的空<span>、空<p>以及 MSHTML 插入的 占位符处理掉。不清也能用,但文件会越来越大,而且用户从 Word 粘贴进来的内容会带一大堆mso-开头的样式,直接保存出去在页面上看是正常的,实际源码里全是冗余。清理逻辑可以写在 GetFullHtml 之后、SaveHtml 之前,用正则逐条替换,这也是二次开发里最容易加值的一个位置。
4. 避坑与排查:五条反复出现的故障
4.1 初始化跑了两遍,模板把用户输入覆盖了
现象:程序启动后编辑区正常,但只要页面里加载过任何带 iframe 的模板,用户打了几个字之后内容突然跳回初始模板;或者切换源码视图再切回来,修改全部丢失。
原因:DocumentComplete是给每个框架文档都发一次的,iframe 加载完成同样触发,而初始化代码里没有判断事件来源,模板被重复灌入。about:blank在某些内核版本下也会触发两次。
解决:用pDisp Is WebBrowser1.Object判断顶层文档,再配一个m_blnInited布尔标志做二次拦截。所有会重建文档的操作(document.write、重新 Navigate、切换源码回灌)执行完之后,都要把标志位重新置为 True,否则下一次事件又会走一遍初始化。
4.2 保存出来的中文在浏览器里是乱码
现象:编辑器里显示正常,保存成 .html 双击打开,中文变成问号、方块或者一段乱码。用记事本打开另存为 UTF-8 之后再打开就正常了。
原因:VB6 的Print #按系统 ANSI 代码页写字节,而页面里声明的是 utf-8,浏览器按声明解码就错位。或者反过来,字节是 UTF-8 的,但 head 里没有 charset 声明,浏览器按本地编码猜。
解决:写入统一走 ADODB.Stream 并设Charset = "utf-8",保存前用EnsureCharset补 meta。验证方法是保存后用十六进制查看器看文件头几个字节,中文的 UTF-8 编码是三字节序列,看到E4 B8 AD这类组合就对了。
4.3 工具栏按钮点了完全没反应,也不报错
现象:按钮能点、有按下效果,编辑区毫无变化,没有任何异常弹窗。
原因:焦点不在编辑文档的 body 上,execCommand 作用到了空选区;或者当前处于源码视图,WebBrowser 已经隐藏,命令发给了不可见文档;又或者命令名拼错、参数格式不对,execCommand 返回 False 但代码没检查返回值。
解决:所有命令入口统一调用SetFocus加body.focus();在源码视图下把工具栏按钮整体禁用,而不是让它们继续发命令;封装里加Debug.Print输出命令名和返回值,先确认返回 True 再谈界面效果。FormatBlock这类需要带尖括号参数的命令,参数写错是最常见的 False 来源。
4.4 插入的图片换一台机器就变成红叉
现象:本机编辑、本机打开一切正常,把 html 发给同事或者复制到另一台机器,图片位置全是红叉,右键查看属性显示的是C:\Users\xxx\Desktop\xxx.png这种本地绝对路径。
原因:InsertImage命令把传入的字符串原样写进src,传的是本地完整路径,文件一离开这台机器就找不到。
解决:插入前把图片复制到工作目录下的images子目录,src写相对路径。文件名加时间戳前缀防重名,同一张图重复插入也不会互相覆盖。
Public Function CopyImageToWorkDir(ByVal strSrc As String) As String Dim fso As Object, strName As String Set fso = CreateObject("Scripting.FileSystemObject") strName = fso.GetFileName(strSrc) ' 时间戳前缀,避免不同目录下同名文件互相覆盖 strName = Format$(Now, "yyyymmddhhnnss") & "_" & strName If Not fso.FolderExists(m_strWorkDir & "\images") Then fso.CreateFolder m_strWorkDir & "\images" End If fso.CopyFile strSrc, m_strWorkDir & "\images\" & strName, True CopyImageToWorkDir = "images/" & strName End Function注意返回值里用的是正斜杠,浏览器对反斜杠的容忍度因内核版本而异,正斜杠是稳的。CopyFile的第三个参数 True 表示覆盖,配合时间戳前缀基本不会撞上。
4.5 换到 64 位机器排版全乱,或者启动直接报类型不匹配
现象:在老机器上排版正常的页面,新机器上 flex 不生效、圆角消失、字体大小不对;或者程序启动时抛"无法将类型为 COM 对象的实例强制转换为接口类型"。
原因:WebBrowser 默认按 IE7 兼容模式渲染,不认现代 CSS;64 位进程加载 32 位 COM 组件或引用平台不匹配时,mshtml 的接口转换会失败。
解决:编译目标设成 x86,同时写入FEATURE_BROWSER_EMULATION注册表值 11001,页面里再加一句<meta http-equiv="X-UA-Compatible" content="IE=edge">作为兜底。三项做完之后,同一份 HTML 在新旧机器上的渲染结果基本一致。另外,卸载或迁移程序时记得把注册表值一起清理,否则同名 exe 会一直沿用旧设置。
5. 二次开发:加一个代码块按钮,把验证流程固定下来
工具栏上的按钮都是"选中文字,点一下,包一层标签"的模式,照着现有代码抄一个就能跑。最容易下手的三个扩展点如下。
| 扩展点 | 位置 | 改动量 | 适合做什么 |
|---|---|---|---|
| 工具栏按钮 | frmMain 的 Toolbar 控件加按钮,写 Click 事件 | 小 | 新增格式化命令、插入固定内容 |
| 命令封装 | modEditor.bas 的 ExecCmd 系列 | 小 | 统一加日志、加权限判断、加二次确认 |
| 保存前后处理 | GetFullHtml 与 SaveHtml 之间 | 中 | HTML 清洗、图片归档、模板注入 |
以"把选中文字包成代码块"为例,FormatBlock能直接产出<pre>,但它会把整段替换成 pre,对行内选区不友好。用 MSHTML 的选区对象包一层更可控:
' 把当前选区包进带 class 的 pre,方便页面里统一挂样式 Public Sub WrapSelectionAsCode() Dim doc As MSHTML.IHTMLDocument2 Dim rng As MSHTML.IHTMLTxtRange Set doc = frmMain.WebBrowser1.Document ' selection 在文档没有焦点时是空的,这里必须先兜一次 frmMain.WebBrowser1.SetFocus Set rng = doc.selection.createRange() If Len(rng.text) = 0 Then MsgBox "先选中一段文字", vbInformation Exit Sub End If ' pasteHTML 会替换选区内容,所以要把原内容拼回去 rng.pasteHTML "<pre class=""code"">" & rng.htmlText & "</pre>" End Subrng.htmlText拿到的是选区内的原始 HTML,不是纯文本,这样用户选中的加粗、链接都会保留在代码块里。pasteHTML会先删掉选区再插入新内容,所以必须把原内容拼进去,直接传一个空<pre>会把用户的文字吃掉。样式可以写在template/default.html的<style>里,.code { background:#f6f8fa; padding:8px; }这类声明会在新文档里自动生效,不用在 VB 代码里拼内联样式。
改造之后的验证流程我一般固定走四步,缺一步都可能留下"看着好了、保存出来不对"的隐患。
第一步,按钮点下去立刻在封装里看 execCommand 的返回值,False 就不用往下测了,先查命令名、参数和焦点。
本文还有配套的精品资源,点击获取