首页 > temp > 简明python教程 >
-
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; } } } |
类似的,笔者定义了Left,Top、Width和Height属性用于映射主窗体的左边位置、顶边位置,宽度和高度。
借助于这些Title、Left、Top、Width和Height属性,用户就可以在脚本中获得和设置主窗体的一些属性了。
这些属性全都是和用户互换相关的功能,因此都受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_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属性获得所有可用脚本方法的名称,然后添加到工具条的“运行脚本”的下拉菜单中,于是可以到达如下的界面效果。