65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (32投票s)

2002年11月18日

4分钟阅读

viewsIcon

208597

downloadIcon

728

一种通过插件组件扩展程序用户界面的架构。

引言

很多时候,在程序部署后对其用户界面(UI)进行扩展或增强是很有价值的。通常这意味着需要重新部署整个应用程序。本文档描述了一种“插件”架构,允许随时扩展 UI。具有可扩展 UI 的程序的一个例子是 Microsoft 管理控制台(MMC)及其相关的管理单元。

概述

在考虑此架构之前,您的程序必须满足一个主要要求。

  • UI 插件之间应完全没有交互。这并不意味着它们不能共享公共数据结构或业务对象,但每个 UI 插件都不应尝试直接调用其他插件。

在此架构中,所有 UI 元素都包含在一组基于 System.Windows.Forms.UserControl 类的插件中。所有插件都在配置文件中描述,并在运行时加载。通过创建新插件并将相应的条目添加到配置文件中,即可实现 UI 的扩展。

此架构的一些优点是:

  • 独立开发不同的 UI 元素。例如,如果您正在开发一个个人信息管理器(PIM),一个人可以负责“约会/日历” UI,而另一个人可以负责“联系人” UI。
  • 应用程序控制。您可以根据用户的姓名、用户的角色或购买的选项来限制应用程序的功能。
  • 您可以随时添加新的 UI 元素。在上面的 PIM 示例中,您可以在应用程序分发后添加一个“日记” UI。

该架构包含 3 个部分:

  1. 一个“shell”应用程序,负责加载和导航插件。
  2. 一个基类,提供“shell”和插件之间的所有通信。
  3. Individual UI 插件本身。

Shell

启动时,shell 应用程序会读取配置文件以获取每个 UI 插件的名称和位置。然后使用反射加载每个插件。在下面的屏幕截图中,Shell 应用程序包含一个 ListBox 用于在插件之间导航,以及一个 Panel 用于加载插件。

下面是一个加载了 2 个插件的“shell”示例。左侧的 ListBox 用于在每个插件之间进行选择,而右侧的 panel 将在插件可见时显示它。

点击“PlugIn1”使插件可见。

然后点击“PlugIn Number 2”使其在 panel 中可见。

Tabbed Shell 应用程序展示了另一种导航方式。

这是加载后的 Tabbed Shell。

这是选择“PlugIn Number 2”后的样子。

Shell 如何找到插件。

将在运行时加载的插件列在一个名为 config.xml 的 XML 文件中。

<?xml version="1.0" encoding="utf-8" ?>
<PlugIns>
  <PlugIn Location="E:\ExtensibleUI\OurControls\bin\Debug\OurControls.dll" 
        Name="OurControls.PlugIn1"></PlugIn>
  <PlugIn Location="E:\ExtensibleUI\OurControls\bin\Debug\OurControls.dll" 
        Name="OurControls.PlugIn2"></PlugIn>
</PlugIns>

在窗体加载事件中,config.xml 文件通过 ReadXml 加载到 DataSet 中。然后遍历每个 DataRow,调用 AddPlugin 并传入插件的“location”和“name”。

private void Form1_Load(object sender, System.EventArgs e)
{
    DataSet ds = new DataSet();
    ds.ReadXml("Config.xml");
    foreach(DataRow dr in ds.Tables["Plug-In"].Rows)
    {
        AddPlugIn(dr["Location"].ToString(),
            dr["Name"].ToString());
    }
}

AddPlugIn 代码的两个示例。

AddPlugIn 会加载包含插件的程序集并创建其实例。它还会将插件添加到 ListBox 中。当选中列表框中的新项时,我们需要隐藏当前插件,并显示新选择的插件。

// Load and add a plug-in to the panel1 control

// Also set the list box to navigate between plugins.

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.ClickHandler(Control_Clicked);
    // Add the plugin to the listBox, listBox will use ToString to

    // get the text to display.

    listBox1.Items.Add(NewPlugIn);

}

private PlugIn CurrentPlugIn;

// When a new item in the listBox is selected,

// hide the current plugin and show the new.

private void listBox1_SelectedIndexChanged(object sender, 
                                           System.EventArgs e)
{
    if(CurrentPlugIn!=null)
    {
        CurrentPlugIn.Visible = false;
    }
    CurrentPlugIn = (PlugIn)listBox1.SelectedItem;
    CurrentPlugIn.Visible = true;
}

Tabbed Shell 应用程序的 AddPlugIn 略有不同。Tabbed Shell 应用程序不需要导航代码,因为导航由 TabControl 处理。

// Load and add a plug-in to the TabControl1 control

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 = true;
    // Create a new TabPage.

    TabPage newPage = new TabPage();
    // Set the text on the tabPage with the PlugIn Caption.

    newPage.Text = NewPlugIn.Caption;
    // Add the PlugIn to the TabPage.

    newPage.Controls.Add(NewPlugIn);
    // Add the page to the tabControl.

    tabControl1.TabPages.Add(newPage);
    // Set up the ClickHandler

    NewPlugIn.Clicked += new PlugInLib.ClickHandler(Control_Clicked);
}

PlugIn 基类

The PlugIn 基类基于 System.Windows.Forms.UserControl 类,并对其进行扩展,提供预定义的事件、方法和属性,每个插件都可以使用它们与 shell 应用程序进行通信。在此示例中,预定义了 Clicked 事件、Caption 属性和 TestFunction 方法。此外,还重写了 ToString 以返回 Caption 而不是对象名称。

using System;
using System.Windows.Forms;

namespace PlugInLib
{
    /// <summary>

    /// A delegate type for hooking up notifications.

    /// </summary>

     public delegate void ClickHandler(object sender, EventArgs e);
    /// <summary>

    /// Summary description for PlugIn.

    /// </summary>

    public class PlugIn : System.Windows.Forms.UserControl
    {
        // The following provides "Clicked" event back to the container.

        public event ClickHandler Clicked;
        protected void DoClick(EventArgs e)
        {
            if (Clicked != null)
                Clicked(this, e);
        }
        // Provide a "Caption" that the container can display.

        protected string m_Caption = "PlugIn";
        public string Caption
        {
            get
            {
                return m_Caption;
            }
            set
            {
                m_Caption = value;
            }
        }
        public override string ToString()
        {
            return m_Caption;
        }

        // Provide a method "TestFunction" that the container can call.

        public virtual void TestFunction()
        {
        }
    }
}

创建 UI 插件。

  1. 使用 Visual Studio 创建一个新的“Windows 控件库”。
  2. 添加对包含插件基类的 PlugInLib 的引用。
  3. 将用户控件的名称从 UserControl1 更改为更具描述性的名称。
  4. 添加 using 指令以导入 PlugInLib
  5. 将用户控件的基类从 System.Windows.Forms.UserControl 更改为 PlugIn
  6. 连接任何您希望发送到 shell 应用程序的事件。
  7. 为从 shell 到插件的调用添加必要的重写。
  8. 像构建任何其他 UserControl 一样构建您的 UI。
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using PlugInLib;            // <---Add using for the plug-In base class


namespace OurControls
{
    /// <summary>

    /// Summary description for PlugIn3.

    /// </summary>

    public class PlugIn3 : PlugIn    // <---Change base class to PlugIn

    {
        /// <summary> 

        /// Required designer variable.

        /// </summary>

        private System.ComponentModel.Container components = null;

        public PlugIn3()
        {
            // This call is required by the Windows.Forms Form Designer.

            InitializeComponent();

            // TODO: Add any initialization after the InitForm call


        }

        /// <summary> 

        /// Clean up any resources being used.

        /// </summary>

        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Component Designer generated code
        /// <summary> 

        /// Required method for Designer support - do not modify 

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()
        {
            // 

            // PlugIn3

            // 

            this.Caption = "PlugIn 3";
            this.Name = "PlugIn3";
            this.Click += new System.EventHandler(this.PlugIn3_Click);

        }
        #endregion

        // Override Base class to receive call from the shell.

        public override void TestFunction()
        {
            Console.WriteLine("TestFunction called by the shell.");
        }

        // Send clicks to the shell, just because we can.

        private void PlugIn3_Click(object sender, System.EventArgs e)
        {
            DoClick(e);
        }
    }
}

结论

通过此架构,插件与 shell 之间的交互应该是明确且有限的。在上面显示的 PlugIn 基类示例中,shell 和插件之间唯一的实际交互是 Caption 属性。另一种可能的交互方式是 shell 加载一个公共数据结构,该结构在加载每个插件时传递给它。

您可以随时添加新插件,只需创建一个新插件并将相应的条目添加到 config.xml 文件中即可。

注释

在下载部分提供的演示 zip 文件中,config.xml 文件位于两个“Shell”应用程序的 ReleaseDebug 目录中。这些文件包含 OurControls.dll 的绝对路径,包括驱动器字母。您需要为本地系统修改这些路径。

© . All rights reserved.