构建插件式的应用法度框架 [转载]

Admin
2011年12月9日

(一)开篇


说起插件(plug-in)式的应用法度大师应当不陌生吧,记得很早以前有一款很风行的MP3播放软件winmap,它是我记忆里最早熟悉的一款应用插件模式的应用法度,你可以应用他的插件经管器插入很多的音乐结果器,皮肤,甚至是歌词显示的面板。接下来看到了Photoshop应用插件模式经管虑镜。最后发明只如果大一点的应用法度根蒂根基都应用了插件式的法度框架,就拿我们最常用的对象来说吧,Visual Studio,Office,Delphi,Eclipse等等。Eclipse将插件模式阐扬到了及至,因为他是开源的的,所以浩繁的爱好者,开辟出了让人应接不暇的插件。
       为什么应用插件式的应用法度框架呢?我的答案就是他为应用法度的功能扩大供给的无穷的想象空间。一个应用法度,无论你前期做了几许的市场查询拜访,需求解析做的多么完美,你也只是逢迎一项目组人的期望,更甚,你只逢迎了一项目组人的一项目组期望,或者一项目组人在某一时候的一项目组期望。所以当法度公布今后,你依然有机会供给新的功能而不必从头公布法度,人们也可以按照本身的须要来开辟新的功能来满足本身的需求,据我所知有很多的软件公司就是专门开辟插件来赚钱,真是一举多得,何乐而不为呢?
       我们来看一些常见的供给插件模式的应用法度是如何实现插件功能的。据我的应用经验来看,Visual Studio和Office其实都是主动化法度,经由过程COM的体式格式供给了一组接口。开辟人员可以哄骗这些接口来开辟基于COM的插件,当插件开辟完成后,注册COM组件。在Visual Studio中你可以应用Add-in经管器来选择是否启用插件,而Office似乎省去了这一步,一旦你注册了Office插件,Office应用法度在启动的时辰会主动加载插件。COM体式格式似乎最受微软的宠爱,因为COM是一种二进制重用标准,用户可以应用大项目组风行的说话来开辟插件。当然你也可以应用此外体式格式,比如通俗DLL,只是如许对于开辟人员来说实用面就窄了,因为各个厂商DLL的内部布局是不尽雷同的,比VC开辟出的DLL和Borland C++builder开辟出的DLL布局就不合,须要专门的对象进行转换。如今,还有别的一种体式格式,应用dotNet的Assembly,应用dotNet的益处是开辟简单,应用也同样简单(不须要注册),并且你也可用经由过程COM互操纵让开辟人员可以应用各类说话进行插件开辟,当然用dotNet开辟还是最简单的,省去不少中心过程。
         其实上方介绍的三种体式格式开辟的插件终极还是寄宿在DLL中,从中我们就可以看出一些端倪。为什么应用DLL呢?DLL固然也是PE格局,然则他是不克不及自力运行的,一般景象下,都是在运行时加载到应用法度的内存空间。插件模式正好是哄骗了这一点,插件不是应用法度的一项目组,他以二进制的体式格式自力存在,由用户决意是否应用他。
      那么插件是如何与应用法度进行交互的呢?起首必须有一个契约,应用法度要声明我有哪些功能是可以被插件应用的,并且具备什么前提才干成为我的插件。其次,应用法度不依附于插件,也就是说,没有你插件,我也可以很好的运行。再次,应用法度必须有一种策略来获取插件存在的地位,比如Visual studio是经由过程注册表的体式格式。最后,应用法度可以经由过程某种体式格式动态的加载插件。 


(二)订立契约


         无论是用COM的体式格式,还是通俗DLL,抑或.NET体式格式来实现插件框架,起首要面对的题目就是如何订立契约。如同我上一篇文章讲到的一样,契约是应用法度和插件之间进行交互的根据和凭证。应用法度必须声明我有什么样的功能可被插件应用,并且插件必须合适什么前提才干被我应用。反之,插件必必要知道应用法度供给什么样的功能,我才干将本身的功能融入到应用法度的体系中。本系列文章首要讲如何应用.NET实现插件式的应用法度框架,所以其它的体式格式我就不再提了。


