插件应用

来源:互联网 发布:淘宝卖衣服进货渠道 编辑:程序博客网 时间:2024/05/07 02:29
 
概述
所谓的“插件”就是代表一个功能模块,插件的配置就是描述该插件并指定如何把这个插件挂到系统中。也就是每一个插件在系统中都有一个扩展点的路径。
例如:
<AddIn name        = "AddinTreeView"
       author      
= "MALONG"
       copyright   
= "GPL"
       url         
= "http://www.CCC.net"
       description 
= "Display AddinTree"
       version     
= "1.0.0">

 
<Runtime>
  
<Import assembly="../../bin/ AddinTreeView.dll"/>
 
</Runtime>

 
<Extension path = "/Gdesigner/Workbench/MainMenu/Tools">
  
<MenuItem id = "AddinTreeView" 
   label 
= "View AddinTree" 
   class 
= "Addins.AddinTreeView.AddinTreeViewCommand"/>
 
</Extension> 
</AddIn>
在配置文件中,Runtime节指定了插件功能模块所在的库文件Addins.dll的具体路径,在Extension节中指定了扩展点路径/ Gdesigner /Workbench/MainMenu/Tools(是打算把它挂到主菜单的工具菜单下),然后在Extension内指定了它的Codon为 MenuItem以及具体的ID、标签、Command类名。
   如果我们对于每一个插件都编写这样的一个配置文件,那么插件的库文件(.dll)、插件配置文件(.addin)是一一对应的。不过这样就带来了一个小小的问题,在这样的一个以插件为基础的系统中,每一个菜单、工具栏按钮、窗体、面板都是一个插件,那么我们需要为每一个插件编写配置文件,这样就会有很多个配置文件(似乎有点太多了,不是很好管理)。于是我们把多个插件的配置合并在一个插件的配置文件中。因此,我把我的两个插件库文件合并到一个Addins工程内生成了Addins.dll,又重新编写了插件配置文件MyAddins.addin如下:
<AddIn name        = "MyAddins"
       author      
= "SimonLiu"
       copyright   
= "GPL"
       url         
= "http://www.ccc.net"
       description 
= "Display AddinTree"
       version     
= "1.0.0">

 
<Runtime>
  
<Import assembly="../../bin/Addins.dll"/>
 
</Runtime>

 
<Extension path = "/Gdesigner/Workbench/MainMenu/Tools">
  
<MenuItem id = "ResourceEditor" 
   label 
= "Resource Editor" 
   class 
= "Addins.ResourceEditor.Command.ResourceEditorCommand"/> 
  
<MenuItem id = "AddinTreeView" 
   label 
= "View AddinTree" 
   class 
= "Addins.AddinTreeView.AddinTreeViewCommand"/> 
 
</Extension> 
</AddIn>
这样,把两个插件的功能模块使用一个插件配置文件来进行配置。同样的,我也可以把几十个功能模块合并到一个插件配置文件中。
我们回过头来看一下,现在我们有了两颗树。首先,插件树本身是一个树形的结构,其次,插件的配置文件本身也具有了一个树形的结构,这个树结构的根节点是系统的各个插件配置文件,其下是根据这个配置文件中的Extension节点的来构成的。
总结一下插件的配置文件格式。首先是 <AddIn>节点,需要指定AddIn的名称、作者之类的属性。其次,在AddIn节点下的<Runtime>节点内,使用<Import …>来指定本插件配置所在的库文件。如果分布在多个库文件中,可以一一指明。然后,编写具体功能模块的配置。每个功能模块的配置都以扩展点<Extension>开始,指定了路径(Path)属性之后,在这个节点内配置在这个扩展点下具体的库文件。每个库文件根据具体不同的实现有不同的属性。
2、结构
1、AddInTree  插件树
插件被组织成一棵插件树结构,树的结构是通过 Extension(扩展点)中定义的Path(路径)来定义的,类似一个文件系统的目录结构。系统中的每一个插件都在配置文件中指定了 Extension,通过Extension中指定的 Path 挂到这棵插件树上。
2、 AddIn 插件
插件是包含多个功能模块的集合。在文件的表现形式上是一个addin配置文件,在系统中对应 AddIn 类进行管理。
3、Extension 扩展点
每一个插件都会被挂到 AddInTree(插件树) 中,而具体挂接到这个插件树的哪个位置,则是由插件的 Extension 对象中的 Path 指定的。在addin 配置文件中,对应于 <Extension> 。例如下面这个功能模块的配置
<Extension path = "/Gdesigner/Workbench/Ambiences">
         
<Class id    = ".NET" class = "IGdesigner.Gdesigner.Services.NetAmbience"/>
 
</Extension>
指定了扩展点路径为 /Gdesigner/Workbench/Ambiences ,也就是在插件树中的位置。
4、包装功能模块
为了方便访问各个插件中的功能模块,给各种功能定义了基本的属性,分别是 ID (功能模块的标识),Name (功能模块的类型。别误会,这个Name 是addin文件定义中的XML结点的名称,ID才是真正的名称),其中Name可能是Class(类)、MenuItem(菜单项)、Pad(面板)等等。根据具体的功能模块。在addin定义文件中,对应于 <Extension> 标签下的内容。例如下面这个定义
<Extension path = "/Gdesigner/Workbench/Ambiences">
         
