65.9K
CodeProject 正在变化。 阅读更多。
Home

为 Visual Studio 2010 (VSX 2010) 编写 P2P 代码片段共享扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (6投票s)

2010年3月13日

GPL3

11分钟阅读

viewsIcon

37818

downloadIcon

350

CodeXchange 是一个简单的 Visual Studio 扩展,它允许您在不离开 Visual Studio 2010 IDE 的情况下创建、编辑和与您的同行共享代码片段。

概述

如今,任何现代应用程序都考虑到了可扩展性。这使得开发人员甚至最终用户可以替换现有组件或添加自己的自定义功能。

在扩展现有应用程序时,我们基本上有两种方法:宏和插件。宏是使用脚本语言创建的小型解释程序,它们在 Excel 和 PowerPoint 等应用程序中运行,而插件是针对应用程序插件机制编译的应用程序,它们可以访问丰富的 API 集,使开发人员能够快速创建和构建新工具,而无需从头开始。

Visual Studio 也不例外;它提供了一个非常丰富的对象模型来自动化任务,添加新的功能,如项目向导、工具,甚至支持任何类型的编译器。

引言

从 2005 版本开始,Visual Studio 就包含了代码片段功能。代码片段是通用的可重用代码的小片段。它们通过减少输入和帮助代码重用来提高开发人员的效率和准确性。

