Skip to content

从twinBASIC驱动Monaco

结合前面教程所有内容的案例研究:一个包含两个CefBrowser控件的窗体——左侧是Microsoft Monaco编辑器,右侧是实时HTML预览。用户输入时,Monaco将编辑的源代码发送给twinBASIC,后者将其镜像到预览面板。

完整项目以示例1b——Chromium Embedded Framework示例的形式在新项目对话框中提供(窗体示例3)。

架构

编辑器作为本地Web应用在虚拟主机名下运行;预览面板通过NavigateToString接收原始HTML。

运行时版本要求

Monaco使用较旧Chromium版本中不存在的现代JavaScript功能。示例在启动时检查并警告加载的运行时是否太旧:

vb
If WebView.CefMajorVersion < 109 Then
    MsgBox "Sorry, Monaco is not supported by this old version of CEF."
End If

实际上这意味着本教程需要v109v145——v49缺少Monaco依赖的JavaScript API。参见入门了解如何选择正确的包引用。

设置编辑器资源

Monaco编辑器是一个约2MB的JavaScript、CSS和字体文件集合。将它们放入项目的 Resources 子文件夹——命名为 MONACO_DEMO——连同 index.html和一个小的引导 script.js托管本地Web资源教程描述了布局。

页面本身是一个 <div id='container'> 加上监听宿主初始内容消息的引导脚本:

html
<!DOCTYPE html>
<html>
    <head>
        <script src="/vs/loader.js"></script>
        <script src="/script.js"></script>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div id="container"></div>
    </body>
</html>
js
window.chrome.webview.addEventListener('message', (event) => {
    let initialHTML = event.data;

    require.config({ paths: { 'vs': 'https://monaco.example/vs' } });
    require(["vs/editor/editor.main"], () => {
        let editor = monaco.editor.create(document.getElementById('container'), {
            value: initialHTML,
            language: 'html',
            theme: 'vs-dark',
            minimap: { enabled: false }
        });

        editor.onDidChangeModelContent(() => {
            // Inform the host of every edit.
            window.chrome.webview.postMessage(editor.getValue());
        });
    });
});

BASIC端

在窗体上放置两个 CefBrowser 控件——WebView(编辑器)和 WebViewPreview(渲染器)。Ready 处理程序部署资源、注册虚拟主机并导航:

vb
Private localPath As String

Private Sub WebView_Ready() Handles WebView.Ready
    localPath = Environ$("USERPROFILE") & "\Documents\tbMonacoDemo"
    CopyResourcesFolderContentsToLocalPath "MONACO_DEMO", localPath

    WebView.SetVirtualHostNameToFolderMapping _
        "monaco.example", localPath & "\"
    WebView.Navigate "https://monaco.example/index.html"
End Sub

CopyResourcesFolderContentsToLocalPath托管本地Web资源中的辅助过程。)

两个控件共享单个辅助浏览器进程——第一个到达ReadyCefBrowser启动它,第二个附加到现有进程。这种共享使双面板模式的成本很低。

推送初始内容

Monaco加载完成后,引导脚本监听包含用于填充编辑器的HTML的 message 事件。在编辑器的NavigationComplete后发送该消息:

vb
Private Sub WebView_NavigationComplete( _
        ByVal IsSuccess As Boolean, ByVal WebErrorStatus As Long) _
        Handles WebView.NavigationComplete

    If WebView.DocumentURL <> "https://monaco.example/index.html" Then Exit Sub

    Dim initialHTML As String = _
        StrConv(LoadResData("initial-editor-html.html", "MONACO_DEMO"), vbFromUTF8)

    WebView.PostWebMessage(initialHTML)
    WebViewPreview.NavigateToString(initialHTML)
End Sub

LoadResData返回资源字节;StrConv(..., vbFromUTF8) 解码它们。PostWebMessage将字符串传递给Monaco的 message 监听器;NavigateToString用相同文本渲染HTML来填充预览面板。

顶部的 If 守卫很重要——NavigationComplete每次导航都会触发,包括内部Monaco资源加载。仅在导航到 index.html 时填充编辑器。

实时预览

Monaco中的每次按键触发其 onDidChangeModelContent 回调,该回调将新内容 postMessage 回BASIC。这以JsMessage事件到达——直接送入预览:

vb
Private Sub WebView_JsMessage(ByVal Message As Variant) Handles WebView.JsMessage
    WebViewPreview.NavigateToString(Message)
End Sub

就是这样——预览面板在每次编辑时重新渲染。

检测缺失的运行时

相当一部分用户将在未安装CEF运行时ZIP的机器上运行应用程序。Error事件报告此情况时附带控件搜索的确切路径:

vb
Private Sub WebView_Error(ByVal code As Long, ByVal msg As String) _
        Handles WebView.Error
    MsgBox "Failed to initialize the CEF control." & vbCrLf & vbCrLf & _
           "Code: " & Hex$(code) & vbCrLf & _
           msg, vbExclamation, "CEF"
End Sub

修复方法是从github.com/twinbasic/cef-runtimes安装匹配的运行时ZIP,或者随应用程序一起提供运行时并在Create事件期间将EnvironmentOptions.BrowserExecutableFolder指向它。安装路径和ZIP文件参见入门

下一步

twinBASIC及其LOGO版权为作者"韦恩"所有