<Class id    = ".NET" class = "IGdesigner.Gdesigner.Services.NetAmbience"/>
 
</Extension>
<Extension ...> 内部定义了一个功能模块,<Class ...>  表示该功能模块是一个 Class(类),接着定义了该功能模块的 ID和具体实现该功能模块的类名。运行期间将通过反射来找到对应的类并创建出来,这一点也是我们无法在以前的语言中实现的。
再例如这一个定义
 <Extension path = "/ Gdesigner /Views/ProjectBrowser/ContextMenu/CombineBrowserNode">
                
<MenuItem id = "Compile"
                          label 
= "${res:XML.MainMenu.RunMenu.Compile}" 
                          class 
= " IGdesigner. Gdesigner.Commands.Compile"/>
                
<MenuItem id = "CompileAll"
                          label 
= "${res:XML.MainMenu.RunMenu.CompileAll}" 
                          class 
= " IGdesigner. Gdesigner.Commands.CompileAll"/>
                
<MenuItem id = "CombineBuildGroupSeparator" label = "-" />
  .
</Extension>
这个扩展点中定义了三个菜单项,以及各个菜单项的名字、标签和实现的类名。
5Command 命令
正如前文所述,功能模块描述了一个功能模块,而每个功能模块都是一个 ICommand 的实现。最基本的 Command 是  AbstractCommand,根据功能模块的不同对应了不同的 Command。例如 MenuItemCodon 对应 MenuItemCommand 等等。

总结如下,定义一个接口ICommand,声明void DoCommand()方法,新增插件必须实现此接口;单击菜单项或工具栏按钮时需要与主窗体交互,这可以通过在ICommand中定义属性MainForm或在void DoCommand(MainForm frm)中增加方法参数来传递主窗体的引用,这些实现起来倒也简单。接下来的问题是如何通知应用程序新增加了插件呢,答案是使用xml配置文件,怎么组织这个配置文件的结构呢?这个问题其实成了实现插件功能的重点和难点,配置文件中希望说明新增插件的dll位置、类名、插接入主程序的菜单还是工具栏项、插接位置。

 

Command模式应用
    在剖析Command模式之前,先来看一下.NET Framework中的菜单及工具栏处理存在的一些缺点:
1. 无法自动同步更新菜单项与工具项的状态以及相关的行为及特性,需要手动去处理两个事件,从而造成了相同的两个操作需要两次处理,也就造成了需要手动地同时维护多份相同的操作;
2.   无法重用菜单项这部分的功能,因为在dotnet中菜单项的单击需要集成在UI中;
3.   无法很好地扩展,如需要增加或删除菜单项,则需要更改原有的代码及UI部分的设计,从而违反了OCP(开闭)原则。按照OCP原则,当扩展相关的功能时,不应该修改原始的代码,而应该扩展该代码;
4.  无法将相关的消息当作原子状态处理,试想一下,如果一个系统不仅通过菜单、工具栏来操作用户接口,也可通过命令(如Visual Studio就可以通过输入命令而执行相关的操作)来执行,如果不将相关的操作抽象出来的话,维护这个操作的成本太大。并且如果想实现更进一步的控制比如精确到原子状态的权限控制,则很难实现。


我们可以运用Command模式,将菜单项相应的操作抽象出来,定义一个命令接口ICommand,此接口仅具有Run方法,可以作为任何命令的基接口。但是由于菜单还具有一些其它的特性,如是否可见,是否可用,文本,需要执行此菜单项的宿主,所以再定义了一个IMenuCommand接口继承此接口,加入了一些新的特性,并定义了一个实现IMenuCommand

 

口的抽象基类MenuCommand,其它类可以派生此类。
ICommand接口:是一个最基本的接口,代表一个动作的行为;
IMenuCommand接口:派生于ICommand接口,作为菜单项或工具项所实现的行为及特性来使用;
IStatusUpdate接口:描述一个能够更新状态的接口,当对象需要更新状态时将调用此接口,如菜单项需要动态地更新EnableVisible特性;
MenuCommand抽象基类:实现了IMenuCommand接口,任何菜单命令可以从此继承;

CustomCommandBarItem:继承于CommandBar组件的CommandBarButton类,类似于菜单项或工具项,实现了IStatusUpdate接口,以根据IMenuCommand接口的VisibleEnabled属性的改变而相应地更新其Enabled,Visible属性;

 

CustomCommandBarMenu:继承于CommandBar组件的CommandBarMenu类,类似于菜单条。           
    当我们需要创建命令时,只需写相关的类继承于
MenuCommand                 
 
    在这里可以设计组件CommandBarCustomCommandBarItem类的构造函数通过传入一个IMenuCommand类的实例与IMenuCommand关联起来,将单击事件(OnClick方法)委托给ICommand接口的Run方法来执行,其Update方法用于更新状态。CustomCommandBarMenu类中的OnDropDown方法用于当打开菜单条时通过调用IStatusUpdate的Update方法自动更新所有菜单项的状态。

可以将权限控制写在IMenuCommand中的Enabled中,在执行Run方法之前先检测Enabled,如果Enabledfalse则禁止执行,我先前写过一个权限框架,通过AttributeReflect技术来实现权限的自动控制。

原创粉丝点击