原创 插件系统的文档(译文)

2009-8-15 22:08 2397 5 5 分类: 软件与OS
1 关于插件系统的文档

这是描述SharpDevelop插件系统的文档.如果你打算为SD写一个插件,你应该阅读一下“AddInBuildingGuide” 学习如何组织你的工程。

本文档包括ICSharpCode.Core中通用的插件系统和SD的公共扩展点(common extensions points for SharpDevelop).

2 AddIn Tree

一个插件安装包(译者注:其实就是.sdaddin文件,这个文件是由.addin和.dll压缩后把扩展名改为.sdaddin而得)一般至少包括两个文件:插件描述文件即.addin,和动态链接库.dll,当然也可能包含其他的文件和类库。

插件描述文件包含了插件的描述信息,并且在SD启动的时候会被加载进来,根据文件的内容,SD会把它放到插件数里。

插件数是一棵"binds them all"(译者注:不知道啥意思,估计是说绑定所有插件的意思)的树。. 它的结构像文件系统,如果你想访
问节点SubNode2,就必须像这样指定节点的位置:/Path1/SubPath1/Node1/SubNode2.我们看到Node1就像一个路
径,但是我们稍后就会看到路径与节点的不同。

从现在开始,我们会把那些路径中包含定义和行为的部分称为节点。(we will just say that nodes are paths that contain definitions of behavior).

对于插件树最常用的一种用法是扩展菜单和Toolbar。当SD想要创建一个菜单或Toolbar的时候,它就会明确的指定插件的路径。路
径 “/SharpDevelop/Workbench/MainMenu” 下包含主菜单的项。路径“/SharpDevelop
/ViewContent/Browser/Toolbar” 包含Toolbar中用于导航的项 (比如Startpage, 帮助等.).




2.1 AddIn Definition

插件数中的每个节点都包含一个Codon(密码子). 在ICSharpCode.Core 中有一个AddInTreeNode的类,它包含一
个Codon的属性,对于路径来说它的值是Null,对于一个节点的Codon来说它就是插件的扩展点
(points to a Codon instance for nodes).

让我们看看如何用XML定义一个含Codon的节点吧:

<MenuItem id = "Build"

 label = "${res:XML.MainMenu.BuildMenu.BuildSolution}"

 shortcut = "F8"

 icon = "Icons.16x16.BuildCombine"

 class = "ICSharpCode.SharpDevelop.Project.Commands.Build"/>

当插件树加载后,它会根据Codon中的(译者注:即上面的MenuItem)class attribute所指向的类创建一个对象,这个对象
的Name属性值被设为 “MenuItem”,ID属性值设为“Build”.其他的attributes中的信息被保存在一个叫做
“Properties” 的数据包里(就像一个Hashtable).

对于MenuItem的Codon包含的信息有:label, shortcut, icon 和当此menu被点击是要执行的类的全名称(译
者注:即上面的class attribute,每个class都会实现ICommand接口,当按钮被点击时会调用接口的Run函数)。



对于插件树比较重要的是把所有的插件整合在一起。例如, 插件startpage的插件文件StartPage.addin包含:

<Path name = "/SharpDevelop/Workbench/MainMenu/View">

<MenuItem id = "ShowStartPage"

 insertafter = "ViewItemsSeparator"

 insertbefore = "FullScreen"

 label = "${res:XML.MainMenu.ViewMenu.ShowStartPage}" 

 icon = "Icons.16x16.BrowserWindow"

 class = "ICSharpCode.StartPage.ShowStartPageCommand"/>

</Path>

路径"/SharpDevelop/Workbench/MainMenu/View" 在SharpDevelop 的主插件文件(译者
注:SharpDevelop.addin)和 StartPage's AddIn文件里都被定义了. 当加载这些插件文件
时, ICSharpCode.Core 会把这些文件里有相同路径的内容组织在一起并插到树中。上面的codon的
insertafter and insertbefore 两个attributes是比较特别的,它们指定了这一个MenuItem将要插入的位置。
2.2 The Runtime Section

每个插件定义文件都包含一个定义插件对象的类,我们可以在插件文件的头部的runtime 段找到关于类定义的信息,每个Codon都会根据这个
runtime段定义的信息来创建插件对象。插件文件头部包含如下
attributes: Name, Author, Copyright, URL of the addin homepage, Description, Version. 它
们的值被保存在AddIn类中的properties里面(译者注:上面提到的那个类似Hashtable的数据包)。

The values are stored in properties of the AddIn class.

Runtime段像这样定义的:

<Runtime>

<Import assembly = "CSharpBinding.dll"/>

<Import assembly = ":ICSharpCode.SharpDevelop"/>

</Runtime>

Import元素的数据被保存在AddIn类里面的RunTimes属性里.AddIn类有一个函数 “CreateObject”. 当用
MenuItem的Class创建一个对象时CreateObject函数就会被调用.一般情况下, 当item被点击时CreateObject才被调
用(译者注:并不是每个插件被加载时就会立即创建插件中class的对象,而是当此插件被用到时才创建的。比如MenuItem,如果你从来就不点击一
下,那么这个类就不会被创建。). CreateObject 会遍历所有导入的assemblies(按照Import的顺序)来尝试创建此插件对
象。 

被导入的assemblies 只有在CreateObject 才第一次被加载进来.这对于SD的启动性能是一个很大的改进。

CreateObject查询导入的assemblies的所有类来创建对象,当你想要使用SharpDevelop 的assembly中的类
时,(不如一个通用的Command撤销等。), 你也必须导入这个assembly. 当这个assembly被多个插件文件引用时同一个
assembly不会被加载对次。既然插件可以放在任一个目录下而不是必须在一个指定的路径,那就不必明确的指定
ICSharpCode.SharpDevelop.dll的相对路径,因为有一个特别的方式来引用SD在主目录下的assemblies,当
assembly attribute以一个冒号开
始 (<Import assembly = ":ICSharpCode.SharpDevelop"/&
gt;), SharpDevelop 会以“Assembly.Load”的方式来加载此assembly,而不是一般的
“Assembly.LoadFrom”.这就是为什么不必须SD的assembly明确路径,但是和一般的导入方式有一点重要的区别:那就是这种情况下
不需要加”.dll”扩展名。



在runtime段里也可以包含doozer和condition的定义, SD会读取doozer和conditions部分的信息并注册到SD里。
2.3 Doozers

现在有这样一个问题: 一个Codon是如何变成 System.Windows.Forms.MenuStripCommand的?

这些就是doozers完成的:Doozers是一些helper类,它们会根据codons来生成一些对象。

下面是MenuItemDoozer的一个简化版本:

public class MenuItemDoozer : IDoozer

{

 // More on HandleConditions in the conditions section.

 public bool HandleConditions { get { return true; } }



 public object BuildItem(object caller, Codon codon, ArrayList subItems)

 {

 if (codon.Properties.Contains("type"))

type = codon.Properties["type"];

 else

type = "Command";

 switch (type) {

case "Separator":

 return new MenuSeparator(codon, caller);

case "CheckBox":

 return new MenuCheckBox(codon, caller);

case "Item":

 return new MenuCommand(codon, caller);

case "Command":

 return new MenuCommand(codon, caller);

case "Menu":

 return new Menu(codon, caller, subItems);

case "Builder":

 return codon.AddIn.CreateObject(codon.Properties["class"]);

default:

 throw new NotSupportedException(type);

 }

 }

}

其中的类“MenuCommand”, “MenuCheckBox” 是 SharpDevelop中override系统标准的“MenuStripCommand”的类. 它们会从codon的属性里取出label, icon 和shortcut等信息。

当一个MenuCommand被点击时,MenuCommand 会调用 “codon.AddIn.CreateObject()”来创建一个对象,把此对象转换成ICommand 接口后,再调用此接口的Run函数.



How to add custom doozers

核心的doozers会把SD直接创建加载的. 但是如果你想在你的插件里定义自己的doozer该如何做呢?要想完成这一点,你可以把你的doozer 写到<Runtime> 段里。比如:

 <Import assembly = 'MyAddIn.dll'>

 <Doozer name='MyDoozer' class = 'MyAddIn.MyDoozer'/>

 </Import>

一般情况下,这个doozer (和assembly)在第一次使用时就会被加载进来。
2.4 Building Items in the AddIn Tree

这一部分将讨论插件如何创建它的扩展点,这样你的插件又可以被其他的插件扩展。

Context menus:

静态函数 MenuService.CreateContextMenu可以用来创建一个ContextMenuStrip.比如:

listView.ContextMenuStrip = MenuService.CreateContextMenu(this, "/MyAddIn/SomePath/ContextMenu");

把this作为参数传入,这样这个路径下的插件就可通过Owner属性来访问到this。在大多数情况下,它会把Owner转换成它实际的类型,这样就可访问Owner的成员了。

Toolbars:

Toolbars 和context menus很相似.将ToolbarService.CreateToolStrip 返回的ToolStrip添加到你的控件中,例如:

toolStrip = ToolbarService.CreateToolStrip(this, "/MyAddIn/SomePath/Toolbar");

toolStrip.GripStyle = ToolStripGripStyle.Hidden;

Controls.Add(toolStrip);

参数this的使用方式和Contextmenu一样,我们可以这样使用:

public class GoBack : AbstractCommand {

 public override void Run() {

((HtmlViewPane)Owner).WebBrowser.GoBack();

 }

}

Your own objects:

定义自己的对象也同样很简单.首先你需要为你存储在插件树的对象定义一个公共接口。假设你的插件执行一些动作并且有给其它插件通知的能.我们像这样定义接口:

public interface IActionNameListener

{

 void DoAction(MyDataClass data);

}

如果这个接口在.NET Framework或SharpDevelop中已经存在,你最后直接引用它们而不是自己从头完成。

如果你真的创建了自己的接口, 扩展了你的插件的插件们就需要引用这个接口的assembly。不过这可能会导致版本的问题。

你可以这样访问这些items:

ArrayList list = AddInTree.BuildItems("/MyAddIn/ActionName", this, false);

foreach (IActionNameListener obj in list) {

obj.DoAction(data);

}

这样你就能够取到这个路径下的所有对象,这些对象的顺序也是和你在 insertbefore/insertafter 指定的顺序是一致的。第
二个参数和前面的使用方式一样的。这个Owner会被传给doozer,doozer 再将它传给创建的对象。当你使用自己定义的对象时,你可以定义自己
的doozer,但并非必须。ICSharpCode.Core 以经包含一个doozer,它能够利用反射根据一个类的无参构造器创建一个对象。
这是我根据SD官方提供的“AddInTree.rtf"翻译的,

安装完SD后,你可以在可执行文件的..\doc\technotes目录下找到它,

如果能完全理解这篇文章,其实想学习SD就不需要找其它资料了。

文中有多处是根据我对SD的理解而意译的,

有翻译不准确的地方,请指正。








PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
5
关闭 站长推荐上一条 /3 下一条