Visual Studio 将您的代码片段存储在文件系统中(确切地说是在 [我的文档]\Visual Studio [版本]\Code Snippets\Visual C#\My Code Snippets),使用 XML 语法。这还可以,但如果您和我一样,可能拥有不止一台开发计算机,这会使您的代码片段在计算机之间保持同步变得更加复杂,并且几乎不可能与您的朋友或同事共享。拥有一个集中的数据库,以便您可以随时随地访问所有代码片段,这难道不好吗?如果您的答案是肯定的,那么 CodeXchange 就是为您准备的!

对于那些不知道 CodeXchange 的人,您可以访问其网站 www.codexchange.org 并了解其内容。所有软件都是免费的,所以这不是某种廉价的商业行为。

我们的扩展将是一个非常简单的 CodeXchange 客户端,基于其公开的 Web 服务,位于 http://www.codexchange.org/api/v1/CodeXchangeservice.asmx。由于本文的目的是构建扩展,我已经将所有不相关的客户端代码放在了另一个程序集中,该程序集公开了一个 Windows Forms 2.0 UserControl,该 UserControl 将托管在 Visual Studio 工具窗口中。

背景

此扩展是我为 Visual Studio 2005 编写的先前插件的改进。在本文中,我将记录移植到 Visual Studio 2010 的工作以及利用 Visual Studio 最新迭代中引入的新功能所需的更改。

在本文中,我们将学习如何

  • 使用“Visual Studio Package”项目向导创建一个骨架扩展
  • 提供适当的元数据
  • 创建一个工具箱窗口并在其中托管一个 Windows Forms 控件
  • 公开新的菜单命令
  • 将菜单项添加到“代码窗口”上下文菜单
  • 将工具箱项添加到 Visual Studio 2010 工具箱
  • 将扩展打包为 VSIX 包
  • 在新的 Visual Studio Gallery 中发布我们的扩展

先决条件:在我们开始编码之前

在开始之前,您需要下载并安装所有必需的工具,当然包括 Visual Studio 2010,您可以在此处下载 RC 版本 http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx

您还需要 Visual Studio 2010 SDK,其中包含项目模板、文档、示例以及构建和部署 Visual Studio 扩展所需的所有工具,请在此处下载 http://www.microsoft.com/downloads/details.aspx?FamilyID=4659f71d-4e58-4dcd-b755-127539e21147&displaylang=en

逐步创建项目文件

SDK 将安装几个新的项目模板 [1]

  • 编辑器视口装饰:编辑器分类器允许与编辑文本进行交互并以多种方式更改它。更改文本的逻辑完全由扩展开发人员自行决定。文本的更改可以是简单地改变颜色,也可以是改变大小、不透明度等。
  • 编辑器分类器:编辑器分类器允许与编辑文本进行交互并以多种方式更改它。
  • 编辑器边距:与视口装饰类似,但编辑器知道它的存在,并相应地使用滚动条进行处理。与 WPF DockPanel 非常相似,内容必须放置在左、右、上或右。
  • 编辑器文本装饰 与编辑器分类器非常相似,您可以与编辑器文本进行交互。然而,结果是一个在 IDE 中呈现的 WPF UIElement。这可以放置在相对于文本位置的任何地方。
  • Visual Studio Package:是最完整的插件类型,它允许您扩展 VS 中的几乎任何内容。

让我们开始吧!

  • 在“文件”菜单上,指向“新建”,然后单击“项目”。
  • 在“新建项目”对话框中,展开“其他项目类型”,然后单击“可扩展性”。
  • 在“模板”窗格中,单击“Visual Studio Studio Package”。
  • 在“位置”框中,键入新扩展的文件路径。
  • 在“名称”框中,键入扩展的名称,然后单击“确定”以启动向导。

iMAGE002.JPG

选择您偏好的语言,然后单击“下一步”。目前,扩展只能使用 C#、Visual Basic 或 Visual C++ 编写。

iMAGE003.JPG

这是关于我们扩展的基本信息。这些信息将用于显示在“关于”对话框中,并会持久化到注册表中。

iMAGE004.JPG

这是一个重要的步骤。我们将选中“菜单命令”和“工具窗口”选项,这将为向 Visual Studio 添加新菜单以及浮动工具窗口生成基本骨架。默认情况下,工具窗口将托管一个 WPF 用户控件,稍后我们将修改代码以托管我们的 Windows Forms UserControl。

iMAGE005.JPG

完成包向导的最后一个步骤后,将创建一个新项目并准备好供您自定义。展开“引用”节点时,您将对 Visual Studio 公开的类、接口和服务的数量有一个大致的了解。版本更新后,更多内容会很好地封装在 .NET 友好类型的对象中,尽管 IDE 的大部分部分都已用托管代码重写(例如新的基于 WPF 的代码编辑器),但有时 Visual Studio 会显示其 COM 传统,并要求处理 COM 风格的接口。

iMAGE006.PNG

您应该做的第一件事是编辑 VSIX manifest 文件,这是一个描述您扩展的 XML 文件。值得庆幸的是,一个不错的可视化编辑器将使过程更容易。只需在解决方案资源管理器中双击 source.extension.vsixmanifest 即可。我不会深入探讨每个字段的细节。

iMAGE007.PNG

大多数属性都是不言自明的。如果您想在 Visual Studio gallery 中发布您的扩展,您必须特别注意
  • Icon - 将在 VS Extension Manager 中显示的图像
  • Preview Image - 将在 VS Extension Manager 中显示的预览图像
  • Version - 如果此字段增加,VS 将提示自动更新(下载并安装)您的扩展

添加和管理菜单

我们的扩展将注册 4 个不同的菜单命令

  • cmdidCodeXchange:将添加到“视图”->“其他窗口”菜单,并将显示/隐藏 CodeXchange 工具窗口
  • cmdidInsertFromCodeXchange:将添加到“代码窗口”上下文菜单,并将连接到在线存储库
  • cmdidContributeToCodeXchange:从 CodeXchange 插入
  • cmdidConnectToCodeXchange:连接到 CodeXchange

要添加新的菜单命令,首先必须编辑项目向导生成的 PkgCmdID.cs 文件,为每个命令添加一个新的唯一条目。

static class PkgCmdIDList
{

    public const uint cmdidCodeXchange =    0x101;
    public const uint cmdidInsertFromCodeXchange = 0x102;
    public const uint cmdidContributeToCodeXchange = 0x103;
    public const uint cmdidConnectToCodeXchange = 0x104;

};

下一步将在 .vsct 文件中添加和定义我们的菜单。Visual Studio Command Table (.Vsct) 文件是一个基于 XML 的文件,它描述了 VSPackage 公开的命令集。在解决方案资源管理器中双击 Sand.Services.CodeXchange.VS2010Addin.vsct,然后在 <Symbols> 部分为每个命令添加一个 <IDSymbol> 元素。值必须与我们在 PkCmdID.cs 中使用的值匹配。

<GuidSymbol name="guidCodeXchangeVS2010AddinCmdSet" value="{b0b84c23-3315-457c-b057-8457b5344521}">
  <IDSymbol name="MyMenuGroup" value="0x1020" />
  <IDSymbol name="cmdidCodeXchange" value="0x0101" />
  <IDSymbol name="cmdidInsertFromCodeXchange" value="0x0102" />
  <IDSymbol name="cmdidContributeToCodeXchange" value="0x0103" />
  <IDSymbol name="cmdidConnectToCodeXchange" value="0x0104" />
</GuidSymbol>

现在,我们需要将我们 4 个命令中的 3 个分组,以便在“代码窗口”上下文菜单中显示它们。另一个命令将在“视图->其他窗口”子菜单中显示。找到 <Groups> 部分并添加以下内容:

<Groups>

  <Group guid="guidCodeXchangeVS2010AddinCmdSet" id="MyMenuGroup" priority="0x0600">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_CODEWIN"/>
  </Group>

</Groups>

添加命令并配置它们

<Button guid="guidCodeXchangeVS2010AddinCmdSet" id="cmdidCodeXchange" priority="0x0100" type="Button">
  <Parent guid="guidSHLMainMenu" id="IDG_VS_WNDO_OTRWNDWS1"/>
  <Icon guid="guidImages" id="bmpPic1" />
  <Strings>
    <CommandName>cmdidCodeXchange</CommandName>
    <ButtonText>CodeXchange</ButtonText>
  </Strings>
</Button>

<Button guid="guidCodeXchangeVS2010AddinCmdSet" id="cmdidInsertFromCodeXchange" priority="0x0100" type="Button">
  <Parent guid="guidCodeXchangeVS2010AddinCmdSet" id="MyMenuGroup"/>
  <Icon guid="guidImages" id="bmpPic2" />
  <Strings>
    <CommandName>cmdidInsertFromCodeXchange</CommandName>
    <ButtonText>Insert snippet from CodeXchange</ButtonText>
  </Strings>
</Button>

<Button guid="guidCodeXchangeVS2010AddinCmdSet" id="cmdidContributeToCodeXchange" priority="0x0100" type="Button">
  <Parent guid="guidCodeXchangeVS2010AddinCmdSet" id="MyMenuGroup"/>
  <Icon guid="guidImages" id="bmpPic3" />
  <Strings>
    <CommandName>cmdidContributeToCodeXchange</CommandName>
    <ButtonText>Contribute snippet to CodeXchange</ButtonText>
  </Strings>
</Button>

<Button guid="guidCodeXchangeVS2010AddinCmdSet" id="cmdidConnectToCodeXchange" priority="0x0100" type="Button">
  <Parent guid="guidCodeXchangeVS2010AddinCmdSet" id="MyMenuGroup"/>
  <Icon guid="guidImages" id="bmpPic4" />
  <Strings>
    <CommandName>cmdidConnectToCodeXchange</CommandName>
    <ButtonText>Connect to CodeXchange</ButtonText>
  </Strings>
</Button>

最后,在 C# 代码中映射我们的命令,以便我们可以根据扩展状态启用或禁用它们并处理其执行。

/* Extension menus */
OleMenuCommand menuShowToolWin = null;
OleMenuCommand menuInsertFromCodeXchange = null;
OleMenuCommand menuContributeToCodeXchange = null;
OleMenuCommand menuConnectToCodeXchange = null;
// 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 tool window
    CommandID commandID = null;

    commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidCodeXchange);
    menuShowToolWin = new OleMenuCommand(
        ExecuteMenuCommandCallback,
        ChangeCallback,
        BeforeStatusQueryCallback,
        commandID);

    /* Add the show/hide tool window command */
    mcs.AddCommand(menuShowToolWin);

    commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidInsertFromCodeXchange);
    menuInsertFromCodeXchange = new OleMenuCommand(
        ExecuteMenuCommandCallback,
        ChangeCallback,
        BeforeStatusQueryCallback,
        commandID);

    /* Add the 'Insert from CodeXchange' command */
    mcs.AddCommand(menuInsertFromCodeXchange);

    commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidContributeToCodeXchange);
    menuContributeToCodeXchange = new OleMenuCommand(
        ExecuteMenuCommandCallback,
        ChangeCallback,
        BeforeStatusQueryCallback,
        commandID);

    /* Add the 'Contribute to CodeXchange' command */
    mcs.AddCommand(menuContributeToCodeXchange);

    commandID = new CommandID(GuidList.guidCodeXchangeVS2010AddinCmdSet, (int)PkgCmdIDList.cmdidConnectToCodeXchange);
    menuConnectToCodeXchange = new OleMenuCommand(
        ExecuteMenuCommandCallback,
        ChangeCallback,
        BeforeStatusQueryCallback,
        commandID);

    /* Add the 'Connect to CodeXchange' command */
    mcs.AddCommand(menuConnectToCodeXchange);
}

并非所有命令都将始终可用。例如,如果我们没有真正连接到在线数据库,“从 CodeXchange 插入”就没有意义。为了启用/禁用命令,我们将使用 BeforeStatusQueryCallback。此回调在 IDE 请求命令的当前状态时执行。

private void BeforeStatusQueryCallback(object sender, EventArgs e)
{
    OleMenuCommand command = sender as OleMenuCommand;

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidCodeXchange)
    {
        // This command is always available
        command.Enabled = true;
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidInsertFromCodeXchange)
    {
        // This command is only available if we are logged to the online repository
        command.Visible = command.Enabled = toolwndCodeXchange.CodeXchange.IsConnected;
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidConnectToCodeXchange)
    {
        // This command is only available if we are logged to the online repository
        command.Visible = command.Enabled = !toolwndCodeXchange.CodeXchange.IsConnected;
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidContributeToCodeXchange)
    {
        // This command is only available if we are logged to the online repository
        // and some text is selected on the active editor
        if (toolwndCodeXchange.CodeXchange.IsConnected)
        {
            //check if any text is highlighted or disable the menu
            if (GetSelectedText().Length > 0)
                command.Enabled = true;
            else
                command.Enabled = false;
        }
        else
        {
            command.Enabled = false;
        }
    }
}

处理命令执行

private void ExecuteMenuCommandCallback(object sender, EventArgs e)
{
    OleMenuCommand command = sender as OleMenuCommand;

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidCodeXchange)
    {
        // Show the CodeXchange VS tool window
        ShowToolWindow();
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidConnectToCodeXchange)
    {
        if (toolwndCodeXchange.CodeXchange.IsConnected == false)
            toolwndCodeXchange.CodeXchange.Connect();
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidContributeToCodeXchange)
    {
        // Get active document currently selected text
        string selectedText = GetSelectedText();

        toolwndCodeXchange.CodeXchange.AddCodeSnippet(selectedText);
    }

    if (command.CommandID.ID == (int)PkgCmdIDList.cmdidInsertFromCodeXchange)
    {
        // Ensure the tool window is currently visible
        ShowToolWindow();

        // Show search user interface
        toolwndCodeXchange.CodeXchange.SwithToSearchMode();
    }
}

操作源代码编辑器

CodeXchange 的目的是将代码从在线存储库插入到我们的本地源代码文件中。当用户想将代码片段粘贴到当前打开的文档时,我们将打开一个新的 UndoContext。UndoContexts 是表示为单个事务的操作组。一旦我们打开了 undo context,我们将创建一个新的 EditPoint 并插入代码。完成后,我们将使用 SmartFormat 方法自动缩进代码,最后一步是关闭我们之前创建的 UndoContext。

//Check to see if UndoContext object is already open. 
if (dte2.UndoContext.IsOpen == true)
    dte2.UndoContext.Close();

// Open a new undo context 
dte2.UndoContext.Open("Snippet inserted from CodeXchange", true);

// remeber we opened an undo context
undoContext = true;

EditPoint start = tsSelection.TopPoint.CreateEditPoint();
EditPoint endpt = tsSelection.BottomPoint.CreateEditPoint();

//Insert source code to document....
endpt.Insert(
    System.Environment.NewLine +
    e.Snippet.Code +
    System.Environment.NewLine);
endpt.StartOfDocument();
start.EndOfDocument();
endpt.SmartFormat(start);

//If UndoContext was already open, don't close it. 
if (undoContext == true)
{
    //Close the UndoContext object to commit the changes. 
    dte2.UndoContext.Close();
}

创建工具窗口

项目向导创建了一个继承自 ToolWindowPane 的类,它是 IDE 中工具窗口的基类。我们将修改构造函数以创建一个新的 CodeXchange 用户控件实例,并通过覆盖基类 ToolWindowPane 的 Window 公共属性来返回该实例。

public class CodeXchangeToolWindow : ToolWindowPane
{
    private CodeXChangeControl _control;

    /// 
    /// Standard constructor for the tool window.
    /// 
    public CodeXchangeToolWindow() :
        base(null)
    {
        _control = new CodeXChangeControl();
    }

    /// 
    /// This property returns the handle to the user control that should
    /// be hosted in the Tool Window.
    /// 
    override public IWin32Window Window
    {
        get { return (IWin32Window)_control; }
    }
}

我们完成了。请记住,工具窗口是按需加载的,当我们第一次需要显示工具窗口时,将创建一个我们的控件的实例。

