首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 發現之旅第 14 講:基於動態編譯的 VB.NET 腳本引擎實戰 (下) — 主程式整合與腳本互動

     編輯:關於C#

承接上集核心引擎實現,本文聚焦於 **如何將腳本引擎整合進 WinForm 主程式**,實現 **全域物件容器 (window/document)、腳本編輯/執行 UI、示範腳本庫、部署策略**,構建一個具備「記事本+腳本擴展」能力的完整應用程式。

主視窗功能方法:供腳本呼叫的宿主 API

主視窗 (frmMain) 定義一組 public 方法,對應檔案操作、編輯操作,腳本透過 document 全域物件間接呼叫:

public partial class frmMain : Form
{
    // 檔案操作
    public bool Function_New() { /* 新建: 提示儲存、清空編輯器、重置檔名 */ }
    public bool Function_Open() { /* 開啟: OpenFileDialog、讀取 GB2312 編碼文字 */ }
    public bool Function_Save() { /* 儲存: 若無檔名則另存新檔,寫入 GB2312 */ }
    public bool Function_SaveAs() { /* 另存新檔 */ }

    // 編輯操作 (直接轉發給 TextBox)
    public void Function_SelectAll() => txtEditor.SelectAll();
    public void Function_Cut() => txtEditor.Cut();
    public void Function_Copy() => txtEditor.Copy();
    public void Function_Paste() => txtEditor.Paste();
    public void Function_Delete() => txtEditor.SelectedText = "";
}

DocumentClass:腳本眼中的「文件物件模型」

封裝主視窗功能,暴露屬性/方法給腳本,採用**內部建構函式**持有 frmMain 參考:

public class DocumentClass
{
    internal DocumentClass(frmMain frm) => myForm = frm;
    private readonly frmMain myForm;

    // 文字內容
    public string Text { get => myForm.txtEditor.Text; set => myForm.txtEditor.Text = value; }
    public void AppendText(string text) => myForm.txtEditor.AppendText(text);
    public string Selection { get => myForm.txtEditor.SelectedText; set => myForm.txtEditor.SelectedText = value; }
    public string FileName => myForm.FileName; // 唯讀

    // 檔案操作轉發
    public bool New() => myForm.Function_New();
    public bool Open() => myForm.Function_Open();
    public bool Save() => myForm.Function_Save();
    public bool SaveAs() => myForm.Function_SaveAs();

    // 編輯操作轉發
    public void Cut() => myForm.Function_Cut();
    public void Copy() => myForm.Function_Copy();
    public void Paste() => myForm.Function_Paste();
    public void Delete() => myForm.Function_Delete();
    public void SelectAll() => myForm.Function_SelectAll();
}

Window 物件:XVBAWindowObject (上集已詳述)

提供 Alert、Confirm、Prompt、BrowseFile/Folder、SetTimeout/SetInterval、視窗位置/大小屬性 (Left, Top, Width, Height, Title)、螢幕尺寸 (ScreenWidth/Height) 等 UI 互動能力。關鍵屬性 UserInteractive 控制是否允許圖形介面操作 (Service/ASP.NET 環境設 false)。

全域物件容器:StandardModuleAttribute 魔法

VB.NET 編譯器支援一個隱藏特性 Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute。標記類別並定義 static 屬性,腳本即可直接使用 Window、Document 等無前綴識別碼:

namespace MyVBAScript.Global
{
    [Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute()]
    public class GlobalObject
    {
        internal static XVBAWindowObject myWindow;
        public static XVBAWindowObject Window => myWindow;

        internal static frmMain.DocumentClass myDocument;
        public static frmMain.DocumentClass Document => myDocument;
    }
}
  • 命名空間隔離 MyVBAScript.Global 減少編譯器搜尋範圍。
  • 引擎初始化時注入實例:GlobalObject.myWindow = new XVBAWindowObject(...); GlobalObject.myDocument = new DocumentClass(this);

引擎初始化與預設腳本載入 (Form_Load)

private void frmMain_Load(object sender, EventArgs e)
{
    myVBAEngine = new XVBAEngine();
    myVBAEngine.AddReferenceAssemblyByType(this.GetType()); // 參考主程式組件
    myVBAEngine.VBCompilerImports.Add("MyVBAScript.Global"); // 匯入全域物件命名空間

    // 注入全域物件實例
    MyVBAScript.Global.GlobalObject.myWindow = new XVBAWindowObject(this, myVBAEngine, this.Text);
    MyVBAScript.Global.GlobalObject.myDocument = new DocumentClass(this);

    // 載入 demo.vbs
    string demoPath = Path.Combine(Application.StartupPath, "demo.vbs");
    if (File.Exists(demoPath))
    {
        string script = File.ReadAllText(demoPath, Encoding.GetEncoding("gb2312"));
        myVBAEngine.ScriptText = script;
        if (!myVBAEngine.Compile())
            txtEditor.Text = "編譯錯誤:\r\n" + myVBAEngine.CompilerOutput;
        RefreshScriptMethodList();
    }
}

