使用 .NET 创建可扩展用户界面,第二部分






4.58/5 (19投票s)
从插件组件访问应用程序级别的用户界面元素。
引言
在第一部分中,我描述了一种允许您创建可在运行时加载的用户界面插件的架构。在本节中,我将描述从插件访问应用程序级别用户界面的方法。请在阅读本文之前参阅 使用 .NET 创建可扩展用户界面,第一部分。
概述
某些用户界面元素自然属于 Shell(主应用程序),不应驻留在插件中。按照惯例,状态栏应贯穿应用程序窗口的整个宽度。将状态栏放在插件中会显得奇怪且不协调。主菜单是另一个属于 Shell 应用程序的用户界面元素,但应能从插件中访问。以下两个示例展示了您可用于提供对这两个用户界面元素访问的方法。
向 Shell 添加状态栏。
在第一个示例中,我们将向 Shell 添加一个状态栏,并为插件提供对状态栏其中一个面板的引用。我们还包括一个用于将文本放置在状态栏面板中的辅助函数。
基类更改。
首先,我们将所需的代码添加到位于 PlugIn.cs 的插件基类中。
- 添加一个本地变量来保存
StatusBarPanel
。/// <summary> /// Local storage for StatusPanel /// </summary> private System.Windows.Forms.StatusBarPanel _StatusPanel;
- 接下来,添加一个属性来设置和获取
StatusBarPanel
。/// <summary> /// StatusPanel property /// </summary> public System.Windows.Forms.StatusBarPanel StatusPanel { get { return _StatusPanel; } set { _StatusPanel = value; } }
- 最后,添加一个辅助函数,插件可以调用该函数来设置
StatusBarPanel
中的文本。请注意,在分配文本之前,我们检查了_StatsPanel
是否已设置。/// <summary> /// Helper function to set StatusPanel text. /// </summary> /// <param name="Text">String to display</param> protected void ShowStatus(string Text) { if(_StatusPanel != null) { _StatusPanel.Text = Text; } }
Shell 应用程序更改
现在我们可以修改 Shell 应用程序。
- 向窗体添加一个
StatusBar
控件。 - 向
StatusBar
添加一个StatusBarPanel
。 - 在
AddPlugIn
中,将插件的StatusPanel
属性设置为您刚刚添加的StatusBarPanel
。
/// <summary>
/// Load and add a plug-in to the panel1 control
/// Also set the list box to navigate between plug-ins.
/// </summary>
/// <param name="Location">The name or path of the file
/// that contains the manifest of the assembly.</param>
/// <param name="ControlName">The name of the type to locate.</param>
private void AddPlugIn(string Location, string ControlName)
{
Assembly ControlLib;
PlugIn NewPlugIn;
// Load the assembly.
ControlLib = Assembly.LoadFrom(Location);
// Now create the plugin.
NewPlugIn = (PlugIn)ControlLib.CreateInstance(ControlName);
NewPlugIn.Location = new System.Drawing.Point(0, 0);
NewPlugIn.Dock = DockStyle.Fill;
NewPlugIn.Visible = false;
// Add it to the panel, note that its Visible property is false.
panel1.Controls.Add(NewPlugIn);
// Set up the ClickHandler
NewPlugIn.Clicked += new PlugInLib.ClickDelegate(PlugIn_Clicked);
NewPlugIn.SetMenu += new PlugInLib.SetMenuDelegate(PlugIn_SetMenu);
NewPlugIn.StatusPanel = statusBarPanel1; //<---
// Add the plugin to the listBox, listBox will use ToString to
// get the text to display.
listBox1.Items.Add(NewPlugIn);
}
插件修改
- 最后,向您的插件添加代码以显示其状态:在此,我们仅在鼠标悬停在
label1
上时显示“Over the Label”。private void label1_MouseEnter(object sender, System.EventArgs e) { ShowStatus("Over the Label"); } private void label1_MouseLeave(object sender, System.EventArgs e) { ShowStatus(""); }
向 Shell 添加菜单
让每个插件提供自己的菜单集是非常可取的。菜单应仅在插件可见时出现。这样做比前面的示例要复杂一些。此设计的首要目标是插件不应管理自己的菜单。当插件不可见时,其菜单应自动从 Shell 的主菜单中删除。
在此示例中,插件将定义其菜单,并将对它们的引用放置在基类中。然后,基类将调用 Shell 中的函数来安装或删除菜单。基类将使用其自身的可见性属性来确定是安装还是删除菜单。
基类更改。
同样,我们将从修改插件基类开始。
- 添加
SetMenuDelegate
的声明。委托声明定义了一个引用类型,该类型可用于封装具有特定签名的函数。此处,它定义了一个将由 Shell 应用程序提供以向其主菜单添加和删除项的函数。/// <summary> /// A delegate type for installing and removing menus. /// </summary> public delegate void SetMenuDelegate(bool Install, System.Windows.Forms.MenuItem[] MenuItems);
- 声明
SetMenuDelegate
委托的一个实例。Shell 应用程序将在此处放置一个引用,指向将添加和删除菜单项的函数。/// <summary> /// SetMenu /// <summary> public SetMenuDelegate SetMenu;
- 由于我们将让基类管理菜单,因此我们需要一个存储它们的地方。
/// <summary> /// Local storage for MenuItems /// <summary> private System.Windows.Forms.MenuItem[] _MenuItems;
- 并且我们需要一个属性来设置它们。
/// <summary> /// MenuItems property /// <summary> public System.Windows.Forms.MenuItem[] MenuItems { get { return _MenuItems; } set { _MenuItems = value; } }
- 重写
OnVisibleChanged
函数。这就是所有魔法发生的地方。当插件的可见性发生变化时,基类将调用 Shell,让它添加或删除菜单。在调用
SetMenu
后,我们调用base.OnVisibleChanged(e)
以便正常处理继续。如果我们不这样做,插件将永远不会收到“VisibleChanged”事件。/// <summary> /// Show/Hide our menus. /// <summary> /// <param name="e">event data</param> protected override void OnVisibleChanged(System.EventArgs e) { if((SetMenu != null) && (_MenuItems != null)) { SetMenu(this.Visible, _MenuItems); } base.OnVisibleChanged(e); }
如果您的 Shell 应用程序同时显示多个插件,您可以重写
OnEnter
和OnLeave
,仅在插件具有焦点时才显示菜单。
Shell 应用程序更改
接下来,我们修改 Shell 应用程序以处理通过 SetMenuDelegate
的调用。
- 向窗体添加一个
MainMenu
控件,并添加 Shell 应用程序所需的任何菜单。 - 添加一个用于添加和删除菜单的函数。此函数必须与插件基类中的
SetMenuDelegate
签名匹配。/// <summary> /// Adds or Deletes MenuItems to the applications main menu. /// </summary> /// <param name="Install">True - Add the menus<br /> /// False - Delete the menus</param> /// <param name="MenuItems">Array of MenuItems /// to Add or Delete</param> private void PlugIn_SetMenu(bool Install, System.Windows.Forms.MenuItem[] MenuItems) { if(Install == true) { this.mainMenu1.MenuItems.AddRange(MenuItems); } else { foreach(MenuItem m in MenuItems) { this.mainMenu1.MenuItems.Remove(m); } } }
- 在
AddPlugIn
中,将插件的SetMenu
属性设置为PlugIn_SetMenu
函数。/// <summary> /// Load and add a plug-in to the panel1 control /// Also set the list box to navigate between plug-ins. /// </summary> /// <param name="Location">The name or path /// of the file that contains the manifest of the assembly.</param> /// <param name="ControlName">The name of the type to locate.</param> private void AddPlugIn(string Location, string ControlName) { Assembly ControlLib; PlugIn NewPlugIn; // Load the assembly. ControlLib = Assembly.LoadFrom(Location); // Now create the plugin. NewPlugIn = (PlugIn)ControlLib.CreateInstance(ControlName); NewPlugIn.Location = new System.Drawing.Point(0, 0); NewPlugIn.Dock = DockStyle.Fill; NewPlugIn.Visible = false; // Add it to the panel, note that its Visible property is false. panel1.Controls.Add(NewPlugIn); // Set up the ClickHandler NewPlugIn.Clicked += new PlugInLib.ClickDelegate(PlugIn_Clicked); NewPlugIn.SetMenu += new PlugInLib.SetMenuDelegate(PlugIn_SetMenu); //<--- NewPlugIn.StatusPanel = statusBarPanel1; // Add the plugin to the listBox, listBox will use ToString to // get the text to display. listBox1.Items.Add(NewPlugIn); }
向插件添加菜单。
您无法将 MainMenu
添加到 UserControl
。您需要手动编写菜单。由于 MenuItem
构造函数重载很多,这很容易做到。
- 首先声明您的顶级菜单。
private System.Windows.Forms.MenuItem FileMenu; private System.Windows.Forms.MenuItem EditMenu;
- 在插件的构造函数中,您现在可以创建完整的菜单。
在这里,我们创建一个带有 2 个子项“新建”和“保存”的“文件”菜单,以及一个带有“剪切”、“复制”和“粘贴”的“编辑”菜单。然后,我们将顶级菜单打包到一个数组中,并将其分配给
MenuItems
属性。// Create a "File" menu FileMenu = new MenuItem("File",new MenuItem[] { new MenuItem("New",new EventHandler(menuNew_Click)), new MenuItem("Save",new EventHandler(menuSave_Click)) }); // Create an "Edit" menu EditMenu = new MenuItem("Edit",new MenuItem[] { new MenuItem("Cut",new EventHandler(menuEdit_Click)), new MenuItem("Copy",new EventHandler(menuEdit_Click)), new MenuItem("Paste",new EventHandler(menuEdit_Click)) }); // Set the MenuItems this.MenuItems = new System.Windows.Forms.MenuItem[] { FileMenu, EditMenu, };
现在,当此插件可见时,其菜单将添加到应用程序的菜单中。在菜单选择发生时生成的事件将直接触发到插件中的事件处理程序。
结论
通过添加这两种与 Shell 应用程序通信的方法,您应该拥有一个创建具有可扩展用户界面的应用程序的完整框架。
注释
在 ExtensibleUI2_demo.zip 文件中,config.xml 文件位于“Shell”应用程序的“Release”和“Debug”目录中。这些文件包含 OurControls.dll 的绝对路径名,包括驱动器号。您需要根据您的本地系统修改这些路径。