承接上集核心引擎實現,本文聚焦於 **如何將腳本引擎整合進 WinForm 主程式**,實現 **全域物件容器 (window/document)、腳本編輯/執行 UI、示範腳本庫、部署策略**,構建一個具備「記事本+腳本擴展」能力的完整應用程式。
主視窗 (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 = "";
}
封裝主視窗功能,暴露屬性/方法給腳本,採用**內部建構函式**持有 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();
}
提供 Alert、Confirm、Prompt、BrowseFile/Folder、SetTimeout/SetInterval、視窗位置/大小屬性 (Left, Top, Width, Height, Title)、螢幕尺寸 (ScreenWidth/Height) 等 UI 互動能力。關鍵屬性 UserInteractive 控制是否允許圖形介面操作 (Service/ASP.NET 環境設 false)。
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);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();
}
}
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}");
}
}
TextBox (多行、Monospace 字型) 編輯腳本原始碼。VBAEngine 接收引擎實例,對話框確定時呼叫 engine.Compile()。ScriptVersion 屬性偵測腳本是否修改,決定是否重新整理方法列表。' 顯示當前腳本原始碼
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 檔直接複製進目標專案。VbaScriptEngine.dll,供 C#/VB.NET/F# 專案參考。本講完整展示了:
DocumentClass 封裝 → 腳本可呼叫。StandardModuleAttribute + 靜態屬性 → 腳本直接用 Window/Document。Execute(methodName) → 例外捕獲友善提示。DocumentClass/XVBAWindowObject 加成員,腳本即時可用,無需重新編譯主程式。這套架構讓應用程式擁有 **VBA 風格的二次開發能力**,適合報表自訂、工作流腳本化、自動化測試腳本等場景。