程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#發現之旅第十四講 基於動態編譯的VB.NET腳本引擎(下)

C#發現之旅第十四講 基於動態編譯的VB.NET腳本引擎(下)

編輯:關於C#

主窗體中還定義了諸如Function_New,Function_Open,Function_Save等等函數用於實現 對文檔的新建、打開文件和保存文件等處理。而工具條上的按鈕就是調用這些功能函數。定 義這些功能函數的代碼如下

/// <summary>
/// 執行新建文檔
/// </summary>
public bool Function_New()
{
     if (QuerySave())
    {
        txtEditor.Text = "";
        txtEditor.Modified = false;
        strFileName  = null;
        return true;
    }
    return  false;
}

/// <summary>
/// 執行打開文件操作
/// </summary>
public bool Function_Open()
{
    if  (QuerySave() == false)
    {
        return false  ;
    }
    using (OpenFileDialog dlg = new  OpenFileDialog())
    {
        dlg.Filter = "文本文件 (*.txt)|*.txt|所有文件|*.*";
        dlg.CheckPathExists =  true;
       if (dlg.ShowDialog(this) == DialogResult.OK)
         {
            System.IO.StreamReader reader =  new System.IO.StreamReader(
                dlg.FileName,  System.Text.Encoding.GetEncoding("gb2312"));
             txtEditor.Text = reader.ReadToEnd();
            reader.Close ();
            strFileName = dlg.FileName;
             txtEditor.Modified = false;
            return  true;
        }
    }
    return false;
}

/// <summary>
/// 執行保存文檔操作
///  </summary>
/// <returns>操作是否成功</returns>
public bool Function_Save()
{
    if (strFileName == null)
    {
        using (SaveFileDialog dlg = new  SaveFileDialog())
        {
            dlg.Filter  = "文本文件(*.txt)|*.txt|所有文件|*.*";
             dlg.CheckPathExists = true;
            dlg.OverwritePrompt =  true;
            if (dlg.ShowDialog(this) ==  DialogResult.OK)
            {
                 strFileName = dlg.FileName;
            }
             else
            {
                 return false;
            }
        }
    }
    System.IO.StreamWriter writer = new  System.IO.StreamWriter(
        strFileName, false,  System.Text.Encoding.GetEncoding( "gb2312" ));
    writer.Write (this.txtEditor.Text);
    writer.Close();
     this.txtEditor.Modified = false;
    return true;
}

/// <summary>
/// 執行另存為操作
/// </summary>
public bool Function_SaveAs()
{
    using (SaveFileDialog dlg  = new SaveFileDialog())
    {
        dlg.Filter = " 文本文件(*.txt)|*.txt|所有文件|*.*";
        dlg.CheckPathExists =  true;
        dlg.OverwritePrompt = true;
         if (dlg.ShowDialog(this) == DialogResult.OK)
        {
             strFileName = dlg.FileName;
             System.IO.StreamWriter writer = new System.IO.StreamWriter(
                 strFileName, false, System.Text.Encoding.GetEncoding ("gb2312"));
            writer.Write(this.txtEditor.Text);
            writer.Close();
             this.txtEditor.Modified = false;
            return true;
        }
    }
    return false;
}

/// <summary>
/// 執行全選操作
/// </summary>
public void Function_SelectAll()
{
    txtEditor.SelectAll ();
}

/// <summary>
/// 執行剪切操作
///  </summary>
public void Function_Cut()
{
     txtEditor.Cut();
}

/// <summary>
/// 執行復制操作
/// </summary>
public void Function_Copy()
{
     txtEditor.Copy();
}

/// <summary>
/// 執行粘帖操作
/// </summary>
public void Function_Paste()
{
     txtEditor.Paste();
}
/// <summary>
/// 執行刪除操作
/// </summary>
public void Function_Delete()
{
     txtEditor.SelectedText = "";
}

文檔對象

