C#增添了真正的动态菜单系统。利用它开发的应用程序可快速适应新的操作环境和软件补丁,并从整体上增强软件的易用性。你仅要学会的技巧就是:如何最有效地利用C#的菜单功能。
和其他菜单不同,动态菜单是在运行时添加的,这是以前从未出现过的一种新功能。不要将动态菜单和隐藏选项混为一谈,后者是因为一项特性被禁用或者不适合当前上下文,所以才暂时隐藏起来的。另外,动态菜单也不代表被严格禁用的菜单。动态菜单为应用程序赋予一个全新的面貌。
虽然动态菜单能为一个应用程序带来许多好处,但本文的重点在于如何在动态菜单的帮助下,通过插件来增添新的软件特性。这是动态菜单最常见的一种用途。不过,这个系统也很容易进行扩展,从而包括那些支持动态更新的应用程序:一旦新的特性准备就绪,就下载新版本,并应用到用户的计算机中。
.NET菜单系统
C#对主菜单和上下文菜单进行区分进行了严格区分。主菜单代表窗体顶部可见的那些菜单。这些菜单包括一些通用的元素,例如File,Edit,Help和View等。上下文菜单则通常通过单击鼠标右键唤出,当然也可能在其他情况下出现。根据定义,上下文菜单用于提供与应用程序当前状态(或上下文)对应的功能。例如,在Microsoft Word中,可用上下文菜单来编辑一个拼写错误的单词。
虽然两个菜单系统在技术上是各自独立的,但都派生于同一个根对象,也就是System.Windows.Forms.Menu。所以,生成上下文和主菜单所需的操作是大致相同的。
添加菜单项
在主菜单或者上下文菜单中,为了提供一个动态菜单元素,都必须在菜单中插入一个新的“菜单项”。这是通过父Menu类的一个函数来完成的。为了插入新菜单项,你只需新建一个MenuItem对象,并在要添加新菜单项的那个类(主菜单或者上下文菜单)中调用MenuItemCollection的Add函数。下面的代码对此进行了说明。这个简单的应用程序只提供了一个文本框和一个按钮。按钮的Click事件用于新建一个菜单项,该菜单项的名称在文本框中提供。
private void AddMenu_Click(object sender,System.EventArgs e)
{
MenuItem newItem = new MenuItem();
newItem.Text = this.textBox1.Text;
newItem.Index = this.mainMenu1.MenuItems.Count;
this.mainMenu1.MenuItems.Add(newItem);
}
代码非常直观易懂:新建一个MenuItem,将菜单文本设为textBox中提供的文本,将菜单项在菜单链中的顺序设为菜单中的最后一项,并将新菜单插入较大菜单。这样,我们就实现了对应用程序主菜单的一次动态修改。由于上下文菜单和主菜单都是从Menu基类派生的,所以可采取相同的操作在上下文菜单中插入一个菜单项。
开发一个动态系统
在现实的开发环境中,上述应用程序需要动态提供新菜单代码。为此可以有几个选择。最简单、最容易想到的做法就是通过“对象抽象”来创建MenuItem对象。然后,可随同构建好的程序集中的一个派生对象,动态地加载插件程序集。然后,该对象可用于生成它自己的MenuItem对象,并随即将其加载到菜单中。.
采用这种方法,可简化代码与对象的关联,而且可避免使用大量回调函数。由于篇幅所限,本文不准备详细讲述这一过程所涉及的步骤(这些步骤主要和“通用语言运行时反射”以及“程序集动态执行”有关,和动态菜单则没有多大关系)。下面只提供了对这些步骤的一个简要说明:
- 定位程序集DLL——在一个目录中查找要添加的新文件,或者要求用户指定目标路径以便加载插件。
- 与插件API协商——最容易的做法就是发布一个必须在远程DLL中出现的接口。只要存在从这个接口派生的任何类,就表明是一个合格的插件。
- 定位DLL——使用LoadFrom方法,通过.NET Assembly类来动态加载程序集。
- 验证插件状态——确保程序集中的一个类支持你定义好的接口。同样能利用反射来实现。
- 实例化一个类实例——加载了程序集后,就可用Assembly类的CreateInstance方法在支持你的接口的类中创建对象的一个新实例。
- 获取MenuItem——只需在你要求的接口中,规定一个函数必须支持GetMenuItem方法。那个方法允许类返回一个提供了所有功能的MenuItem对象。
- 添加MenuItem——使用前面已经展示的代码,就可完成最后一步的操作。