下面通过一个简单直观的例子,我们来了解一下如何例用插件树来获得指定的对象,并实现相应的功能。
在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各自的功用。