如何应用.NET订立契约呢?起首想到的Interface,其次是抽象类,然则在插件模式中我应用接口,因为我们是在满足应用法度的首要目标的根蒂根基上来供给附加的插件功能,就这一点来说,接口更灵活,更轻易扩大。接下来,如何订立契约的内容呢?这就要按照你的营业需求了,为了讲解的便利,我们定义一个最最根蒂根基的插件式应用法度的插件契约。我们做一个假定,我们的应用法度是一个多文档的应用法度,包含一个主菜单栏,一个对象栏,菜单栏可以在法度的高低阁下四个标的目标停靠,别的还有一个状况栏。到后边,若是有须要,我会扩大这个应用法度,让他本身供给更多的可供插件应用的功能。所以就今朝而言,我想实现的功能就是让插件为主法度添加对象条,菜单项并实现一些简单的功能。


应用法度向插件供给办事有两种体式格式,一种是直接再应用法度接口中声明属性或者办法,一种是将应用法度接口声明成一个办事容器。我筹算两种体式格式都用,明白的功能就在接口中直接声明成属性或者办法,别的将应用法度声明成一个办事容器,以便利插入更多的办事功能,进步应用法度的可扩大性。   


下边是一个很是简单的应用法度接口定义,对于我们的假定已经足够了。


using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel.Design;
using System.Windows.Forms;

namespace PluginFramework
{
    public interface IApplication:IServiceContainer
     {
        ToolStripPanel LeftToolPanel  { get;}
        ToolStripPanel RightToolPanel  { get;}
        ToolStripPanel TopToolPanel  { get;}
        ToolStripPanel BottomToolPanel  { get;}

        MenuStrip MainMenuStrip  { get;}
        StatusStrip StatusBar  { get;}
    }
}


插件的接口定义:


using System;
using System.Collections.Generic;
using System.Text;

namespace PluginFramework
{
    public interface IPlugin 
     {
        IApplication Application  { get;set;}
        String Name  { get;set;}
        String Description  { get;set;}
        void Load();
        void UnLoad();

        event EventHandler<EventArgs> Loading;
    }
}


 


(三)动态加载


不管你采取什么体式格式实现插件式的应用法度框架,核心还是动态加载,换句话说,没有动态加载技巧也就无所谓插件式的应用法度框架了。应用Com实现的话,你可以哄骗Com的API经由过程ProgID来动态创建COM对象,若是应用通俗DLL,你须要应用Windows 的API函数LoadLibrary来动态加载DLL,并用GetProcAddress函数来获取函数的地址。而应用.NET技巧的话,你须要应用Assembly类的几个静态的Load(Load,LoadFile,LoadFrom)办法来动态加载汇集。
       一个Assembly里可以包含多个类型,由此可知,一个Assembly里也可以包含多个插件,就像前一篇文章所讲,只要它从IPlugin接口派生出来的类型,我们就承认它是一个插件类型。那么Assembly被动态加载了今后,我们如何获取Assembly里包含的插件实例呢?这就要用到反射(Reflection)机制了。我们须要应用Assembly的GetTypes静态办法来获得Assembly里所包含的所有的类型,然后遍历所有的类型并断定每一个类型是不是从IPlugin接口派生出来的,若是是,我们就应用Activator的静态办法CreateInstance办法来获得这个插件的实例。.NET的动态加载就是这几个步调。下来,我做一个简单的例子来练习训练一下动态加载。起首声明一点,这个例子很是简单,纯粹是为了练习训练动态加载,我们的真正的插件式的应用法度框架里会有专门的PluginService来负责插件的加载,卸载。 
      我们的插件位于一个DLL里,所以我们起首创建一个Class library工程。创建一个FirstPlugin类让它派生于IPlugin接口,并实现接口的办法和属性,因为本文的目标是演示动态加载,所以IPlugin接口的Loading事务我们就不供给默认的实现了,固然编译的时辰会给出一个警告,我们不必理会它。这个插件的功能就是在应用法度里创建一个停靠在主窗体底部的ToolStrip,这个ToolStrip上有一个按钮,点击这个按钮,会弹出一个MessageBox显示“The first plugin”。下面是代码:


using System;
using System.Collections.Generic;
using System.Text;
using PluginFramework;
using System.Windows.Forms;

namespace FirstPlugin
{
    public class FirstPlugin:IPlugin
     {
        private IApplication application = null;
        private String name="";
        private String description = "";

