
enjoyo
admin
- 组别:管理员
- 性别:
- 来自:
- 积分:388
- 帖子:374
- 注册:
2006-10-19
|
SharpDevelop浅析_1_AddInTree 使用ICSharpCode.Core创建插件支持的应用程序
SharpDevelop浅析_1_AddInTree 使用ICSharpCode.Core创建插件支持的应用程序
Demo运行界面: 使用AddIn好处: AddIn实现分析: SharpDevelop重要概念 Demo代码分析 总结: 相关资料: Demo下载
1、Demo运行界面: 程序初始运行界面如下:
 添加插件界面如下:
 添加插件后界面如下:
 运行环境:VS2005
2、使用AddIn好处 方便扩展,可以看到SharpDevelop几乎是通过插接功能模块组装而成;核心可以不必实现自己的定义,方便地通过接口扩充功能;插件dll可以放在任意位置,对插件使用拷贝、粘贴式的部署。 许多应用程序也使用了一些插件机制,但大多数局限于特定的功能,如扩展菜单或新文件格式。SharpDevelop插件体系的目标是为应用程序提供简单易用而又强大的扩展点,allowing AddIns to extend nearly everything.
3、AddIn实现分析: 简单分析后,实现思路是这样的:定义一个接口ICommand,声明void DoCommand()方法,新增插件必须实现此接口; 单击菜单项或工具栏按钮时需要与主窗体交互,这可以通过在ICommand中定义属性MainForm或在void DoCommand(MainForm frm)中增加方法参数来传递主窗体的引用,这些实现起来倒也简单。 接下来的问题是如何通知应用程序新增加了插件呢,答案是使用xml配置文件,怎么组织这个配置文件的结构呢?这个问题其实成了实现插件功能的重点和难点,配置文件中希望说明新增插件的dll位置、类名、插接入主程序的菜单还是工具栏项、插接位置,或许还希望配置文件更容易被扩展? 这里(http: //www.codeproject.com/cs/library/Net_AddinProjFrmwork.asp)有一个结构不太好的配置文件定义形式(可能也是我们简单分析后会想到的定义方式,可以看出结构固定,且不易扩展),大家可以自行分析下:
 sample.xml 1 <ProjectFrameworkAddin> 2 <AppVer>1</AppVer> 3 <AddinName>Report Addin</AddinName> 4 <ToobarButtonCount>1</ToobarButtonCount> 5 <MainMenu> 6 <Name>Bar Code</Name> 7 <ShortcutKeyIndex>1</ShortcutKeyIndex> 8 <SubMenu> 9 <Name>Bar Code</Name> 10 <ShortcutKeyIndex>1</ShortcutKeyIndex> 11 <LeafMenu> 12 <Name>Test Menu</Name> 13 <FunctionName>AddinFunctionName</FunctionName> 14 <HelpString>Some Status bar text</HelpString> 15 <ToolTip>Some tool tip text</ToolTip> 16 <ToolBarIndex>Addin2Settings.ico</ToolBarIndex> 17 <ShortCutKey>Ctrl + H</ShortCutKey> 18 </LeafMenu> 19 <LeafMenu> 20  .. 21  .. 22 </LeafMenu> 23 </SubMenu> 24 <SubMenu> 25    .. 26    .. 27 </SubMenu> 28 </MainMenu> 29 <MainMenu> 30   31   32 </MainMenu> 33 </ProjectFrameworkAddin>
现在来看看SharpDevelop的AddIn配置文件结构(参见Demo中的Entry.myAddins.Menus.addin):
 Menus.addin 1 <AddIn name = "basic menus" 2 author = "michael zhang" 3 url = "http://www.cnblogs.com/michael-zhang/" 4 description = "基本插件" 5 addInManagerHidden = "true"> 6 <Manifest> 7 <Identity name="michael.addin.basic" version = "@EntryAssemblyVersion"/> 8 </Manifest> 9 <Runtime> 10 <Import assembly = "..\MainForm.dll"/> 11 </Runtime> 12 <Path name = "/michael/BlackText"> 13 <FileFilter id = "Text" name = "Text files" extensions = "*.txt"/> 14  15 </Path> 16 <Path name = "/michael/dymanic_Menus"> 17 </Path> 18 <Path name = "/michael/myMenus"> 19 <MenuItem id = "File" 20 type = "Menu" 21 label = "${res:Demo.Menu.File}"> 22 <MenuItem id = "CmdBlack" 23 label = "Cmd&Black" 24 shortcut = "Control|B" 25 icon = "qq.face1" 26 class = "MainForm.CmdBlack"/> 27 <Include id = "DynamicMenuList" path = "/michael/dymanic_Menus"/> 28 <MenuItem id = "Separator1" type = "Separator"/> 29 <MenuItem id = "Exit" 30 label = "E&xit" 31 shortcut = "Control|X" 32 class = "MainForm.CmdExit"/> 33 </MenuItem> 34 <MenuItem id = "Manager" 35 type = "Menu" 36 label = "&Manager"> 37 <Include id = "AddInManager" path = "/michael/AddInManager"/> 38 </MenuItem> 39 </Path> 40 <Path name = "/michael/myToolbar"> 41 <ToolbarItem id = "CmdBlack" 42 tooltip = "Black command" 43 icon = "qq.face1" 44 class = "MainForm.CmdBlack"/> 45 <ToolbarItem id = "Separator1" type = "Separator"/> 46 <ToolbarItem id = "Exit" 47 tooltip = "Exit the app" 48 icon = "CloseIcon" 49 class = "MainForm.CmdExit"/> 50 </Path> 51 </AddIn> <AddIn>节提供了插件的名称、作者、url、插件描述等信息 <Indetity>提供了插件的唯一标识名称及版本,其它插件的配置文件可以引用该名称,如<Dependency addin=...> <Import>指向该插件引用的dll位置 后面的形如<Pathname="..."><MenuItem>...</Path>的是定义配置文件的核心数据,path节的name属性指明该节下的节点所处(在AddInTree中)的命名层次,节点下的MenuItem, ToolBarItem, FileFilter,Include等统称为Condon,各代表菜单项、工具栏按钮、文件过滤等,这些数据结构可以非常简单地被扩展、解析。 注: addin配置文件中的 label = "${res:Demo.Menu.File}", icon = "qq.face1" 等属性值是指向资源文件的引用,资源文件见Entry项目的StrImgRes.resx
4、SharpDevelop插件树中的重要概念 Condon:代表<Path>节下的一个(一般化)节点(如:<MenuItem><ToolBarItem>...)统称为Condon,该类含ID、Name、InsertBefore、InsertAfter、Conditions、Properties(类似于HashTable的结构)等属性,配置节中的其它属性(除ID,Name,InsertBefore,InsertAfter外,如label,shortcut等)存储在Properties对象中。 Doozer:代表Condon节点的更具体的实例,如MenuItemDoozer, ToolBarItemDoozer, FileFilterDoozer,IncludeDoozer, FileFilterDoozer等,用以创建具体的object对象,可以扩展编写自定义的Doozer。
5、Demo项目代码分析 至此,我们大概能猜到SharpDevelop中的插件机制是怎样的,下面就结合Demo的分析来体验一下SharpDevelop的插件功能: MainForm.FrmMain.cs中使用单件模式获取此类,关键代码如下:
 FrmMain.cs 1 using ICSharpCode.Core; 2  3 // 变量声明 4 const string _BoundProperty = "FormBounds"; 5 Label _lblMsg; 6 MenuStrip _menuStrip; 7 ToolStrip _toolStrip; 8 // 9 void IniFrm() 10  { 11 // 设置窗体位置 12 Rectangle rect = PropertyService.Get<Rectangle>(_BoundProperty, new Rectangle(10, 10, this.Width, this.Height)); 13 this.StartPosition = FormStartPosition.Manual; 14 this.Bounds = rect; 15 this.FormClosing += delegate 16 { 17 // 设置用户属性信息 18 PropertyService.Set<Rectangle>(_BoundProperty, this.Bounds); 19 }; 20 21 _lblMsg = new Label(); 22 _lblMsg.Dock = DockStyle.Fill; 23 _lblMsg.Font = new Font("Arial", 16, FontStyle.Bold); 24 _lblMsg.Text = "App loaded!"; 25 this.Controls.Add(_lblMsg); 26 27 _toolStrip = ToolbarService.CreateToolStrip(this, "/michael/myToolbar"); 28 this.Controls.Add(_toolStrip); 29 30 _menuStrip = new MenuStrip(); 31 MenuService.AddItemsToMenu(_menuStrip.Items, this, "/michael/myMenus"); 32 this.Controls.Add(_menuStrip); 33 } 34 public void DrawMsg(string msg,Color color) 35  { 36 _lblMsg.Text = msg; 37 _lblMsg.ForeColor = color; 38 } 主窗体的类中声明了Label, MenuStrip, ToolStrip分别用以显示文字、菜单、工具栏。菜单、工具栏对象的获取通过ICSharpCode.Core内置类仅用简单的两行代码实现。此类中公开的DrawMsg(...)方法用以向扩展菜单、工具栏按钮等提供公开可调用的功能。创建菜单、工具栏的两个方法中的一个重要参数是路径参数(分别是"/michael/myToolbar"和"/michael/myMenus"),在前面的配置文件代码中可以找到相关定义节,其中相关节点一个重要属性是class, 该属性指定了(菜单、工具栏按钮的)相关类,其实现代码如下(MainForm项目的Commands.cs): 注: FrmMain类的IniFrm()方法中前几行代码用以设置启动窗体的大小和位置,使用到了ICSharpCode.Core.PropertyService类,该类将配置文件保存在"C:\Documents and Settings\michael\Application Data\michael'sadd-in test\myCfgParas.xml",其中[michael]是计算机名;[michael's add-intest]是应用程序名称,在程序启动时创建CoreStartup实例时指定;[myCfgParas.xml]由属性CoreStartup.PropertiesName指定(详见后面Main()中的代码)。
 Commands.cs 1 //using  2 namespace MainForm 3  { 4 public class CmdBlack : AbstractMenuCommand 5 { 6 public override void Run() 7 { 8 FrmMain frm = (FrmMain)this.Owner; 9 10 StringBuilder sBuilder = new StringBuilder(); 11 ArrayList alDatas = AddInTree.BuildItems("/michael/BlackText", null, true); 12 foreach (string str in alDatas) 13 sBuilder.AppendLine("suported types: " + str); 14 15 frm.DrawMsg(sBuilder.ToString(), Color.Black); 16 } 17 } 18 19 public class CmdExit : AbstractMenuCommand 20 { 21 public override void Run() 22 { 23 FrmMain frm = (FrmMain)this.Owner; 24 if (MessageBox.Show("Sure to exit?","Info:",MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question) == DialogResult.Yes) 25 frm.Close(); 26 } 27 } 28 }
可以看到插件类必须实现ICommand接口或继承AbstractMenuCommand类,ICommand接口定义的Owner属性返回该对象的拥有者Object,在此例子中即FrmMain对象,CmdBlack类中通过((FrmMain)Owner).DrawMsg(...)向主窗体发出功能命令。 至此,只剩下对ICSharpCode.Core进行必要的初始化配置了(参见Demo中的Entry项目Program.cs中的static void Main()函数):
 void Main 1 LoggingService.Info("Application start"); 2 Assembly asm = Assembly.GetExecutingAssembly(); 3 FileUtility.ApplicationRootPath = Path.GetDirectoryName(asm.Location); 4 ResourceService.RegisterNeutralStrings(new ResourceManager("Entry.StrImgRes", asm)); 5 ResourceService.RegisterNeutralImages(new ResourceManager("Entry.StrImgRes", asm)); 6 7 LoggingService.Info("Starting core services "); 8 CoreStartup coreStartup = new CoreStartup("michael's add-in test"); 9 coreStartup.PropertiesName = "myCfgParas"; 10 coreStartup.StartCoreServices(); 11 // 在指定文件夹中搜寻插件的配置文件 12 coreStartup.AddAddInsFromDirectory(Path.Combine(FileUtility.ApplicationRootPath, "myAddIns")); 13 // AddinManager 插件的属性:保存用户禁用的插件信息 14 coreStartup.ConfigureExternalAddIns(Path.Combine(PropertyService.ConfigDirectory, "AddIns.xml")); 15 // AddinManager 插件的属性:保存用户安装的插件信息 16 coreStartup.ConfigureUserAddIns(Path.Combine(PropertyService.ConfigDirectory, "AddInInstallTemp"), 17 Path.Combine(PropertyService.ConfigDirectory, "AddIns")); 18 coreStartup.RunInitialization(); 19 try 20  { 21 LoggingService.Info("Running application "); 22 Application.Run(MainForm.FrmMain.Instance); 23 }catch { 24 
注: Demo项目引用的AddinManager也是SharpCode.Core中的一个插件实现,查看AddinManager.Addin,可见其定义了菜单项、新界面(点击新菜单时Run()方法中定义弹出的新窗体)、新界面的上下文菜单,上下文菜单中使用<Condition>配置菜单项何时可用…… Demo项目中扩展菜单的例子参见Demo中的ExtenalMenus工程中的Command.cs和ExternalMenu.addin,该项目实现了两个新的菜单项,并且实现了一个自定义的Doozer。
6、总结: a, ICSharpCode.Core默认实现的Doozer Class 根据配置文件的声明由System.Reflection创建出相关对象 FileFilter 创建出路径后缀名的过滤选项提供给OpenFileDialog或是SaveFileDialog使用 Include 向addin tree引进一个(使用item属性)或多个(使用path属性)子项 Icon 用以创建文件类型与图标间的关联 MenuItem 创建菜单项 type可为:Seperator, CheckBox, Item/Command, Menu, Builder ToolBarItem 创建工具栏按钮荐 type可为:Seperator, CheckBox, Item, ComboBox, DropDownButton
b, 配置文件中引用预定义资源格式 ${res:ResourceName} 引用ResourceService系统资源 ${property:PropertyName} 引用PropertyService中的属性 ${env:VariableName} 引用系统变量 ${exe:ProperName} 引用整个程序集的属性
c, Condition [略(有待进一步分析)]
7、相关资料: 《Dissecting a C# Application Inside SharpDevelop.pdf》 SharpDevelop源代码(\src\ 和 \samples\ICSharpCode.Core.Demo\) http://www.sharpdevelop.com/OpenSource/SD/Default.aspxhttp://www.codeproject.com/csharp/ICSharpCodeCore.asp
 enjoyo 最后编辑于 2008-05-25 22:22:49
|