前言 在之前的两篇随笔中,我介绍了Add-In的运行机制,这样对Add-In的事件、生命周期、与VS如何交互可以有个基本的了解了。现在是时候看看如何在VS中完成一些操作,这才是Add-In开发的目的所在。 一般的,Add-In应当提供一些界面元素,这样用户可以进行某些操作,比如在主菜单内添加一个菜单项,或者在编辑器的上下文菜单内添加一个菜单项,在本文中就来看看如何实现这些。 关于命令(Command) 考虑一个极为常见的场景:在编写代码的过程中,选中一段文本,点击Edit->Copy(或工具栏按钮)或者按下Ctrl+C,我们可以把选中文本拷贝到剪贴板,这个过程的背后发生了什么? 是什么完成的拷贝操作呢?答案是命令。可以认为命令就是某个特定的功能,如Copy、Paste、Cut等等。VS本身就内置大量的命令(有数千个之多),而上面说到的菜单项、工具栏或快捷键则执行了这些命令。通过Tools->Options菜单可以查看命令列表: |
||||||||
图1:VS的命令列表 值得注意的是,执行命令并非必须通过界面元素,比如在命令窗口中: |
||||||||
图2:在命令窗口执行命令 这样也可以执行同样的命令。现在我们知道存在多种方式来执行命令,即菜单、工具栏、快捷键或命令窗口,这里统称为触发者。在VS中,触发者与命令是分离开来的:用户通过触发者来执行命令,而命令负责检查自身的状态(名称、是否可见、是否可用等等)并执行。这意味着,命令可以对应一个或多个菜单项,也可以不对应任何菜单项。 另一方面,对于同一个命令,比如Edit.Copy,仍然可能有不同的情况。在文本编辑器内和在解决方案管理器内的Edit.Copy命令执行内容并不相同。这里有一个命令目标(Command Target)的概念,VS将命令转向给了命令目标,而命令目标按自己的实现来执行该命令。总结下来就是:
命令是个“乖孩子”,首先是我们让它做什么它就做什么,第二就是它不会单独出门,它会跟其它同伴待在一块儿,它们都被放在命令栏中。比如主菜单中File下的那些菜单项。 既然这样,如果希望向已经存在的命令栏内添加命令,就得首先找到命令栏才能添加。现在来看一个例子,向Tools菜单中添加一个菜单项。 添加一个新命令 使用Add-In向导新建一个Add-In,名字定为NEnhancer,意为对VS进行增强:),以后关于Add-In的例子都会放在这里面。注意要选中向Tools菜单添加一个菜单项。在Connect类的OnConnection方法中可以看到添加菜单项的代码如下: C# Code - 向Tools菜单添加新项
try { // 如果需要向其它菜单栏添加命令,将Tools改为其它名称(要使用英文的) // 关于这些菜单栏的名称,可以查看CommandBar.resx文件 string resourceName; ResourceManager resourceManager = new ResourceManager("NEnhancer.CommandBar", Assembly.GetExecutingAssembly()); CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID); if(cultureInfo.TwoLetterISOLanguageName == "zh") { System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent; resourceName = String.Concat(parentCultureInfo.Name, "Tools"); } else { resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools"); } toolsMenuName = resourceManager.GetString(resourceName); } catch { // 如果没能找到,就使用英文版本的名称 toolsMenuName = "Tools"; } // 获取VS的主菜单 CommandBar menuBarCommandBar = ((CommandBars)_applicationObject.CommandBars)["MenuBar"]; // 获取Tools菜单 CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName]; CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl; // 如果需要添加多个菜单项,这个try/catch代码块可以多次使用 // 但别忘了更新QueryStatus/Exec这两个方法 try { // 向Commands集合添加命令 Command command = commands.AddNamedCommand2(_addInInstance, "NEnhancer","NEnhancer", "Executes the command for NEnhancer", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton); // 向菜单中添加新项 if((command != null) && (toolsPopup != null)) { command.AddControl(toolsPopup.CommandBar, 1); } } catch(System.ArgumentException) { // 如果引发异常,可能是由于同名的命令已经存在了,可以忽略该异常 }
C# Code - Method Signature
Command AddNamedCommand2(AddIn AddInInstance, string Name, string ButtonText, string Tooltip, bool MSOButton, object Bitmap, ref object[] ContextUIGUIDs, int vsCommandStatusValue, int CommandStyleFlags, vsCommandControlType ControlType);
从本文开头举的例子可以看到,对于VS内置的菜单项,它们对应的命令名称是有一定规律的,即按照菜单的嵌套关系,如Edit.Copy,表示Edit下的Copy项(有空格的话要去掉)。那么对于我们的Add-In来说,也是有规律的,即Namespace.ClassName.CommandName,这里就是NEnhancer.Connect.NEnhancer。 命令的名称应当反应它的意图,所以这里把AddNamedCommand2的第二个参数改为CommandViewer。前面提到过,VS中有很多命令,它们按照命令栏来组织,这里就做一个Add-In来查看所有的命令和命令栏,方便以后的开发。 命令的执行 现在命令和菜单项是添加了,当用户点击菜单时,如何处理它呢?要实现IDTCommandTarget接口,一旦实现了它,我们的Connect类就成为一个合格的命令目标(Command Target)了。确切地说,当用户点击菜单时,VS要执行它的Exec方法,Exec方法有如下几个参数: C# Code - Method Signature
void Exec(string CmdName, vsCommandExecOption ExecuteOption, ref object VariantIn, ref object VariantOut, ref bool Handled);
命令的状态 有的命令并不总是可用,比如我们开发了一个Add-In,它专门针对于文本编辑器,如果没有任何文件打开,它就不应该是可用的。这里要使用IDTCommandTarget接口的另一个方法QueryStatus,它的参数有:
好了,了解了命令的概念,也知道如何添加、执行命令了,剩下的就是实现命令的功能了。这里要新建一个窗体,添加一个TreeView来显示命令栏和命令: |
||||||||
图3:CommandBarViewer窗体 要查看该窗体的具体代码,可以在文章末尾处下载代码。 现在只要在Connect类中稍作修改显示窗体: C# Code - 执行命令
执行已有命令 先看下图。 |
||||||||
我们可以关闭当前文档,也可以关闭其它文档,就是不能关闭所有文档。有时候还是需要这个功能的,我想你应该用过Window->Close All Documents菜单项吧?它实现的正是我们所需要的,现在考虑如何使用这个已有的命令。首先得找到这个命令,按前面的规则,我们到Tools-Options里面去找(见图1),我们可以试着输入window.close,这时就可以看到了:Window.CloseAllDocuments。 接下来跟前面例子类似,首先要添加一个命令,前面是把命令添加到了主菜单栏(通过“MenuBar”获取命令栏),现在要添加到另外一个命令栏:“Easy MDI Document Window”,这名字有些奇怪(我是在所有的CommandBar中搜了好多次才找到的),在刚才添加Tools菜单项的代码下面添加如下代码: C# Code - 添加CloseAllDocuments命令
C# Code - Execute Command
_applicationObject.ExecuteCommand("Window.CloseAllDocuments", string.Empty);
|
||||||||
赶紧试一试吧!这里再提一下,在测试、调试完成之后,如果要发布Add-In,最简单的方法是将dll和.AddIn放在[My Documents Path]\Visual Studio 2008\Addins,同时把那个用来测试的.AddIn文件(如NEnhancer - For Testing.AddIn)移掉。 代码下载请点击这里。 我们身在何处? 本文首先介绍了命令和命令栏的概念,正是通过命令VS才可以与Add-In进行交互。然后通过两个例子解释了如何添加、执行命令,以及如何执行VS内置的命令,接下来我们就有办法操作VS的方方面面了:解决方案、项目、文档、代码等等,敬请期待:-) |