在Visual Studio.NET中使用自定义插件最大化您的生产力
原文连接:http://msdn.microsoft.com/msdnmag/issues/02/02/VSIDE/print.asp
作者:Leo A. Notenboom
翻译:张翔 kilxy@hotmail.com
这篇文章假设您已经熟悉C#, Visual Basic .NET和CLR
难度程度 1 2 3
摘要
对于开发者来说,不管多么强大的集成开发环境(IDE)都需要具有自定义插件的功能。基于这个原因,Visual Studio. NET IDE提供了一个可扩展的、添加方便的而且还没有数量限制的自定义插件功能。这个自定义插件可以用Visual Basic、C、C#或则其它的.NET的使用的语言来编写。这篇文章解释了如何在Visual Studio. NET中增加一个自定义插件的例子。这个例子将演示如何通过自定义插件完成一个自定义的文本编辑功能,其中实现了两个功能,一个是在文本中简单的插入当前的日期,另一个是更加复杂的功能是实现重新格式化一段文本。最后您将学习如何在选项对话框中增加一个页。
如果您开始使用Microsoft Visual Studio .NET你应该知道他充满了新特征和技术。虽然,你肯能认为集成开发环境(IDE)是一个编写代码的简单的文本编辑器,它已经非常强大了。它提供的框架支持开发工具的插入而且是单一的无缝的可开发的。当然,IDE并不能满足所有的人,你或则我所要的功能可能并没有在这里。幸运的是IDE已经提供了可扩展的功能,它允许您扩展你所需要的任何东西。
开始
插件和宏是扩展IDE的两种方法。宏是可以被记录的和被立即执行的。因此,这是一种探索对象模型的强大的方法。宏使用一个以 .vsmacros 文件的形式来分布,通过双击这个文件来加载到宏编辑器中。宏在加载可用之后,宏就可以被使用者非常容易的修改。
另一方面,插件是编译的而且在分布之后就不能被修改。这样保护了你的知识财产。通过插件,你可以创建工具窗口这些操作对于Visual Studio.NET就像操作本地的功能一样。插件可以动态的改变菜单、工具条事件的状态和在帮助对话框中增加信息。插件是通过微软的安装文件(.MSI)来进行部署的,它可以轻易的安装和通过控制面板中的添加/删除程序对话框进行卸载。
创建一个插件的步骤在Visual Studio.NET的在线帮助中已经隐藏起来,同样也在Visual Studio.NET Automation Examples Web site中被隐藏了。我在这里不想介绍每一个的具体过程,但是可以回顾一下我创建一个插件所选择的过程。
创建一个插件首先要创建一个项目,在新建项目(New Projects)对话框其他项目(Other Projects)下面,你会发现扩展性项目(Extensibility Projects)。选择Visual Studio.NET外接程序(Visual Studio.NET Add-in)。图一显示了在一个点上的新建工程对话框。点击确定按钮开始向导。
图1创建一个新工程
对于我的插件,向导中的前三步都比较容易。第一步,我选择了C#语言作为插件的开发语言。第二步,我选择了Microsoft Visual Studio.NET作为插件的主机(host)。在您的插件中你当然可以包括VSMacros IDE。第三步,插件命名为“Text Editing Utilites”,并给出了适当的描述。
图2
在向导中的第四步,如图2所示,需要一些操作。
l 选择“是的,创建工具菜单项”。
l 确定“我的外接程序不提供……”没被选中。这个例子中我没有准备提供一个模式用户界面,但是如果你真的想更多的扩展你的插件功能,你可以选择此项。
l 确定“我希望我的外接程序在宿主程序启动时加载”没被选中。这样可以使调试是容易一些。插件的用户可以在以后的插件管理的操作中改变此选项。
l 我选择了“我的外接程序不仅仅……”选项。在我的计算机中我是真正的用户,同样我确认这个例子也是为了更多的开发者。这个选项仅仅是改变插件已经注册的用户。
第五步,你可以包含一些帮助信息。选中复选框,在下面输入任何的联系信息。现在,你可以完成向导,开始我们的插件。
下面列出了Connect对象的方法。这些方法是不完整的,可以在Connect.cs文件找到该类。这些你的新建项目的一部分。
l Connect::Connect。这是构造函数,在这里你可做简单初始化。
l Connect::OnConnection。这个方法是在IDE真正加载您的插件时调用。在这里你可以初始化你的插件,而且必须为IDE提供命令信息、制定键盘绑定等等。
l Connect::QueryStatus。这个方法是在适当的时候IDE确定当前状态时被调用。
l Connect::Exec。这个方法是IDE真正的执行命令是被调用的。
现在,所有的基础内容在这里,让我们开始开发一个简单的插件例子。
一个简单的编辑功能:插入日期
我创建了一个简单插入日期的功能,确切的功能是:在当前光标的位置或则当前选择文本的地方插入当前日期。这是Visual Studio .NET中已经包含了一个用宏实现此功能的例子。所以你可以看见通过宏和插件两种不同的方法实现同样的功能。这个例子非常简单,只需要你修改一个在前面列出来的那个Exec函数。通过前面章节我已经创建了一个基本插件,这个插件的名字是“TextUtil”更加确切的说是“TextUtil.Connect.TextUtil”。在Exec函数中我替换了向导自动生成的代码。
handled = true;
替换为
handled = InsertDate();
增加如下InsertData()函数,可以在Connect对象中的任何地方增加该函数。
private bool InsertData()
{
if(applicationObject.ActiveDocument!= null)
((TextSelection)applicationObject.ActiveDocument.Selection).Text
=DateTime.Now.ToString("yyy-MM-dd");
return true;
}
InsertData函数使用了System.DateTime和Sytem.String对象。关于System.String的介绍可以参看帮助。在这里我注意到在OnConnection中关于CommandBars的三行代码,这三行代码是在以后运行的过程中要在工具菜单中建立菜单项。
除了名称插件已经完成。按F5来试验插件。一个新的IDE实例运行,在这个实例中这个插件将出现在工具|外接程序管理对话框中。可以通过选中再外接程序管理对话框中最左面的复选框来加载插件。现在你可以打开任何一个文本文件,然后再Visual Studio .NET的命令窗口中输入“TextUtil.Command.TextUtil”在当前光标的位置将插入当前日期。事实上在你完成输入之前自动变异已经天填充了命令。
当你按下F5开始运行一个IDE新的实例的时候,你的插件是在调试模式下。我们可以在调试模式下发现所有异常的信息。在普通的操作中出现了任何的异常,他们都可能不可用。尽可能的得到异常的信息,那么错误的原因就会尽早的发现。
改变命令名称
向导已经为菜单命令指定了默认的名字(TextUtil)。因此这个名字是不具有特色描述的,我一会会增加更多的菜单命令、改变菜单命令名称。同时,因为我在改变命令名称的时候遇到过名称混乱的情况,所以我们必须确认我们的插件不会有相同的命令名称。
第一个改变是在OnConnection函数中调用AddNameCommand之处
Command command = commands.AddNamedCommand(addInInstance,
"InsertDate",
"Insert Current Date",
"Insert Current Date",
true, 59, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported
+(int)vsCommandStatus.vsCommandStatusEnabled);
在QuertyStatus中更改如下:
if(commandName.ToLower() == "textutil.connect.insertdate")
向导在初始运行加载之前应经注册了插件信息,但是名称的改变影响了注册信息。这就意味着在增加了插件功能的时候我们需要从新生成插件项目和安装项目。然后再运行安装程序。
图3 运行插件(原文:Running the Macro 译者:可能是错误)
现在“TextUtil.Connect.InsertDate”命令可以按照我的想法工作了(图3)。但是,他是怎么工作的呢?让我们继续往下看。
插件是怎么工作的?
在前面章节中给你们演示的InsertDate代码是相当简单的,而且自动完成功能让人感觉不可思议。
我将从对象浏览器开始(Object Browser)开始,因为他能够非常容易的让我们学习对象所以他是一非常好的助手。你可以在你要查看的对象上单击右键然后选择转到定义(Go To Definition)来快事的查看类的成员。最终结果参看图4,你可以在列出的成员中查看任何一个成员的原型,或则选择一个成员按F1访问在先帮助。
图4 对象浏览器
applicationObject对象描述了插件宿主(host)应用程序,在这个例子中宿主(host)应用程序为Visual Studio .NET IDE。applicationObject在OnConnection方法中实现。在在线帮助中找到“DTE”,甚至在向导自动产生的代码中声明为“_DTE”类型的变量applicationObject。在应用程序级别你会看到她又许多有趣的成员。
其中一个成员是ActiveDocument属性(property),它描述了当前焦点的文档(document)。这个文档就是你要进行InsertDate操作的文档。在窗口焦点和文档焦点有关系但是不同的情况下,一个非常有用的规则是,不管窗体是否有焦点具有焦点的文档都是将要被保存的。
ActiveDocument.Selection属性返回一个描述当前在文档中选择的对象。因为在C#中它是一个普通对象,所以我他它强制类型转换成TextSelectioni。ActiveDocument是一个普通的类,因为document(译者:类似于VC++中的document类)没有必要基于text,就好比一个窗体设计document(译者:学过VC++的人可能比较熟悉document类的继承关系,不熟悉的人可以看看有关这方面的书籍)。作为最终结果Selection属性也是一个普通的类,在这里我把它强制类型转换成类我们在代码中实际操作的TextSelection类型。
TextSelection描述了一个文件的视图(view),并且这个文件的动作和工具|选项中的设置还有使用的状态一至。他提供了很多的你可能想到用于修改文件的属性和方法,还有他可以影响使用的视图(view),当前选择的内容,插入位置。如果你曾经录制过宏,你可以看见使用TextSelection对象来捕获内容。
InsertDate简单的的设置了Text属性值是当前的日期。类似Text一样,所有的动作都可以设置同类型的值。这就意味着所有得到的内容都要被替换,如果没有选择内容那么就会在光标的位置上替换,不过你要注意当前的状态是插入还是覆盖模式。
另外一种方法是使用Insert函数,这个方法允许你控制文本放置的位置和描述了一个不能重做的动作。使用这个方法来替换Text属性指定插入内容的代码如下:
((TextSelection)applicationObject.ActiveDocument.Selection).Insert(
DateTime.Now.ToString("yyyy-MM-dd"),
(int)EnvDTE.vsInsertFlags.vsInsertFlagsCollapseToEnd);
这个vsInsertFlags指出了文本已什么方式什么位置插入的。在线帮组中忽略了vsInsertFlags的文档,所以我在图5中包含了这部分。
现在你因该理解了插件是怎么工作的了,让我们为这个命令简单的作一个键盘邦定和增加一个菜单。
图5 vsInsertFlags 值
vsInsertFlagsCollapseToEnd | The selection's current contents are deleted before performing the insertion, and the TextSelection is left empty at the end of the newly inserted text.
|
|
vsInsertFlagsCollapseToStart
| The selection's current contents are deleted before performing the insertion, and the TextSelection is left empty at the beginning of the newly inserted text.
|
|
vsInsertFlagsContainNewText
| The selection's current contents are replaced with the inserted text, and the TextSelection is left containing the new text.
|
|
vsInsertFlagsInsertAtStart | The inserted text is placed at the beginning of the TextSelection, and the resulting TextSelection contains both the new and previous text.
|
|
vsInsertFlagsInsertAtEnd
| The inserted text is placed at the end of the TextSelection, and the resulting TextSelection contains both the new and previous text. |
|