将工具箱项添加到工具箱

通过 CodeXchange 工具窗口访问代码片段列表是扩展的设计用途,事实上,它是进行预览、删除或编辑已发布代码片段等操作的唯一方式。工具窗口很棒,但有时我们会隐藏它们以节省空间。为了解决这个问题,我们将添加代码片段的快捷方式到工具箱中。用户应该能够通过将其拖到源文件上来插入其中任何一个。

// Get a refence to the IVsToolbox interface.
IVsToolbox  tbs = GetService(typeof(IVsToolbox)) as IVsToolbox;

// For each snippet add a toolbox item
foreach (Snippet snippet in toolwndCodeXchange.CodeXchange.UserContributedSnippets)
{
    TBXITEMINFO[] itemInfo = new TBXITEMINFO[1];
    OleDataObject tbItem = new OleDataObject();
 
    itemInfo[0].bstrText = snippet.Summary;
    itemInfo[0].dwFlags = (uint)__TBXITEMINFOFLAGS.TBXIF_DONTPERSIST;

    tbItem.SetText(snippet.Code, TextDataFormat.Text);

    tbs.AddItem(tbItem, itemInfo, "My CodeXchange Snippets");
}

最终结果

iMAGE008.PNG

注意:CodeXchange 代码片段工具箱条仅在活动文档是基于文本的编辑器时可见。

测试和调试您的 Visual Studio 2010 扩展

当您尝试运行新创建的项目时,您会注意到启动了一个新的 Visual Studio 实例。您的扩展将自动安装到这个名为“实验实例”的新实例中,它本质上是 Visual Studio 的一个隔离副本,拥有自己的扩展、设置等。这将允许您在不破坏主要开发环境的情况下测试您的项目。调试会话将在您关闭它时结束。

将您的扩展打包为 VSIX

在以前版本的 Visual Studio 中,插件是从 msi 安装包安装的。Visual Studio 2010 引入了一种新的部署包技术,称为 VSIX。VSIX 文件只是一个扩展名为 (.vsix) 的标准 zip 文件,它符合 Open Package Convention http://msdn.microsoft.com/en-us/magazine/cc163372.aspx。它包含扩展以及 Visual Studio 安装和管理扩展所需的所有元数据、资源文件、图像和清单文件。构建项目时,Visual Studio 会为您生成它。

在 Visual Studio gallery 中共享您的扩展

共享您的扩展就像发布我们刚刚创建的 .vsix 文件一样简单。要安装您的扩展,用户需要双击并按照向导进行操作。Visual Studio 2010 包含一项名为“扩展管理器”的新功能,它将帮助您轻松安装、更新和删除扩展。还可以通过新的 Visual Studio Gallery 在 IDE 中下载扩展。

extensionManager.png

有关新的 Visual Studio gallery 的更详细信息,请访问:http://visualstudiogallery.msdn.microsoft.com/en-us/

CodeXchange 扩展也可以从 Visual Studio Gallery 下载:http://visualstudiogallery.msdn.microsoft.com/en-us/3b9d5e70-055c-4228-abc1-3723cd5f78f2

想法和未来改进

没有项目是完美的,这个项目也不例外,总有改进的空间,以下是一些当前缺失或可以改进的地方:

  • 使用 WPF 而不是 Windows Forms 2.0 为 CodeXchange 控件实现新的炫酷界面
  • 与现有的 Visual Studio 代码片段体系结构更好地集成。
  • 实现本地存储作为代码片段缓存,允许离线工作

结论

Visual Studio 2010 相较于以前的版本有了很大的改进。凭借其庞大而成熟的可扩展性模型,它为创建丰富强大的扩展和工具提供了更大的机会。

本文附带的 CodeXchange 示例客户端并不打算实现完整的命令集,而是支持基本功能。您可以使用此代码创建自己的自定义客户端,或将其作为参考和指南用于您自己的项目。玩得开心!

参考文献

历史

    * 初始草稿 (V1.0) 2010 年 3 月 10 日

致谢

感谢 DotNetPark 大力托管 codexchange 网站和数据库

最后说明

英语不是我的母语,所以请原谅任何错误。这是我为 CodeProject 写的第一篇文章,任何建议和/或反馈都将受到赞赏。感谢您一直读到这里!如果您能补充任何内容或有建议和/或技巧,请在下方留言,如果您喜欢,请不要忘记投票。:)

© . All rights reserved.