腳本方法列表 UI:動態選單綁定

private void RefreshScriptMethodList()
{
    btnRunScript.DropDownItems.Clear();
    string[] names = myVBAEngine.ScriptMethodNames;
    if (names?.Length > 0)
    {
        foreach (string name in names)
        {
            var item = new ToolStripMenuItem(name);
            item.Click += ScriptItem_Click;
            btnRunScript.DropDownItems.Add(item);
        }
        statusLabel.Text = $"共載入 {names.Length} 個腳本方法";
    }
    else
    {
        var item = new ToolStripMenuItem("無可用腳本方法") { Enabled = false };
        btnRunScript.DropDownItems.Add(item);
        statusLabel.Text = "無可用腳本方法";
    }
}

private void ScriptItem_Click(object sender, EventArgs e)
{
    string methodName = ((ToolStripMenuItem)sender).Text;
    try { myVBAEngine.Execute(methodName, null, true); }
    catch (Exception ex)
    {
        MessageBox.Show(this, $"執行腳本 '{methodName}' 失敗: {ex.Message}");
    }
}

腳本編輯對話框:dlgEditScript

  • 內含 TextBox (多行、Monospace 字型) 編輯腳本原始碼。
  • 屬性 VBAEngine 接收引擎實例,對話框確定時呼叫 engine.Compile()。
  • 主視窗透過 ScriptVersion 屬性偵測腳本是否修改,決定是否重新整理方法列表。

示範腳本庫:demo.vbs 精選

' 顯示當前腳本原始碼
Sub 顯示當前使用的腳本代碼()
    Document.Text = Window.Engine.ScriptText
End Sub

' 插入當前時間
Sub 插入當前時間()
    Document.Selection = DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss")
End Sub

' 視窗狀態資訊
Sub 屏幕狀態()
    Window.Alert("螢幕大小: " & Window.ScreenWidth & " * " & Window.ScreenHeight & vbCrLf & _
                 "視窗位置: " & Window.Left & " " & Window.Top & vbCrLf & _
                 "視窗大小: " & Window.Width & " * " & Window.Height)
End Sub

' 動畫移動視窗 (遞迴 SetTimeout)
Sub AniMoveWindow()
    Window.Left = Window.Left - 10
    If Window.Left > 10 Then Window.SetTimeout(500, "AniMoveWindow")
End Sub

' 正弦波文字動畫
Dim Rate As Double
Sub 模擬顯示正弦曲線()
    Rate = Rate + 0.1
    If Rate > 50 Then Exit Sub
    Dim strText As New String(" "c, 50 + CInt(Math.Sin(Rate) * 30))
    Document.AppendText(vbCrLf & strText & "######")
    Window.SetTimeout(100, "模擬顯示正弦曲線")
    Window.Title = Math.Sin(Rate)
End Sub

' 標題跑馬燈
Dim strTitle As String = "《C# 發現之旅》VB.NET 腳本演示 - 袁永福 2008"
Dim TitleCount As Integer
Sub 在標題欄顯示移動字幕()
    TitleCount = TitleCount + 1
    If TitleCount > strTitle.Length Then TitleCount = 0 : Exit Sub
    Window.Title = strTitle.Substring(strTitle.Length - TitleCount, TitleCount)
    Window.SetTimeOut(100, "在標題欄顯示移動字幕")
End Sub

部署策略:零依賴嵌入

  • 原始碼嵌入:將 XVBAEngine、XVBAWindowObject、GlobalObject 等 .cs 檔直接複製進目標專案。
  • 獨立 DLL:建立 Class Library 專案編譯為 VbaScriptEngine.dll,供 C#/VB.NET/F# 專案參考。
  • 無需額外安裝、註冊 COM、配置檔,純 .NET 組件部署極簡。

小結

本講完整展示了:

  1. 宿主 API 設計:主視窗功能 → DocumentClass 封裝 → 腳本可呼叫。
  2. 全域物件注入:StandardModuleAttribute + 靜態屬性 → 腳本直接用 Window/Document。
  3. 引擎生命週期:初始化 → 載入預設腳本 → 編譯 → 更新 UI 選單。
  4. 執行入口:工具列下拉選單 → Execute(methodName) → 例外捕獲友善提示。
  5. 擴展性:新增宿主功能只需在 DocumentClass/XVBAWindowObject 加成員,腳本即時可用,無需重新編譯主程式。

這套架構讓應用程式擁有 **VBA 風格的二次開發能力**,適合報表自訂、工作流腳本化、自動化測試腳本等場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved