VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > VB.net教程 >
  • C#发现之旅第十四讲 基于动态编译的VB.NET脚本引擎(2)

 

这里的ParentWindow属性表示应用系统的主窗体。

 

延时调用和定时调用

Window全局对象中,笔者使用System.Windows.Forms.Timer对象实现了延时调用和定时调用,由于定时器对象属于用户互换相关的功能,因此延时调用和定时调用受到UserInteractive属性的影响。笔者使用以下代码来实现延时调用的功能

private string strTimeoutMethod = null;

private System.Windows.Forms.Timer myTimer;

/// <summary>

/// 设置延时调用

/// </summary>

/// <param name="MinSecend">延时的毫秒数</param>

/// <param name="MethodName">调用的方法名称</param>

public void SetTimeout(int MinSecend, string MethodName)

{

    // 若不支持和用户互换则本功能无效

    if ( bolUserInteractive == false)

        return;

    if (myEngine == null)

        return;

    if (myIntervalTimer != null)

    {

        // 取消当前的演示处理

        myIntervalTimer.Stop();

    }

    strTimerIntervalMethod = null;

    if (myTimer == null)

    {

        // 若定时器不存在则创建新的定时器对象

        myTimer = new System.Windows.Forms.Timer();

        myTimer.Tick += new EventHandler(myTimer_Tick);

    }

    // 设置定时器

    myTimer.Interval = MinSecend;

    // 设置脚本方法名称

    strTimeoutMethod = MethodName;

    // 启动定时器

    myTimer.Start();

}

/// <summary>

/// 清除延时调用

/// </summary>

public void ClearTimeout()

{

    if (myTimer != null)

    {

        // 停止定时器

        myTimer.Stop();

    }

    // 清空延时调用的脚本方法名称

    strTimeoutMethod = null;

}

 

/// <summary>

/// 延时调用的定时器事件处理

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void myTimer_Tick(object sender, EventArgs e)

{

    myTimer.Stop();

    if (myEngine != null && strTimeoutMethod != null)

    {

        // 获得脚本方法

        string m = strTimeoutMethod.Trim();

        strTimeoutMethod = null;

        if (myEngine.HasMethod(m))

        {

            // 若脚本引擎中定义了该名称的脚本方法则安全的执行该脚本方法

            myEngine.ExecuteSub(m);

        }

    }

}

SetTimeout函数用于实现延时调用,它的参数为延时调用的毫秒数和脚本方法名称。在该函数中程序初始化一个名为myTimer定时器,设置它的Interval属性为指定的毫秒数,然后启动该定时器。而在myTimer的定时事件处理中程序停止myTimer定时器,然后调用脚本引擎的ExecuteSub函数运行指定名称的无参数脚本方法。使用SetTimeout只会运行一次脚本方法,在调用SetTimeout函数准备延时调用后可以调用ClearTimeout函数来立即取消延时调用。

笔者使用以下代码来实现定时调用的功能

/// <summary>

/// 定时调用使用的定时器控件

/// </summary>

private System.Windows.Forms.Timer myIntervalTimer = null;

/// <summary>

/// 定时调用的脚本方法的名称

/// </summary>

private string strTimerIntervalMethod = null;

 

/// <summary>

/// 设置定时运行

/// </summary>

/// <param name="MinSecend">时间间隔毫秒数</param>

/// <param name="MethodName">方法名称</param>

public void SetInterval(int MinSecend, string MethodName)

{

    if (bolUserInteractive == false)

    {

        // 若不能和用户互换则退出处理

        return;

    }

    // 检查参数

    if (MethodName == null || MethodName.Trim().Length == 0)

    {

        return;

    }

    if (this.myEngine == null)

    {

        return;

    }

 

    if (myTimer != null)

    {

        //取消当前的延时调用功能

        myTimer.Stop();

    }

    strTimeoutMethod = null;

 

    if (myEngine.HasMethod(MethodName.Trim()) == false)

        return;

    strTimerIntervalMethod = MethodName.Trim();

 

    if (myIntervalTimer == null)

    {

        // 初始化定时调用的定时器控件

        myIntervalTimer = new System.Windows.Forms.Timer();

        myIntervalTimer.Tick += new EventHandler(myIntervalTimer_Tick);

    }

 

    myIntervalTimer.Interval = MinSecend;

}

/// <summary>

/// 清除定时运行

/// </summary>

public void ClearInterval()

{

    if (myIntervalTimer != null)

    {

        // 停止定时调用

        myIntervalTimer.Stop();

    }

    strTimerIntervalMethod = null;

}

/// <summary>

/// 定时调用的定时器事件处理

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void myIntervalTimer_Tick(object sender, EventArgs e)

{

    if (myIntervalTimer != null)

    {

        // 设置定时调用的脚本方法名称

        strTimerIntervalMethod = strTimerIntervalMethod.Trim();

    }

    if (strTimerIntervalMethod == null

        || strTimerIntervalMethod.Length == 0

        || myEngine == null

        || myEngine.HasMethod(strTimerIntervalMethod) == false)

    {

        if (myIntervalTimer != null)

        {

            // 若没找到指定名称的脚本方法则停止定时调用

            myIntervalTimer.Stop();

        }

        return;

    }

    // 安全的执行指定名称的脚本方法

    myEngine.ExecuteSub(strTimerIntervalMethod);

}

SetInterval函数用于实现定时调用,它的参数为两次调用之间的时间间隔,以及脚本方法名称。在该函数中程序初始化一个名为myIntervalTimer的定时器,设置它的Interval属性为指定的时间间隔,然后启动该定时器。在myIntervalTimer的定时事件处理中程序调用脚本引擎的ExecuteSub函数运行指定名称的无参数脚本方法。SetInterval会无休止的定时调用脚本方法,直到调用ClearInterval函数终止定时调用。

延时调用和定时调用是相互排斥的过程,启动延时调用会停掉定时调用,而启用定时调用会停掉延时调用。

映射应用程序主窗体

Window全局对象定义了一些属性用于映射应用程序主窗体,笔者定义一个Title属性应用映射主窗体的文本,其代码如下

/// <summary>

/// 窗体标题

/// </summary>

public string Title

{

    get

    {

        System.Windows.Forms.Form frm = myParentWindow as System.Windows.Forms.Form;

        if (frm == null)

        {

            return "";

        }

        else

        {

            return frm.Text;

        }

    }

    set

    {

        System.Windows.Forms.Form frm = myParentWindow as System.Windows.Forms.Form;

        if (frm != null)

        {

            frm.Text = value;

        }

    }

}

类似的,笔者定义了LeftTopWidthHeight属性用于映射主窗体的左边位置、顶边位置,宽度和高度。

借助于这些TitleLeftTopWidthHeight属性,用户就可以在脚本中获得和设置主窗体的一些属性了。

这些属性全都是和用户互换相关的功能,因此都受UserInteractive属性控制。若ASP.NET程序和Windows Service程序使用的脚本调用这些属性将不会产生任何效果。对于WinForm程序,运行脚本前应当将主窗体设置到window全局对象的ParentWindow属性上。

显示消息框

Window全局对象还定义了一些函数用于显示一些消息对话框实现用户互换。主要代码为

/// <summary>

/// 将对象转化为用于显示的文本

/// </summary>

/// <param name="objData">要转换的对象</param>

/// <returns>显示的文本</returns>

private string GetDisplayText(object objData)

{

    if (objData == null)

        return "[null]";

    else

        return Convert.ToString(objData);

}

 

/// <summary>

/// 显示消息框

/// </summary>

/// <param name="objText">提示信息的文本</param>

public void Alert(object objText)

{

    if (bolUserInteractive == false)

        return;

    System.Windows.Forms.MessageBox.Show(

        myParentWindow,

        GetDisplayText(objText),

        SystemName,

        System.Windows.Forms.MessageBoxButtons.OK,

        System.Windows.Forms.MessageBoxIcon.Information);

}

/// <summary>

/// 显示错误消息框

/// </summary>

/// <param name="objText">提示信息的文本</param>

public void AlertError(object objText)

{

    if (bolUserInteractive == false)

        return;

    System.Windows.Forms.MessageBox.Show(

        myParentWindow,

        GetDisplayText(objText),

        SystemName,

        System.Windows.Forms.MessageBoxButtons.OK,

        System.Windows.Forms.MessageBoxIcon.Exclamation);

}

 

/// <summary>

/// 显示一个提示信息框,并返回用户的选择

/// </summary>

/// <param name="objText">提示的文本</param>

/// <returns>用户是否确认的信息</returns>

public bool ConFirm(object objText)

{

    if (bolUserInteractive == false)

        return false;

    return (System.Windows.Forms.MessageBox.Show(

        myParentWindow,

        GetDisplayText(objText),

        SystemName,

        System.Windows.Forms.MessageBoxButtons.YesNo,

       System.Windows.Forms.MessageBoxIcon.Question)

        == System.Windows.Forms.DialogResult.Yes);

}

 

/// <summary>

/// 显示一个信息输入框共用户输入

/// </summary>

/// <param name="objCaption">输入信息的提示</param>

/// <param name="objDefault">默认值</param>

/// <returns>用户输入的信息</returns>

public string Prompt(object objCaption, object objDefault)

{

    if (bolUserInteractive == false)

        return null;

    return dlgInputBox.InputBox(

        myParentWindow,

        GetDisplayText(objCaption),

        SystemName,

        GetDisplayText(objDefault));

}

 

/// <summary>

/// 显示一个文本选择对话框

/// </summary>

/// <param name="objCaption">对话框标题</param>

/// <param name="objFilter">文件过滤器</param>

/// <returns>用户选择的文件名,若用户取消选择则返回空引用</returns>

public string BrowseFile(object objCaption, object objFilter)

{

    using (System.Windows.Forms.OpenFileDialog dlg

               = new System.Windows.Forms.OpenFileDialog())

    {

        dlg.CheckFileExists = true;

        if (objCaption != null)

        {

            dlg.Title = this.GetDisplayText(objCaption);

        }

        if (objFilter != null)

            dlg.Filter = GetDisplayText(objFilter);

        if (dlg.ShowDialog(myParentWindow) == System.Windows.Forms.DialogResult.OK)

            return dlg.FileName;

    }

    return null;

}

/// <summary>

/// 显示一个文件夹选择对话框

/// </summary>

/// <param name="objCaption">对话框标题</param>

/// <returns>用户选择了一个文件夹则返回该路径,否则返回空引用</returns>

public string BrowseFolder(object objCaption)

{

    using (System.Windows.Forms.FolderBrowserDialog dlg

               = new System.Windows.Forms.FolderBrowserDialog())

    {

        if (objCaption != null)

        {

            dlg.Description = this.GetDisplayText(objCaption);

        }

        dlg.RootFolder = System.Environment.SpecialFolder.MyComputer;

        if (dlg.ShowDialog(myParentWindow) == System.Windows.Forms.DialogResult.OK)

            return dlg.SelectedPath;

        else

            return null;

    }

}

调用这些方法,脚本能显示简单的消息框,显示文件选择对话框或文件夹选择对话框以实现和用户的互换。当前这些函数都受到UserInteractive属性的控制。

这里定义了一个Alert方法用于显示一个简单的消息框,在VB中可以调用MsgBox方法来实现相同的功能,但MsgBox方法是VB运行库的方法,不受UserInteractive属性的控制,因此不建议使用,而使用Alert方法。

测试脚本引擎

脚本引擎设计和开发完毕后,袁某就可以编写应用程序来测试使用脚本引擎了。在这里笔者仿造Windows记事本开发了一个简单的文本编辑器,其用户界面如下。

 


    在一个标准的
C# WinForm项目中笔者新建一个名为frmMain的主窗体,上面放置工具条,下面放置一个名为txtEditor的多行文本框。工具条中放上新增,打开,保存,另存为等按钮并添加事件处理以实现简单文本编辑器的功能。

主窗体中还定义了诸如Function_NewFunction_OpenFunction_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类型表示记事本当前处理的文档对象。

创建全局对象容器

为了在脚本代码中使用documentwindow这样的全局对象,笔者得创建一个类型为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特性,并定义了WindowDocument两个静态属性。未来我们将脚本要操作的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脚本引擎用的全局对象容器,设置它的WindowDocument的属性值。

程序试图加载应用程序所在目录下的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属性获得所有可用脚本方法的名称,然后添加到工具条的“运行脚本”的下拉菜单中,于是可以到达如下的界面效果。

 

相关教程