筆者袁某在主窗 體中定義了一個DocumentClass的套嵌類型,該類型就是腳本中使用的document全局對象的類 型,其代碼為

/// <summary>
/// 腳本中使用的文檔對象類型, 本對象是對 frmMain 的一個封裝
/// </summary>
public class  DocumentClass
{
    /// <summary>
    /// 初始化對 象
    /// </summary>
    /// <param  name="frm"></param>
    internal DocumentClass(frmMain frm)
    {
        myForm = frm;
    }

     internal frmMain myForm = null;
    /// <summary>
    /// 設置或返回文檔文本內容
    /// </summary>
     public string Text
    {
        get
         {
            return myForm.txtEditor.Text;
         }
        set
        {
             myForm.txtEditor.Text = value;
        }
    }
    /// <summary>
    /// 向文檔添加文本內容
     /// </summary>
    /// <param name="text">要添加的文本內 容</param>
    public void AppendText(string text)
     {
        myForm.txtEditor.AppendText(text);
    }
    /// <summary>
    /// 設置獲得文檔中選擇的部分
     /// </summary>
    public string Selection
     {
        get { return myForm.txtEditor.SelectedText; }
         set { myForm.txtEditor.SelectedText = value; }
    }
    /// <summary>
    /// 文檔文件名
    ///  </summary>
    public string FileName
    {
         get { return myForm.FileName; }
    }
    ///  <summary>
    /// 新建文檔
    /// </summary>
    /// <returns>操作是否成功</returns>
    public  bool New()
    {
        return myForm.Function_New();
    }
    /// <summary>
    /// 保存文檔
     /// </summary>
    /// <returns>操作是否成功 </returns>
    public bool Save()
    {
         return myForm.Function_Save();
    }
    ///  <summary>
    /// 文檔另存為
    ///  </summary>
    /// <returns>操作是否成功</returns>
    public bool SaveAs()
    {
        return  myForm.Function_SaveAs();
    }
    /// <summary>
    /// 打開文件
    /// </summary>
    ///  <returns>操作是否成功</returns>
    public bool Open()
    {
        return myForm.Function_Open();
    }
    /// <summary>
    /// 剪切
    ///  </summary>
    public void Cut()
    {
         myForm.Function_Cut();
    }
    /// <summary>
    /// 復制
    /// </summary>
    public void  Copy()
    {
        myForm.Function_Copy();
     }
    /// <summary>
    /// 粘帖
    ///  </summary>
    public void Paste()
    {
         myForm.Function_Paste();
    }
    ///  <summary>
    /// 刪除
    /// </summary>
     public void Delete()
    {
         myForm.Function_Delete();
    }
    /// <summary>
    /// 全選
    /// </summary>
    public void  SelectAll()
    {
        myForm.Function_SelectAll();
    }

}//public class DocumentClass

DocumentClass 類型表示記事本當前處理的文檔對象。

創建全局對象容器

為了在腳本代碼中 使用document,window這樣的全局對象,筆者得創建一個類型為GlobalObject的全局對象容 器,定義該類型的代碼如下

namespace MyVBAScript.Global
{
     /// <summary>
    /// 定義VB.NET腳本使用的全局對象容器類型
    /// </summary>
     [Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute()]
     public class GlobalObject
    {
        internal static  XVBAWindowObject myWindow = null;
        ///  <summary>
        /// 全局的 window 對象
         /// </summary>
        public static XVBAWindowObject  Window
        {
            get { return  myWindow; }
        }

        internal static  frmMain.DocumentClass myDocument = null;
        ///  <summary>
        /// 全局 document 對象
         /// </summary>
        public static  frmMain.DocumentClass Document
        {
             get { return myDocument; }
        }
    }
}

在這個腳本全局對象容器類型中,筆者添加了StandardModuleAttribute特性 ,並定義了Window和Document兩個靜態屬性。未來我們將腳本要操作的window對象和 document對象設置到這兩個靜態屬性中。

和其他類型不一樣,筆者設置該類型的名稱 空間為MyVBAScript.Global,這樣是為了將全局對象和其他類型區別開來,減少VB.NET編譯 器的工作量。

初始化腳本引擎

在窗體的加載事件中我們初始化腳本引擎,其 代碼為

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);
     // 加載演示腳本文本
    string strDemoVBS =  System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath,  "demo.vbs");
    if (System.IO.File.Exists(strDemoVBS))
     {
        System.IO.StreamReader reader = new  System.IO.StreamReader(strDemoVBS, System.Text.Encoding.GetEncoding ("gb2312"));
        string script = reader.ReadToEnd();
         reader.Close();
        myVBAEngine.ScriptText =  script;
        if (myVBAEngine.Compile() == false)
         {
            this.txtEditor.Text = "編譯默認腳本錯 誤:"r"n" + myVBAEngine.CompilerOutput;
        }
         // 刷新腳本方法列表
        this.RefreshScriptMethodList();
    }
}

這裡程序首先創建了一個名為myVBAEngine的腳本引擎對 象,然後向它的VBCompilerImports列表添加了全局對象容器類型所在的名稱空間 MyVBAScript.Global。

然後程序創建一個文檔對象,並填充VB腳本引擎用的全局對象 容器,設置它的Window和Document的屬性值。

程序試圖加載應用程序所在目錄下的 demo.vbs文件中的內容作為默認加載的腳本代碼,若成功加載並編譯成功則調用 RefreshScriptMethodList來更新用戶界面中的可用腳本方法列表,定義 RefreshScriptMethodList函數的代碼如下

/// <summary>
///  刷新“運行腳本”按鈕的下拉菜單項目,顯示所有可以執行的腳本方法名稱
/// </summary>
private void RefreshScriptMethodList()
{
    // 情況腳本方法名稱列表
     this.btnRunScript.DropDownItems.Clear();
    // 獲得腳本引擎中所有的腳 本方法名稱
    string[] names = myVBAEngine.ScriptMethodNames;
    if (names != null && names.Length > 0)
     {
        // 將腳本方法名稱添加到“運行腳本”的下拉菜單項 目中
        foreach (string name in names)
         {
            ToolStripMenuItem item = new  ToolStripMenuItem();
            item.Text = name;
             item.Click += new EventHandler(ScriptItem_Click);
             btnRunScript.DropDownItems.Add(item);
        }
        myStatusLabel.Text = "共加載 " + names.Length + "  個腳本方法";
    }
    else
    {
         ToolStripMenuItem item = new ToolStripMenuItem();
         item.Enabled = false;
        item.Text = "沒有加載任何腳本方 法";
        btnRunScript.DropDownItems.Add(item);
         myStatusLabel.Text = "沒有加載任何腳本方法";
    }
}

這個函數的功能是,使用腳本引擎的ScriptMethodNames屬性獲得所有可用腳 本方法的名稱,然後添加到工具條的“運行腳本”的下拉菜單中,於是可以到達 如下的界面效果。

編輯腳本

工具條上有一個“編輯腳本”的按鈕,該按鈕是點擊事件處 理過程為

/// <summary>
/// 編輯腳本按鈕事件處理
///  </summary>
/// <param name="sender"></param>
///  <param name="e"></param>
private void btnLoadScript_Click (object sender, EventArgs e)
{
    //顯示腳本文本編輯對話框
    using (dlgEditScript dlg = new dlgEditScript())
    {
        int VersionBack = myVBAEngine.ScriptVersion;
         dlg.VBAEngine = this.myVBAEngine;
        dlg.ShowDialog (this);
        if( VersionBack != myVBAEngine.ScriptVersion )
        {
            // 若腳本引擎內容發生改變則 刷新腳本方法下拉菜單項目
            RefreshScriptMethodList ();
        }
    }
}

