下面通过一个简单直观的例子,我们来了解一下如何例用插件树来获得指定的对象,并实现相应的功能。
在SharpDevelop主界面中我们可看到很多的菜单,这些菜单都是在插件文件中指定的,这样也可以方便各种插件自定义菜单项。
ICSharpCode.SharpDevelop工程Src\Workbench\下的DefaultWorkbench.cs文件是主界面的实现。
readonly static string mainMenuPath = "/SharpDevelop/Workbench/MainMenu";
这行代码以常量的形式指定了主界面菜单在插件中的位置。在ICSharpCode.SharpDevelop工程下的ICSharpCode.SharpDevelop.addin文件中我们可看到name属性为"/SharpDevelop/Workbench/MainMenu/*"的节点,"*"表示自定义的部分。这些Path节点下面都Condition和MenuItem标签。Condition表示如何判断其包含的菜单项是否可用。MenuItem则表示具体的菜单项。
void CreateMainMenu() { TopMenu = new MenuStrip(); TopMenu.Items.Clear(); try { MenuService.AddItemsToMenu(TopMenu.Items, this, mainMenuPath); UpdateMenus(); } catch (TreePathNotFoundException) {} }
这里可以看出DefaultWorkbench通过指定插件树的路径,创建了相应的ToolStripItem对象并添加到主菜单的MenuStrip的Items属性中去。下面是MenuService中的相关方法。
public static void AddItemsToMenu(ToolStripItemCollection collection, object owner, string addInTreePath) { AddItemsToMenu(collection, AddInTree.BuildItems<MenuItemDescriptor>(addInTreePath, owner, false)); } static void AddItemsToMenu(ToolStripItemCollection collection, List<MenuItemDescriptor> descriptors) { foreach (MenuItemDescriptor descriptor in descriptors) { object item = CreateMenuItemFromDescriptor(descriptor); if (item is ToolStripItem) { collection.Add((ToolStripItem)item); if (item is IStatusUpdate) ((IStatusUpdate)item).UpdateStatus(); } else { ISubmenuBuilder submenuBuilder = (ISubmenuBuilder)item; collection.AddRange(submenuBuilder.BuildSubmenu(null, descriptor.Caller)); } } } static object CreateMenuItemFromDescriptor(MenuItemDescriptor descriptor) { Codon codon = descriptor.Codon; string type = codon.Properties.Contains("type") ? codon.Properties["type"] : "Command"; bool createCommand = codon.Properties["loadclasslazy"] == "false"; switch (type) { case "Separator": return new MenuSeparator(codon, descriptor.Caller); case "CheckBox": return new MenuCheckBox(codon, descriptor.Caller); case "Item": case "Command": return new MenuCommand(codon, descriptor.Caller, createCommand); case "Menu": return new Menu(codon, descriptor.Caller, ConvertSubItems(descriptor.SubItems)); case "Builder": return codon.AddIn.CreateObject(codon.Properties["class"]); default: throw new System.NotSupportedException("unsupported menu item type : " + type); } }
从第一个方法中我们注意到它调用了AddInTree.BuildItems<T>这个方法。
public static List<T> BuildItems<T>(string path, object caller, bool throwOnNotFound) { AddInTreeNode node = GetTreeNode(path, throwOnNotFound); if (node == null) return new List<T>(); else return node.BuildChildItems<T>(caller); }
此方法查找到路径指定的插件树上的节点,通过节点来再来构造对象。
public List<T> BuildChildItems<T>(object caller) { List<T> items = new List<T>(codons.Count); if (!isSorted) { codons = (new TopologicalSort(codons)).Execute(); isSorted = true; } foreach (Codon codon in codons) { ArrayList subItems = null; if (childNodes.ContainsKey(codon.Id)) { subItems = childNodes[codon.Id].BuildChildItems(caller); } object result = codon.BuildItem(caller, subItems); if (result == null) continue; IBuildItemsModifier mod = result as IBuildItemsModifier; if (mod != null) { mod.Apply(items); } else if (result is T) { items.Add((T)result); } else { throw new InvalidCastException("The AddInTreeNode <" + codon.Name + " id='" + codon.Id + "' returned an instance of " + result.GetType().FullName + " but the type " + typeof(T).FullName + " is expected."); } } return items; }
此方法首先调用TopologicalSort方法,根据addin文件中节点定义的InsertBefore/InsertAfter 属性对Codon对象进行排序。然后是一个递归的过程,调用子节点的BuildChildItems方法。在我们这个创建子菜单的例子中,这个递归就可以实现先创建子菜单,然后再创建父菜单并添加进去的过程。
注意:在"/SharpDevelop/Workbench/MainMenu/*"路径下都MenuItem的节点,那Codon对象Name属性都为MenuItem。Codon对象和MenuItem标签之间的关系请参考上一篇随笔。
下面我们看一下如何通Codon对象创建我们所需要的对象。请看Codon.cs的BuildItem方法。
public object BuildItem(object owner, ArrayList subItems) { IDoozer doozer; if (!AddInTree.Doozers.TryGetValue(Name, out doozer)) throw new CoreException("Doozer " + Name + " not found!"); if (!doozer.HandleConditions && conditions.Length > 0) { ConditionFailedAction action = GetFailedAction(owner); if (action != ConditionFailedAction.Nothing) { return null; } } return doozer.BuildItem(owner, this, subItems); }
Codon对象根据Name属性去查找相应的Doozer对象,在AddInTree对象的构造函数中已经指定MenuItem对应的Doozer
doozers.Add("MenuItem", new MenuItemDoozer());
那么在doozer.BuildItem(owner, this, subItems)中就是调用MenuItemDoozer的BuildItem方法
public object BuildItem(object caller, Codon codon, ArrayList subItems) { return new MenuItemDescriptor(caller, codon, subItems); }
这样我们得到了我们需要的MenuItemDescriptor对象。下面我们再回到MenuService的CreateMenuItemFromDescriptor方法中去。这个方法根据MenuItemDescriptor创建ToolStripMenuItem对象。
我们看一下MenuCommand类,类中codon属性中有addin文件MenuItem标签中定义的所有信息。ICommand menuCommand指定了点击时执行的命令,对MenuItem标签的Class属性,通过
menuCommand = (ICommand)codon.AddIn.CreateObject(codon.Properties["class"]);
我们就可在插件树中取得这个对象了。
通过这个例子我们看到如何通过插件树来构建一个UI对象并且指定事件的响应,可以更好理解AddInTree,AddInTreeNode, Doozer, Codon各自的功用。