 #region IPlugin Members

        public IApplication Application
         {
            get
             {
                return application;
            }
            set
             {
                application = value;
            }
        }

        public string Name
         {
            get
             {
                return name;
            }
            set
             {
                name = value;
            }
        }

        public string Description
         {
            get
             {
                return description;
            }
            set
             {
                description = value;
            }
        }

        public void Load()
         {
            if (application != null && application.BottomToolPanel != null)
             {
                //创建一个向主法度添加的ToolStrip
                ToolStrip sampleToolStrip = new ToolStrip();
                ToolStripButton button = new ToolStripButton("Click Me");
                button.Click += new EventHandler(button_Click);
                sampleToolStrip.Items.Add(button);

                //在主法度的底端添加ToolStrip
                application.BottomToolPanel.Controls.Add(sampleToolStrip);
            }
        }

        void button_Click(object sender, EventArgs e)
         {
            MessageBox.Show("The first plugin");
        }

        //相干的文章首要讲动态加载,所以卸载就不实现了
        public void UnLoad()
         {
            throw new Exception("The method or operation is not implemented.");
        }

        public event EventHandler<EventArgs> Loading;

        #endregion
    }
}


接下来我们创建一个Windows Application工程让主窗体派生于IApplication接口并实现IApplication接口的办法和属性,下来我们声明1个MenuStrip和1个StatusStrip,让他们分别停靠在窗口的顶部和底端,接下来我们声明4个ToolStripPanel,分别人他们停靠在高低阁下四个边,最后我们创建一个ToolStrip,在上边添加一个按钮,当点击这个按钮的时辰,我们动态的加载插件。
      为了便利演示,我们把生成的Assembly放置到固定的地位,以便利主法度加载,在本例里,我们在应用法度地点的文件夹里创建一个子文件夹Plugins(E:\Practise\PluginSample\PluginSample\bin\Debug\Plugins),将插件工程产生的Assembly(FirstPlugin.dll)放置在这个子文件夹。下面是动态加载的代码:


private void toolStripButton1_Click(object sender, EventArgs e)
{
            //动态加载插件,为了便利起见,我直接给出插件地点的地位
            String pluginFilePath = Path.GetDirectoryName(Application.utablePath) + "\\plugins\\FirstPlugin.dll";
            Assembly assembly = Assembly.LoadFile(pluginFilePath);

            //获得Assembly中的所有类型
            Type[] types = assembly.GetTypes();

            //遍历所有的类型,找到插件类型,并创建插件实例并加载
            foreach (Type type in types)
            {
                  if (type.GetInterface("IPlugin") != null)//断定类型是否派生自IPlugin接口
                  {
                       IPlugin plugin = (IPlugin)Activator.CreateInstance(type);//创建插件实例
                       plugin.Application = this;
                       plugin.Load();
                 }
           }
}


 


(四)办事


在(二)订立契约一文中,可以看到我们的IApplication接口是派生于IServiceContainer接口的。为什么要派生于IServiceContainer呢?我们来看看IServiceContainer的定义,它有几个AddService办法和RemoveService办法以及从IserviceProvider持续过来的GetService办法。Service本身是.NET设计时架构的根蒂根基,Service供给设计时对象接见某项功能的办法实现,说起来还真拗口。就我看来,ServiceContainer机制的本质就是解耦合,就是将类型的设计时功能从类型本身剥离出来。若是你把类型的设计时功能也封装到类型里,如许的类型包含了很多只有开辟人员才会用到而终极用户底子不须要的功能,使得类型既痴肥有不便于扩大。而将设计时功能剥离出来,如许类型就可以不依附于特定的设计景象,之所以如今有这么多非官方的.NET设计景象可能就是这个原因吧。
       我们的插件式的应用法度框架正好也须要如许一个疏松的架构,我就移花接木把它应用到我们的框架中。
       ServiceContainer是.NET供给的IserviceContainer的实现,若是没有特别的须要我们不必扩大它,而是直接的哄骗它。在上一篇文章中我们在实现IApplication接口的时辰就直接应用的ServiceContainer。我们在应用Service架构的时辰,老是偏向于有一个根容器,各个Service容器构成了一个Service容器树,每一个节点的办事都可以一向向上传递,直到根部,而每一个节点恳求Service的时辰,我们老是可以从根节点获得。我把这个根节点比方成一个办事中间,它汇总了所有可供给的办事,当某个对象要恳求办事(GetService)只须要向根结点发送要获得的办事,根结点就可以把办事的对象传递给它。
       从别的一个角度看,ServiceContainer为我们的插件是应用法度供给了有力的支撑,哄骗ServiceContainer,你不单可以获得应用法度所供给的所有的功能,并且你还可以经由过程插件向应用法度添加Service,而你添加的Service又可以办事别的的Service,如许我们的应用法度框架就加倍的灵活了。然则任何器材都是有两面性的,带来灵活的同时也为开辟人员的工作增长了错杂度,所以应用ServcieContianer开辟的应用法度必须供给足够具体的文档,不然开辟人员可能底子不知道你到底有几许Service可以用,因为很多的Service是经由过程插件供给的,可能应用法度的作者都不会知道法度公布今后会呈现几许Service。


 


