通过扩展展开 Visual Studio 2013
现在,通过 Visual Studio Community 2013 IDE 免费向公众提供更高级别的 Visual Studio 版本,其中最值得称道的是:能够使用来自 Visual Studio 库的扩展(可以在 bit.ly/115mBzm 中找到这些扩展)。那么,什么是扩展?扩展即插件,您可以通过其扩展 Visual Studio Community 2013(可在 bit.ly/1tH2uyc 中下载)和 Visual Studio 的功能以执行新任务、添加新功能以及重构代码甚至支持新语言。
Microsoft 开发人员社区在 Visual Studio 库中提供各种各样的扩展。您看了之后便会惊讶于各种可用的扩展。其中一些扩展与我们的生活息息相关,如 Productivity Power Tools 2013 扩展(可在 bit.ly/1xE3EAT 下载)。您可以从 Web 安装这些扩展,或者您也可以在 Visual Studio 中使用“工具”菜单中的“扩展和更新”窗口搜索在线扩展。查看“在线”类别找到诸如 Visual Assist 和 ReSharper 这类比较流行的工具。您甚至可以在代码库(bit.ly/11nzi9Q)中找到更多的扩展。
即使是最好的工具可能也无法完全匹配要在 IDE 中执行或自动化的特定任务。您可能已拥有经过微调即可简化工作的个人自定义脚本。或许,这个脚本可检查生成目录以搜寻一个成功生成,或在 XML 文件上运行转换,或清理复杂生成进程留下的残余。您是愿意将您的工具作为 IDE 的一部分还是生成进程的一部分运行在 Visual Studio 内?现在,您可以使用 Visual Studio 2013 Community 和 Visual Studio 2013 SDK。
您可以首先下载 Visual Studio SDK。它提供了创建各种不同扩展所需的所有的库、工具和项目模板。接下来对其进行安装,一切便将准备就绪。
设置要运行在 Visual Studio 中的工具
安装了该 SDK 之后,从 Visual Studio 菜单运行您的工具或可执行脚本便非常容易了。本文将以最常见的 Notepad.exe 为例,但您可以使用任何可执行文件或将自己的代码集成到命令处理程序中。
基本的扩展是一个 Visual Studio Package 二进制文件。您可以在“Visual Basic”|“可扩展性”、“C#”|“可扩展性”或“其他项目类型”|“可扩展性”下的“新建项目”对话框中查找 Visual Studio Package 模板。
本文将向您演示如何制作一个启动记事本的简单扩展。我们将使用“C#/可扩展性”下的模板来创建一个 C# Visual Studio Package 项目。我们将其放在 D:\Code 目录中,并称其为 StartNotepad。这种扩展将最终从 Visual Studio IDE 菜单项中启动记事本。
双击此模板后,Visual Studio 会启动一个向导来帮助您配置此扩展。对于初学者,仅需接受前两个对话框页面上的默认值。
接下来,执行以下步骤:
- 在“选择 VSPackage 选项”页中,选择“菜单命令”选项。
- 在“命令选项”页上,将命令名称设置为“Start Notepad”(启动记事本),将命令 ID 设置为“cmdidStartNotepad”。
- 在“测试选项”页上,取消选中两个复选框。
- 单击“完成”。
此时,可以生成和运行此 Package 项目了。当项目打开后,开始调试(按 F5 键,或使用工具栏上的“启动”命令)。Visual Studio Community 2013 的新实例将启动。完成之后,您会看到此 IDE,标题栏上默认显示 Start Page – Microsoft Visual Studio – Experimental Instance(参见图 1)。这是您的 Visual Studio 测试实例。它与您的 Visual Studio 工作实例分开运行,所以如果出现错误,您不必担心自己的开发环境受到影响。
图 1 Visual Studio 实验实例
“实验实例”实际上是宣布您已经启动了 Visual Studio 的沙盒实例以测试您的扩展的另一种时髦说法。它具有 Visual Studio Community 2013 的所有功能,但其不会危害您在原始 Visual Studio 实例中的工作。对于这种简单的示例,似乎有些言过其实。但是,如果要构建一个完整的设计框架,或一套复杂的相互依赖的生成工具,您就不会想从正在开发的同一个实例运行它而让您的代码面临风险。
在实验实例的“工具”菜单上,打开“扩展和更新”窗口。您应该看到此处所示的 StartNotepad 扩展(参见图 2)。(如果您在 Visual Studio 的工作实例中打开“扩展和更新”,就不会看到 StartNotePad。)
图 2 显示了 StartNotepad 的“扩展和更新”
您还可以在“工具”菜单下看到“Start Notepad”(参见图 3)。
图 3 “工具”下 StartNotepad 的新菜单项确认
现在前往“实验实例”中的“工具”菜单。您应该可以看到 Start Notepad 命令。此时,其只会弹出一个上面写有“StartNotepad – Inside MSIT.StartNotepad.StartNotepadPackage.MenuItemCallback()”的消息框。您会在下一部分了解到究竟如何使用此命令启动记事本。
设置“菜单”命令
停止调试并返回到 Visual Studio 的工作实例。打开 StartNotepadPackage.cs 文件,其中包含派生的 Package 类。这是所有 VSPackage 扩展的起点。这个类的 Initialize 方法实际上设置了以下命令:
// Add our command handlers for menu (commands must exist in the .vsct file)
OleMenuCommandService mcs =
GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if ( null != mcs )
{
// Create the command for the menu item.
CommandID menuCommandID = new CommandID(GuidList.guidStartNotepadCmdSet,
(int)PkgCmdIDList.cmdidStartNotepad);
MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
mcs.AddCommand( menuItem );
}
现在不用考虑该代码的细节。您应该注意此菜单命令是如何实例化的。VSPackage 扩展的菜单和其他 UI 在 .vsct 文件中进行定义,如代码注释所述。
将该命令处理程序命名为 MenuItemCallback(属于同一 StartNotepadPackage 类)。删除现有的方法并添加以下内容:
private void MenuItemCallback(object sender, EventArgs e)
{
Process proc = new Process();
proc.StartInfo.FileName = "notepad.exe";
proc.Start();
}
现在来试用一下。当开始调试项目并单击“工具”| Start Notepad 时,您应该可以看到一个记事本实例。事实上,您每次单击 Start Notepad 时,都会得到一个新的记事本实例。
您可以使用一个 System.Diagnostics.Process 类的实例来运行所有可执行文件,而不仅仅是记事本。例如,使用 calc.exe 来试一试。
定义界面
现在,您将使用.vsct 文件来为您的扩展定义 UI。查看 StartNotepad.vsct 文件。您可以在这里方便地找到要在扩展中使用的所有 Visual Studio UI 定义。这是一个 XML 文件,所以对角括号打起精神来吧。
查找 <Groups> 块。每一个菜单命令都必须属于某个组,它会告知 Visual Studio 命令的放置位置。在这种情况下,命令在“工具”菜单上,其父级就是主菜单:
<Groups>
<Group guid="guidStartNotepadCmdSet"
id="MyMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
</Group>
</Groups>
不要过多考虑其中的细节。这只是向您显示重要元素的位置。命令本身在 <Buttons> 块中进行定义:
<Button guid="guidStartNotepadCmdSet" id="cmdidStartNotepad"
priority="0x0100" type="Button">
<Parent guid="guidStartNotepadCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPic1" />
<Strings>
<ButtonText>Start Notepad</ButtonText>
</Strings>
</Button>
您可以看到使用 GUID(guidStartNotepadCmdSet,与组 GUID 相同)和 ID(cmdidStartNotepad,这是您在程序包向导中指定的 ID)定义的按钮。该命令与组是父子关系。它有一个图标和一些文本。该图标是包含在解决方案中的一组默认图标之一。如果命令组中存在多个命令,则优先级将指定该按钮在菜单上的位置。用于组和按钮的 GUID 和 ID 都在 <Symbols> 块中进行定义:
<!-- This is the package GUID. -->
<GuidSymbol name="guidStartNotepadPkg"
value="{18311db7-ca0f-419c-82b0-5aa14c8b541a}" />
<!-- This is the GUID used to group the menu commands together -->
<GuidSymbol name="guidStartNotepadCmdSet"
value="{b0692a6d-a8cc-4b53-8b2d-17508c87f1ab}">
<IDSymbol name="MyMenuGroup" value="0x1020" />
<IDSymbol name="cmdidStartNotepad" value="0x0100" />
</GuidSymbol>
位图也在 <Symbols> 块中进行定义:
<GuidSymbol name="guidImages"
value="{b8b810ad-5210-4f35-a491-c3464a612cb6}" >
<IDSymbol name="bmpPic1" value="1" />
<IDSymbol name="bmpPic2" value="2" />
<IDSymbol name="bmpPicSearch" value="3" />
<IDSymbol name="bmpPicX" value="4" />
<IDSymbol name="bmpPicArrows" value="5" />
<IDSymbol name="bmpPicStrikethrough" value="6" />
</GuidSymbol>
尝试将图标更改为此处定义的其他图标之一。没有一个选择是特别适合记事本的,因此选择删除线,因为它显示了设置的其他部分。虽然图标是在 <Symbols> 块中定义,但也必须将它们列在 <Bitmaps> 块的 usedList 属性中:
<Bitmap guid="guidImages" href="Resources\Images.png" usedList="bmpPic1,
bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
您看到 bmpPicStrikethrough 未列出,尽管其已获定义。如果将图标更改为此位图,则其不会出现在菜单上。因此,添加 bmpPicStrikethrough:
<Bitmap guid="guidImages" href="Resources\Images.png" usedList="bmpPic1,
bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows, bmpPicStrikethrough"/>
现在,您可以将图标更改为 bmpPicStrikethrough 以获得菜单按钮:
<Button guid="guidStartNotepadCmdSet" id="cmdidStartNotepad"
priority="0x0100" type="Button">
<Parent guid="guidStartNotepadCmdSet" id="MyMenuGroup" />
<Icon guid="guidImages" id="bmpPicStrikethrough" />
<Strings>
<ButtonText>Start Notepad</ButtonText>
</Strings>
</Button>
现在,您可以尝试一下。当实验实例启动后,您应该可以看到“工具”菜单类似如图 4 所示。
图 4 启动记事本的命令
现在为“Start Notepad”菜单命令添加键盘快捷方式。此示例将使用 Ctrl+1。如果添加了键盘快捷方式,确保挑一个冷僻的按键组合,这样您的命令就不会与其他标准组合发生冲突。您完全可以在 .vsct 文件中实现此目的。键盘快捷方式在 .vsct 文件中称作 KeyBindings。向 .vsct 文件添加以下块:
<KeyBindings>
<KeyBinding guid="guidStartNotepadCmdSet"
id="cmdidStartNotepad"
editor="guidVSStd97" key1="1" mod1="CONTROL"/>
</KeyBindings>
key1 属性声明标准键,mod1 属性声明与标准键一起按下的修改键或加速键(通常是 CTRL、ALT 或 Shift)。如果添加 mod2="ALT" 以增加第二个修改键,您需要将 Ctrl+Alt+1 作为快捷方式。现在要坚持用 Ctrl+1。但是,请记住,最好从 .vsct 文件开始您的自定义。现在,可以启动调试了。重新启动该程序包。当实验实例出现,点击“Ctrl+1”。您应该会得到一个记事本实例。
保持 UI 响应
MenuItemCallback 方法目前已存在,它不会阻止 UI 线程。您仍然可以使用“Start Notepad”命令(或按“Ctrl+1”)启动记事本。您也仍然可以在 Visual Studio IDE 中执行相关操作。您可以移动窗口,在菜单上单击其他命令等。假设在退出处理程序之前,您的工具需要完成其工作。换言之,您需要调用 Process.WaitForExit。
不幸的是,如果在 Visual Studio UI 线程上调用 WaitForExit,因为此时 MenuItemCallback 正在执行操作,所以整个 Visual Studio UI 会冻结。在运行中试试此操作。在 MenuItemCallback 方法中,调用 WaitForExit:
private void MenuItemCallback(object sender, EventArgs e)
{
Process proc = new Process();
proc.StartInfo.FileName = "notepad.exe";
proc.Start();
proc.WaitForExit();
}
开始调试,并在实验实例启动时,点击“Ctrl+1”。您应该可以看到记事本实例。现在,尝试移动 Visual Studio 窗口(实验 1)。无法移动。也无法在 Visual Studio UI 中执行其他任何操作。您甚至看到弹出窗口,上面写着:“Microsoft Visual Studio is Busy.”。很显然,这是您需要修复的东西。
幸运的是,解决方法很简单。如果您的工具或进程需要很长时间才能完成,请将其封装在 MenuItemCallback 中的任务中,就像这样:
private void MenuItemCallback(object sender, EventArgs e)
{
ThreadHelper.JoinableTaskFactory.RunAsync(async delegate
{
Process proc = new Process();
proc.StartInfo.FileName = "notepad.exe";
proc.Start();
await proc;
});
}
现在,您应该能够在 Visual Studio 中同时运行您的工具和工作了。
清理您的实验
如果您正在开发多个扩展,或只是使用不同版本的扩展代码以探究结果,那么您的实验环境停止的方式可能是非正常的。在这种情况下,您应该运行重置脚本。
该脚本称为 Reset the Visual Studio 2013 Experimental Instance(重置 Visual Studio 2013 实验实例),并随附在 Visual Studio 2013 SDK 中。该脚本将删除实验环境中对您的扩展的所有引用,以便您可以从头开始。您可以通过以下两种方式获得此脚本:
- 从桌面上找到 Reset the Visual Studio 2013 Experimental Instance
- 从命令行运行以下命令:
<VSSDK installation>\VisualStudioIntegration\Tools\Bin\
CreateExpInstance.exe /Reset /VSInstance=12.0
/RootSuffix=Exp && PAUSE
部署您的扩展
现在,您的工具扩展运行正常,因此是时候考虑将其与您的朋友和同事分享了。只要您安装了 Visual Studio 2013,实现分享非常简单。您所需做的就是向他们发送您生成的 .vsix 文件。一定要在 Release 模式下生成此文件。
您可以在 StartNotepad bin 目录中找到此扩展的 .vsix 文件。假设您已经生成了 Release 配置,它会位于 \D:\Code\StartNotepad\StartNotepad\bin\Release\StartNotepad.vsix 中。
若要安装此扩展,用户需要关闭所有打开的 Visual Studio 实例,然后双击 .vsix 文件以弹出 VSIX 安装程序。将这些文件复制到 %LocalAppData%\Microsoft\VisualStudio\12.0\Extensions 目录。
当用户目前再次出现 Visual Studio 时,他会在“工具”|“扩展和更新”中找到 StartNotepad 扩展。他可以前往“扩展和更新”卸载或禁用此扩展。
学习如何创建可让您获得真正属于自己的 Visual Studio 体验的扩展。它也可以让您与社区分享您的最佳工作效率增强功能。当您发布您的扩展时,Microsoft 也会为此感到高兴。
总结
本文介绍的 Visual Studio 扩展用途只是冰山一角。若要总体了解有关 Visual Studio 扩展的详细信息,请参阅“将您的应用程序或服务与 Visual Studio 集成”页面: bit.ly/1zoIt59。
若要进行深入了解,请参阅“扩展 Visual Studio 概述”部分 bit.ly/1xWoA4k 下的 MSDN 库文章。一些用于构建这方面知识的优秀代码示例,请查看 MSDN 示例库中的实例集合 bit.ly/1xWp5eD。在生成并分享了一些扩展之后,您就可以构建真正意义上属于自己的 Visual Studio 环境了。
Susan Norwood 就职于 Microsoft,主要撰写有关 Visual Studio SDK 的文章。她曾帮助许多人将其工具集成到 Visual Studio。
Doug Erickson 作为 Microsoft 的开发人员和技术作家已有 13 年。他期待看到开发人员在通过 Visual Studio Community 开发适用于所有平台的应用程序和游戏的方面一展身手。
衷心感谢以下 Microsoft 技术专家对本文的审阅:Anthony Cangialosi 和 Ryan Molden