技术改变世界!学习改变自己!

每天进步一点点,遥不可及的事情,在你一点一滴的努力下,也会变成现实。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  55 随笔 :: 0 文章 :: 33 评论 :: 0 引用

公告

很多时候,我们希望可以扩展或者改善应用程序的UI,特别在应用程序已经发布后。通常情况下,这意味着要重新部署整个应用程序,本文探讨了一种插件体系结构,可以在任何时候,实现应用程序UI的改进。

一、概述

当不考虑插件体系架构之前,你的应用程序通常是这种情况:应用程序UI插件之间没有任何交互,这不是说这些UI插件不能共享一个公共的数据结构或者业务对象,而是说这些插件之间不能直接去调用对方。

在我们要讨论的插件体系结构中,所有的UI元素都被设计成包含在一组基于System.Windows.Forms.UserControl类型上的插件集,并且所有的插件都由一个配置文件进行描述,能够在运行时动态地加载。通过创建新的插件,并在配置文件中添加对应的配置,就可以完成应用程序UI的扩展。

这样的插件体系结构,具有很多优点:

· 不同的UI元素可以独立地开发。例如,如果正在开发个人信息管理系统(PIM),可以让一个人去实现“约会/日程安排”的UI,而派另一个人去完成“联系人”的UI

· 更好的系统控制。你可以根据用户的姓名或者用户的角色,甚至基于所购注册码的选项,来限制系统提供给用户的功能

· 你可以在任何时候添加新的UI,例如上述PIM的例子,你可以在应用程序发布之后,添加一个“日记”功能的UI

本插件体系结构包含如下三个方面的内容:

1. 一个shell,用户加载和浏览插件

2. 一个实现shell和插件之间所有通信的类

3. 各自独立的UI插件

二、shell的实现

当应用程序启动时,shell负责读取配置文件中每个UI的名字和位置信息,然后shell通过反射来加载每个插件,在下面的截图中,shell应用包含了一个列表框控件,用于展示插件列表,同时它还包含一个面板控件用于加载插件。

下面是一个加载两个插件后的shell示例,左边的列表框用于展示所有可用选择的插件,当插件被选中后,右边的面板则用来显示插件。

clip_image001

当点击左边列表中的"PlugIn1"后,可以看到"PlugIn1"插件被加载到右边的面板中:

clip_image002

而点击左边列表框中的"PlugIn Number2"后,可以看到右边的面板加载了"PlugIn Number2":

clip_image003

在标签风格的Shell中,可以看到另外一种加载插件的界面:

clip_image004

下图是选中"PlugIn Number2"后的界面:

clip_image005

三、Shell如何发现插件的?

插件通过config.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>

当Shell应用的主窗体load时,程序通过DataSet对象的ReadXml方法加载config.xml配置。然后通过AddPlugIn方法遍历配置文件中PlugIns内的每一行来加载插件的"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方法加载包含插件的程序集,并创建插件对象实例,然后将插件的名字添加到左边的列表框中,当列表框中的一个插件被选中后,我们就隐藏当前的插件,显示被选中的插件。

// Load and add a plug-in to the panel1 control
// Also set the list box to nav1gate between plug-ins.
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(00);
    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); 



// 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;

 

 

在标签风格的shell程序中,AddPlugIn方法的实现略有不同。标签风格的Shell不需要像列表框那样的导航处理代码,因为标签控件已经能够自己处理了。

 

// 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(00);
    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:

插件核心类PlugIn继承自System.Windows.Forms.UserControl类,通过扩展UserControl类对外提供预定义事件、方法、以及属性,使得每一个插件可以与shell应用程序进行通信。在这个示例中,PlugIn预定义了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()
        {
        }
    }

 

 

六、如何创建一个插件

1. 使用Visual Studio创建一个“Windows窗体控件库”项目

2. 添加对PlugInLib项目的引用,引入PlugIn核心类

3. 将缺省的窗体控件的名字修改为更有意义的名称

4. 用using 添加对PlugInLib的引用

5. 修改窗体控件的基类由 System.Windows.Forms.UserControl 修改为 PlugIn

6. 关联任何希望发送给shell应用的事件

7. 适当重写将被shell调用的方法

8. 实现你所希望的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文件中适当配置它,就是这么简单!

posted on 2010-05-11 21:51 阿捷 阅读(...) 评论(...) 编辑 收藏