(五)经管插件


我们如今已经搭建了插件式的应用法度框架,接下来的工作就是要充分框架的内容,供给根蒂根基的办事,也就是Service。我想首要的任务就是供给插件的经管办事,我在前面的文章也提到了,要实现动态加载必必要知道插件寄宿在哪里,哪些要加载,哪些不加载,这些就是这篇文章要评论辩论的题目。
       起首解决的就是插件放在什么处所,我采取的传统的办法,将插件放到应用法度地点目次下的制订目次,我会在应用法度地点的目次下创建一个文件夹,定名为Plugins。接下来的工作就是要通知哪些插件是要加载的,哪些是不须要加载的,我会将这些信息放到应用法度的设备文件中的制订设备块中,当应用法度运行的时辰,就会读取设备文件,并按照获得的信息加载插件。别的我们的应用法度框架是建树在Service根蒂根基之上,所以我须要创建一个经管插件的service。
       我们如今定义一个插件经管的Service接口。        


using System;
using System.Collections.Generic;
using System.Text;

namespace PluginFramework
{
    public interface IPluginService
     {
        IApplication Application  { get;set;}
        void AddPlugin(String pluginName, String pluginType, String Assembly, String pluginDescription);
        void RemovePlugin(String pluginName);
        String[] GetAllPluginNames();
        Boolean Contains(String pluginName);
        Boolean LoadPlugin(String pluginName);
        Boolean UnLoadPlugin(String pluginName);
        IPlugin GetPluginInstance(String pluginName);
        void LoadAllPlugin();
    }
}


      PluginService要实现的目标起首是在设备文件中添加/删除要加载的插件以及相干的信息,接下来就是动态的加载插件。我们要定义几个类型:Plugin设备区块类型,Plugin元素类型,plugin元素凑集类型,以便我们可以或许读取插件的信息。
       最后我们实现PluginService:


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Configuration;
using System.Reflection;
using System.Windows.Forms;
using System.IO;
using System.Collections;

namespace PluginFramework
{
    public class PluginService : IPluginService
     {
        private IApplication application = null;
        private PluginConfigurationSection config = null;
        private Dictionary<String, IPlugin> plugins = new Dictionary<string, IPlugin>();
        private XmlDocument doc = new XmlDocument();

        public PluginService()
         {

        }

        public PluginService(IApplication application)
         {
            this.application = application;
        }

        IPluginService Members#region IPluginService Members

        public void AddPlugin(string pluginName, string pluginType, string assembly, string pluginDescription)
         {
            doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            XmlNode pluginNode = doc.SelectSingleNode("/configuration/PluginSection");
            XmlElement ele = doc.CreateElement("add");
            XmlAttribute attr = doc.CreateAttribute("Name");
            attr.Value = pluginName;
            ele.SetAttributeNode(attr);
            XmlAttribute attrType = doc.CreateAttribute("Type");
            attrType.Value = pluginType;
            ele.SetAttributeNode(attrType);

            XmlAttribute attrAss = doc.CreateAttribute("Assembly");
            attrAss.Value = assembly;
            ele.SetAttributeNode(attrAss);

            XmlAttribute attrDes = doc.CreateAttribute("Description");
            attrDes.Value = pluginDescription;
            ele.SetAttributeNode(attrDes);
            pluginNode.AppendChild(ele);
            doc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            ConfigurationManager.RefreshSection("PluginSection");
        }

        public void RemovePlugin(string pluginName)
         {
            doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            XmlNode node = doc.SelectSingleNode("/configuration/PluginSection");
            foreach (XmlNode n in node.ChildNodes)
             {
                if (n.Attributes != null)
                 {
                    if (n.Attributes[0].Value == pluginName)
                     {
                        node.RemoveChild(n);
                    }
                }
            }
            doc.Save(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
            ConfigurationManager.RefreshSection("PluginSection");
        }

        public string[] GetAllPluginNames()
         {
            config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
            PluginConfigurationElement pe = new PluginConfigurationElement();
            ArrayList ps = new ArrayList();
            for (Int32 i = 0; i < config.PluginCollection.Count; i++)
             {
                pe = config.PluginCollection[i];
                ps.Add(pe.Name);
            }
            return (String[])ps.ToArray(typeof(String));
        }

        public bool Contains(string pluginName)
         {
            config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
            PluginConfigurationElement pe = new PluginConfigurationElement();
            List<String> ps = new List<string>();
            for (Int32 i = 0; i < config.PluginCollection.Count; i++)
             {
                pe = config.PluginCollection[i];
                ps.Add(pe.Name);
            }
            return ps.Contains(pluginName);
        }

        public bool LoadPlugin(string pluginName)
         {
            Boolean result = false;
            config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
            PluginConfigurationElement pe = new PluginConfigurationElement();

            String path = Path.GetDirectoryName(System.Windows.Forms.Application.utablePath) + "\\Plugin";
            try
             {
                for (Int32 i = 0; i < config.PluginCollection.Count; i++)
                 {
                    pe = config.PluginCollection[i];
                    if (pe.Name == pluginName)
                     {
                        Assembly assembly = Assembly.LoadFile(path + "\\" + pe.Assembly);
                        Type type = assembly.GetType(pe.Type);
                        IPlugin instance = (IPlugin)Activator.CreateInstance(type);
                        instance.Application = application;
                        instance.Load();
                        plugins[pluginName] = instance;
                        result = true;
                        break;
                    }
                }
                if (!result)
                 {
                    MessageBox.Show("Not Found the Plugin");
                }
            }
            catch (Exception e)
             {
                MessageBox.Show(e.Message);
                result = false;
            }
            return result;
        }

        public bool UnLoadPlugin(string pluginName)
         {
            Boolean result = false;
            try
             {
                IPlugin plugin = GetPluginInstance(pluginName);
                plugin.UnLoad();
                result = true;
            }
            catch (Exception e)
             {
                MessageBox.Show(e.Message);
            }
            return result;
        }

        public void LoadAllPlugin()
         {
            PluginConfigurationElement pe = new PluginConfigurationElement();
            config = (PluginConfigurationSection)ConfigurationManager.GetSection("PluginSection");
            String path = Path.GetDirectoryName(System.Windows.Forms.Application.utablePath) + "\\Plugin";
            try
             {
                for (Int32 i = 0; i < config.PluginCollection.Count; i++)
                 {
                    pe = config.PluginCollection[i];
                    Assembly assembly = Assembly.LoadFile(path + "\\" + pe.Assembly);
                    Type type = assembly.GetType(pe.Type);
                    IPlugin instance = (IPlugin)Activator.CreateInstance(type);
                    instance.Application = application;
                    instance.Load();
                    plugins[pe.Name] = instance;
                }
            }
            catch (Exception e)
             {
                MessageBox.Show(e.Message);
            }
        }

        public IApplication Application
         {
            get
             {
                return application;
            }
            set
             {
                application = value;
            }
        }

        public IPlugin GetPluginInstance(string pluginName)
         {
            IPlugin plugin = null;
            if (plugins.ContainsKey(pluginName))
             {
                plugin = plugins[pluginName];
            }
            return plugin;
        }

        #endregion
    }
}      


    因为代码斗劲多,我也就不一一列举了,只把斗劲首要的代码列出来,其余的我会供给源代码的下载。在实现了PluginService今后,我们须要有一个处所可以或许应用这个Service来经管插件,我的做法是在一个菜单里添加一个项目,当用户点击这个项目标时辰弹出插件经管的对话框,用户在这个对话框中选择应用那些插件,当插件被选中的时辰,插件会被立即加载进来,并且记录到设备文件里,当用户下次运行应用法度的时辰,插件默认会被主动的加载。


       别的从如今开端我们就须要应用设备文件了,所以,我们须要给应用法度添加一个app.config文件,文件内容如下:      


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
      name="PluginSection"
      type="PluginFramework.PluginConfigurationSection, PluginFramework"
      />    
  </configSections> 
  <PluginSection>
    
  </PluginSection>
</configuration>


 


(六)通信机制


这个系列的文章写到这里,也该谈谈这个题目了,毕竟成果已经有了插件经管。不知道大师有没有重视到我在第四篇文章里谈到的办事容器(Service Container),Service是我所提到的插件式的应用法度框架的根蒂根基,我们也可以回头看看IApplication的接口定义,IApplication是派生于IServiceContainer。我把应用法度供给的相干的功能抽象成一个一个的Service,比如文档经管的,我们就抽象成IDocumentService,停靠对象栏经管功能抽象成IDockBarService,菜单经管的功能抽象成IMenuService,等等。我在第四篇文章里也提到了“我们在应用Service架构的时辰,老是偏向于有一个根容器,各个Service容器构成了一个Service容器树,每一个节点的办事都可以一向向上传递,直到根部,而每一个节点恳求Service的时辰,我们老是可以从根节点获得。我把这个根节点比方成一个办事中间,它汇总了所有可供给的办事,当某个对象要恳求办事(GetService)只须要向根结点发送要获得的办事,根结点就可以把办事的对象传递给它。”
      IApplication是从IServiceContainer接口派生出来的,而我们的应用法度主窗口又是从IApplication接口派生出来的,所以,我们的应用法度主窗口就是一个ServiceContainer。从IPlugin的定义来看,它有一个IApplication接口属性,这个IApplication属性是什么时辰指定的呢,在第五篇文章的源代码里我们看到,当每一个Plugin被实例化的时辰,由PluginService指定的,所以在每一个Plugin被Load之前,IApplication已经被指定,而代表这个IApplication接口的实例恰是我们的应用法度主窗口,而它恰是我们所须要的办事容器。一旦我们可以或许获得IApplication实例,我们就可以获得全部应用法度所供给的所有的办事。假设我们要获得文档办事,就可以应用Plugin的Application.GetService(typeof(IdocumentService))来获得文档办事的实例,接着我们就可以应用这个实例来完成某项功能,比如添加一个新文档等等,其实在第五篇文章的源代码就有如许代码:      


private void CheckExistedPlugin()
         {
            IPluginService pluginService = (IPluginService)application.GetService(typeof(IPluginService));
            if (pluginService != null)
             {
                List<String> nameList=new List<string>();
                String[] pluginNames = pluginService.GetAllPluginNames();
                nameList.AddRange(pluginNames);
                foreach (ListViewItem item in listView1.Items)
                 {
                    if (nameList.Contains(item.Text))
                     {
                        item.Checked = true;
                    }
                }
            }
        }


      当然,要在插件中获得实例,你必须在应用法度里或者其他插件里实例化办事对象,然后添加到办事容器里,还拿上边的例子,我们在应用法度里实例化了PluginService,然后添加到了容器里,代码如下:
   


        public MainForm()
         {
            InitializeComponent();
            pluginService = new PluginService(this);
            serviceContainer.AddService(typeof(IPluginService), pluginService);
        }


      稍后,我会持续完美这个例子,做一个简单的多文档编辑器来做演示,并供给一些根蒂根基的办事,以便大师浏览


 


(七)根蒂根基办事


既然做好了框架,我们就欲望为某个目标办事,我们要供给一些根蒂根基的办事,便哄骗户持续扩大他的功能。起首想到的功能就是,菜单,对象栏的经管,接下来我们要实现一些更风行的功能,比如停靠对象栏等等。
       如何实现这些办事呢?我们欲望我们的插件在运行时可以获得应用法度本身的菜单,对象条,停靠对象栏等等,然后向他们添加项目,比如参加一个菜单项,添加一个对象栏按钮。为了在运行时获得某个菜单或者对象栏,我们要为每一个菜单后者对象栏分派一个Key,然后放到一个词典中,当须要的时辰,我们经由过程这个key来获得实例。对于这个Key呢,在我的例子斗劲简单就是他的名字,我们来看看ToolStripService的代码:


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace PluginFramework
{
    public class ToolStripService:IToolStripService
     {
        private IApplication application = null;
        private Dictionary<String, ToolStrip> toolStrips = new Dictionary<string, ToolStrip>();

        public ToolStripService(IApplication application)
         {
            this.application = application;
        }

        IToolStripService Members#region IToolStripService Members

        public System.Windows.Forms.ToolStrip GetToolStrip(string toolStripName)
         {
            ToolStrip toolStrip = null;
            if (toolStrips.ContainsKey(toolStripName))
             {
                toolStrip = toolStrips[toolStripName];
            }
            return toolStrip;
        }

        public void AddToolStrip(string toolStripName, System.Windows.Forms.ToolStrip toolStrip)
         {
            if (toolStrips.ContainsKey(toolStripName))
             {
                MessageBox.Show("The tool strip name has existed!");
            }
            else
             {
                toolStrips[toolStripName] = toolStrip;
                //若是没有指定toolstrip在哪个面板,择默认加到顶部
                if (application.TopToolPanel != null)
                 {
                    application.TopToolPanel.Controls.Add(toolStrip);
                }
            }
        }

        public void AddToolStrip(string toolStripName, System.Windows.Forms.ToolStrip toolStrip, ToolStripDockState option)
         {
            if (toolStrips.ContainsKey(toolStripName))
             {
                MessageBox.Show("The tool strip name has existed!");
            }
            else
             {
                toolStrips[toolStripName] = toolStrip;
                switch (option)
                 {
                    case ToolStripDockState.Left:
                        if (application.LeftToolPanel != null)
                         {
                            application.LeftToolPanel.Controls.Add(toolStrip);
                        }
                        break;
                    case ToolStripDockState.Right:
                        if (application.RightToolPanel!= null)
                         {
                            application.RightToolPanel.Controls.Add(toolStrip);
                        }
                        break;
                    case ToolStripDockState.Top:
                        if (application.TopToolPanel != null)
                         {
                            application.TopToolPanel.Controls.Add(toolStrip);
                        }
                        break;
                    case ToolStripDockState.Bottom:
                        if (application.BottomToolPanel != null)
                         {
                            application.BottomToolPanel.Controls.Add(toolStrip);
                        }
                        break;
                }
            }
        }

        public void RemoveToolStrip(string toolStripName)
         {
            ToolStrip toolStrip = GetToolStrip(toolStripName);
            if (toolStrip != null)
             {
                if (application.TopToolPanel != null && application.TopToolPanel.Controls.Contains(toolStrip))
                 {
                    application.TopToolPanel.Controls.Remove(toolStrip);
                }
                else if (application.BottomToolPanel != null && application.BottomToolPanel.Controls.Contains(toolStrip))
                 {
                    application.BottomToolPanel.Controls.Remove(toolStrip);
                }
                else if (application.LeftToolPanel != null && application.LeftToolPanel.Controls.Contains(toolStrip))
                 {
                    application.LeftToolPanel.Controls.Remove(toolStrip);
                }
                else if (application.RightToolPanel != null && application.RightToolPanel.Controls.Contains(toolStrip))
                 {
                    application.RightToolPanel.Controls.Remove(toolStrip);
                }
            }
            toolStrips.Remove(toolStripName);
        }

        #endregion
    }
}


      对于视图或者是停靠对象栏来说,最好是不要直接在词典中放入实例,而是应当将对象的类型放入到词典中,因为,视图和停靠对象栏本身都是从Form派生而来,所以,当视图或者是停靠对象栏被封闭的时辰,对象就被烧毁了,而对象的创建在是插件的Load办法里完成的,我们不成能再去调用插件的Load办法,如许给我们的应用带来了不便,所以我们应当注册类型,然后在Service中实现一个Show办法是斗劲公道的,这里为了演示便利,我就直接在Load里面实例化了,并把实例放到了词典里。
      下边这个图例里显示了插件参加的停靠对象栏,对象栏,一个新的菜单“View”和View菜单的子菜单:


 


 


(八)视图办事的简单实现


我在前一篇文章里提到,对于停靠对象栏或者是视图最好是不要将实例放到词典中,而是将对象栏或者视图的类型放到词典中,因为视图类型会经常的被重用,并且会经常被封闭或者再打开。当实例被封闭后,资料就被开释了,对于实例的经管就会斗劲麻烦,所以我们分为两步走。在插件被加载的时辰,我们只注册类型,在应用法度运行的时辰,我们经由过程某种路子来实例化他。
       我批改的以前的例子,首要凸起本次演示的功能。此次的例子实现的功能是经由过程插件扩大应用法度处理惩罚不合文件的才能。在原始的应用法度中,我们可以经由过程File菜单的Open,只能打开一种文件,就是文本文件,大师可以在例子中看到,当我们没有加载插件的景象下,在OpenFileDialog的Filter中只有"Text(*.txt)"。选择一个文本文件今后,将会呈现文本文件视图。当我们加载插件今后,在点击File->Open菜单,我们调查Filter,发明会多出两种文件:"JPEG"和"BMP",这是我们就可以打开图片文件,选中文件今后,将会呈现Picture视图,并且在主菜单下边,还会呈现一个对象栏,点击对象栏上的按钮,可以给图片加上水印,并且对象栏会按照PictureView的状况(Active)显示和消散。比如你打开了一个文本视图和一个图片视图,当你切换到文本视图的时辰,对象栏就会消散,再切换到图片视图的时辰,对象栏又会呈现。
       我在框架里面添加了一个IDocumentViewService的接口,用以描述办事的功能:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;

namespace PluginFramework
{
    public interface IDocumentViewService
     {
        void RegisterView(String fileType,string fileFilter,Type viewType);
        void ShowView(String fileType, String filePath);
        void RemoveRegister(String fileType);
        String GetFileFilter(String fileType);
        String GetFileTypeByFileFilter(String fileFilter);

        StringCollection FileTypies  { get;}
    }
}



     下面是这个办事的实现:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;

namespace PluginFramework
{
    public class DocumentViewService:IDocumentViewService
     {
        private Dictionary<String, Type> docViewRegister = new Dictionary<string, Type>();
        private Dictionary<String, String> fileTypeToFileFilter = new Dictionary<string, string>();
        private Dictionary<String, String> fileFilterToFileType = new Dictionary<string, string>();
        private IApplication application = null;

        public DocumentViewService(IApplication app)
         {
            application = app;
        }

        IDocumentViewService Members#region IDocumentViewService Members

        public void RegisterView(string fileType, string fileFilter, Type viewType)
         {
            docViewRegister[fileType] = viewType;
            fileTypeToFileFilter[fileType] = fileFilter.ToUpper();
            fileFilterToFileType[fileFilter.ToUpper()] = fileType;
        }

        public void ShowView(string fileType, string filePath)
         {
            if(docViewRegister.ContainsKey(fileType))
             {
                IDocumentView docView = null;
                try
                 {
                    docView = (IDocumentView)Activator.CreateInstance(docViewRegister[fileType]);
                    docView.Application = application;
                    docView.ShowView(filePath);
                }
                catch
                 {
                    
                }
                
            }
        }

        public void RemoveRegister(string fileType)
         {
            docViewRegister.Remove(fileType);
        }

        public StringCollection FileTypies
         {
            get
             {
                StringCollection sc = new StringCollection();
                foreach (String key in docViewRegister.Keys)
                 {
                    sc.Add(key);
                }
                return sc;
            }
        }

        #endregion


        IDocumentViewService Members#region IDocumentViewService Members


        public string GetFileFilter(string fileType)
         {
            String result = "";
            if (fileTypeToFileFilter.ContainsKey(fileType))
             {
                result = fileTypeToFileFilter[fileType];
            }
            return result;
        }

        #endregion

        IDocumentViewService Members#region IDocumentViewService Members


        public string GetFileTypeByFileFilter(string fileFilter)
         {
            String result = "";
            if (fileFilterToFileType.ContainsKey(fileFilter))
             {
                result = fileFilterToFileType[fileFilter];
            }
            return result;
        }

        #endregion
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值