這段代碼中使用了腳 本引擎的ScriptVersion屬性,腳本引擎中每進行一次編譯時都會更新因此ScriptVersion屬 性,因此比較該屬性可以判斷腳本引擎中當前執行的腳本代碼是否修改過。【袁永福原創, 轉載請注明出處】

dlgEditScript是一個腳本代碼編輯對話框,其用戶界面如下

運行腳本

該文本編輯器中,只能點擊工具條的“運行腳本”的下拉菜單的某個項目 才能運行腳本方法。在屬性該下拉菜單的RefreshScriptMethodList中為每個菜單項目的點擊 事件綁定了ScriptItem_Click方法,該ScriptItem_Click代碼為

///  <summary>
/// 運行腳本的下拉菜單項目點擊事件處理
///  </summary>
/// <param name="sender"></param>
///  <param name="args"></param>
private void ScriptItem_Click (object sender, System.EventArgs args)
{
    ToolStripMenuItem  item = (ToolStripMenuItem)sender;
    try
    {
         // 調用腳本執行指定名稱的腳本方法
         myVBAEngine.Execute(item.Text, null, true);
    }
    catch  (Exception ext)
    {
        System.Console.WriteLine("執 行腳本 " + item.Text + " 錯誤:" + ext.ToString());
         MessageBox.Show(this, "執行腳本 " + item.Text + " 錯誤:" +  ext.Message);
    }
}

ScriptItem_Click方法中,首先獲得 用戶點擊的菜單項目,然後調用腳本引擎的Execute方法來執行腳本,菜單項目顯示的文本就 是腳本方法的名稱。

演示用腳本代碼說明

程序目錄下有一個demo.vbs的文本 文件,該文件內容就是演示實用的VBA.NET腳本。該腳本代碼為

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

sub ShowText()
     window.alert( document.text )
end sub

sub MoveWindow()
    window.left = 100
end sub

sub ShowFileName()
    window.alert( "當前文件名為:" & document.FileName )
end  sub

sub AniMoveWindow()
    window.left = window.left -  10
    if( window.left > 10 )
         window.SetTimeout( 500 , "AniMoveWindow" )
    end if
end  sub

dim Rate as double
sub 模擬顯示正铉曲線()
     Rate = Rate + 0.1
    if( Rate > 50 )
         exit sub
    end if
    dim strText as new string( " "  , 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 )
        TitleCount = 0
         exit sub
    end if
    window.Title = strTitle.SubString(  strTitle.Length - TitleCount ,   TitleCount )
     window.SetTimeOut( 100 , "在標題欄顯示移動字幕")
end sub

這 裡說明一下“模擬顯示正铉曲線”這個腳本方法,首先定義一個Rate的全局變量 作為計數器,每執行一次該計數器加一,若超過50則退出方法,腳本中使用sin函數計算出空 白字符串的長度生成一個空白字符串,然後使用文檔對象的AppendText方法向當前編輯的文 檔添加空白字符和結尾字符,這裡腳本調用window對象的SetTimeout方法來延期調用這個腳 本方法自己。於是這個腳本方法每隔100毫秒執行一次,並使用文本模擬顯示正铉曲線,若顯 示了50次則停止執行。

這樣袁某就完成了一個簡單的文本編輯器程序,而且該程序能 使用VBA.NET腳本引擎來擴展功能,能方便的進行二次開發。

部署腳本引擎

在 實際開發中,開發人員可以將XVBAEngine等C#代碼拷貝到應用程序中即可添加腳本功能,也 可以將修改本C#工程的屬性使其單獨編譯成一個DLL然後供其他.NET程序使用。部署起來非常 方便。

小結

在本次課程中,筆者使用動態編譯技術實現了VBA.NET腳本引擎, 目前很多商業軟件,比如OFFICE,VS.NET等等都具有二次開發用的VBA腳本技術,使用本課程 介紹的知識我們也可以為應用系統配置功能強大的腳本引擎,這能比較大的提高應用系統的 靈活性。腳本引擎技術是一個非常實用的軟件開發技術,值得推廣。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved