SharpDevelop源码剖析(三)————使用插件树

下面通过一个简单直观的例子,我们来了解一下如何例用插件树来获得指定的对象,并实现相应的功能。

在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各自的功用。

原文地址:https://www.cnblogs.com/SharpDeveloper/p/1743665.html