这里所谓的动态菜单是指动态指定某些菜单是否显示是否可用,而不是指动态的创建一个新的菜单。就我现在所知,还没有找到通过代码生成菜单而不依赖于vsct的方法。在日常的情形中我们经常能够见到这些需求,比如当用户右击某个解决方案时希望显示“关闭该解决方案”,而在.cs文件上单击时候就不要显示这个选项。下面我们来实现这个需求。
方法一
新建一个带menu项目后,打开vsct文件并在Commands标签之后插入VisibilityConstraints标签。具体的内容如下图所示
你可能已经猜到了这句话的意思就是在没有解决方案打开的情况下,cmdidMyCommand按钮显示。一旦加载了解决方案这个按钮就不可见了。其中的context属性我们可以在vsx1中提到的vsshlids.h文件中找到。同时你也能看到其他你可以使用的context信息。
然后在button标签内插入CommandFlag标签,表明这是一个动态的菜单。
至此,你不需要多写一行c#代码就可以完成菜单的动态显示了。不过这种方法也存在一些缺陷,比如只能使用预定义的条件,比如UICONTEXT_NOSolution不能灵活的自定义其他的条件,而且这种方式只能控制菜单的隐藏与显示,如果我想disable这个菜单就办不到了。
方法二
这个方法弥补了上面说到的一些缺点。具体的做法如下:
- 和方法一一样,在button标签中添加CommandFlag标签,并设置为DynamicVisibility。
- 在创建代码
OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (null != mcs) { // Create the command for the menu item. CommandID menuCommandID = new CommandID(GuidList.guidVSPackage1CmdSet, (int)PkgCmdIDList.cmdidMyCommand); OleMenuCommand menuItem = new OleMenuCommand(MenuItemCallback, menuCommandID); menuItem.BeforeQueryStatus += new EventHandler(menuItem_BeforeQueryStatus); mcs.AddCommand(menuItem); }
这里和原来创建的区别在于OleMenuCommand 对象。原来添加菜单的代码中使用的是MenuCommand对象,而这个OleMenuCommand 对象继承于MenuCommand并实现了一些特殊的功能和事件。比如说我们要使用到的BeforeQueryStatus事件。这个事件的意思就是每次vs进行状态更新的时候都会执行到这个方法,借助于这个机会你可以在该事件中对你的菜单进行动态的设置。事件方法如下:
OleMenuCommand menuCommand = sender as OleMenuCommand; if (menuCommand != null) { IntPtr hierarchyPtr, selectionContainerPtr; uint projectItemId; IVsMultiItemSelect mis; IVsMonitorSelection monitorSelection = (IVsMonitorSelection)Package.GetGlobalService(typeof(SVsShellMonitorSelection)); monitorSelection.GetCurrentSelection(out hierarchyPtr, out projectItemId, out mis, out selectionContainerPtr); IVsHierarchy hierarchy = Marshal.GetTypedObjectForIUnknown(hierarchyPtr, typeof(IVsHierarchy)) as IVsHierarchy; if (hierarchy != null) { object value; hierarchy.GetProperty(projectItemId, (int)__VSHPROPID.VSHPROPID_Name, out value); if (value != null && value.ToString().EndsWith(".cs")) { menuCommand.Visible = true; } else { menuCommand.Visible = false; } } }
这里代码的具体意思不解释了,因为这篇文章的重点不在这里。这段代码的效果就是只有但你在.cs结尾的文件上右击菜单的时候才会见到菜单,否则就不会见到。
最后,由于vsx的动态加载机制,现在创建的动态菜单并不会显示。需要在package上面添加一个AutoLoad标签,让vs在启动的时候就自动加载我们的插件。这样动态菜单才能正确显示出来。
[ProvideAutoLoad("2c2dd76c-69d1-4889-b82f-b2f2e86c3eeb")]
这种方法相比较于上一种给我们提供了更大的灵活性和可控性。所以,在使用的时候肯定一般用第二种了。