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

编号书签 - Visual Studio 扩展 VSX 2010

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2010年2月28日

CPOL

15分钟阅读

viewsIcon

86711

downloadIcon

1159

用于创建编号书签的 Visual Studio 2010 扩展。

Numbered bookmarks screenshot

目录

引言

书签功能是我一直不太喜欢的 Visual Studio 功能。我从来不想有成百上千的书签。我只想为最常用的行/部分提供一些快捷方式。我找不到类似的东西,微软也不想添加这个功能到 Visual Studio,所以我自己动手创建了这个 Visual Studio 扩展。

编号书签是一个 Visual Studio 2010 扩展,用于使用数字创建和检索书签。它允许用户创建 10 个编号书签(从 0 到 9)。用户可以使用相同的快捷键添加或导航到特定的书签。该工具会在 Visual Studio 编辑器中添加一个书签边距,位于滚动条旁边。每当创建书签时,书签边距上就会放置一个可视的图标。用户还可以通过“工具”菜单下的“编号书签”菜单来创建/导航/清除书签。

使用编号书签相对容易

  • 创建书签:按组合键 Ctrl+Alt+数字 来创建书签,其中数字可以是 0 到 9 之间的任意数字。
  • 导航到书签:按组合键 Ctrl+Alt+数字 来移动到书签位置。
  • 删除书签:右键单击书签边距中的书签。
  • 删除所有书签:按组合键 Ctrl+Alt+Backspace。
  • 书签信息:鼠标悬停在书签上会显示有关书签的基本信息,包括书签编号、文件名、行号和列号。
  • 如何使用信息:鼠标悬停在书签边距的绿色书签上会显示基本的使用说明。

Numbered bookmarks screenshot

先决条件

为了开发编号书签,我们需要预装以下应用程序

分而治之

解释一个功能齐全的 Visual Studio 扩展的任务可能非常繁重,这取决于扩展的复杂性。编号书签虽然是一个简单的扩展,但确实有点难以解释(尤其是对初学者而言)。让我们尝试将其分解成更小的任务,逐个完成,最终达到我们的目标。那么,我们还在等什么?让我们开始吧。

创建基本基础结构

我们可以使用 Visual Studio 包向导来生成基本的基础结构。所以,让我们继续创建基础;您需要遵循的步骤是

  • 转到文件->新建,然后在新建项目对话框中,选择其他项目类型->可扩展性,然后在右侧窗格中选择Visual Studio 包。将包命名为 NumberedBookmarks,然后单击确定
  • Create new extensibility package

  • Visual Studio 包向导的欢迎页面上单击下一步。选择Visual C#作为您的编程语言,并选择生成新密钥以签名程序集选项,然后单击下一步
  • 在下一步中,提供有关包的信息。修改图标、公司名称、包名称、包版本和包信息,然后单击下一步
  • Package information page

  • 在下一步中选择菜单命令选项。它会在“工具”菜单中创建一个标题为“编号书签”的菜单项。单击下一步
  • 在下一步中,将命令名称更改为 Numbered Bookmarks,将命令 ID更改为cmdIDNumberedBookmarks,然后单击下一步
  • Command information page

  • 测试项目选项页面上,取消选择单元测试项目集成测试项目,然后单击完成。现在,我们的基本基础结构已准备就绪。
  • Basic infrastructure

添加菜单和子菜单

除了键盘快捷键外,用户还可以使用菜单选项来创建、移动到或删除所有书签。让我们向前面步骤创建的“编号书签”菜单添加子菜单。最终我们的菜单应该如下所示

Numbered bookmarks sub menu

首先,我们将把命令 ID 添加到PkgCmdIDList.cs,这些 ID 用于将事件处理程序连接到菜单项。此类中已经有一个用于“编号书签”菜单的条目。我们不使用此菜单,而是将子菜单添加到“工具”菜单中,因此删除条目并为所有菜单项创建命令 ID。最后,它看起来像

static class PkgCmdIDList
{
  public const uint cmdBookmark0          = 0x0005;
  public const uint cmdBookmark1          = 0x0015;
  ...
  public const uint cmdBookmark9          = 0x0095;
  public const uint cmdClearBookmarks     = 0x0105;
};

现在,在进一步修改 VSCT(Visual Studio 命令表)文件以获得更多菜单选项之前,让我们创建一个位图菜单条,用于菜单图标(每个图标 16x16)。是的,我们必须创建一个位图条。要创建此位图条,请将所有图像并排放置在新图像中,并将最终图像保存为位图。下面是编号书签使用的位图条的示例。别忘了将其添加到资源中。

Bitmap menu strip

现在是时候动手实践了。打开NumberedBookmarks.vsct文件。滚动到Bitmaps部分并按如下方式修改该部分

<Bitmaps>
  <Bitmap guid="guidIcons"
  href="Resources\AllIcons.bmp"
  usedList="bmpZero, bmpOne, bmpTwo, bmpThree, bmpFour, bmpFive,
  bmpSix, bmpSeven, bmpEight, bmpNine, bmpNumbers"/>
</Bitmaps>

在此部分,我们指定了位图条的位置以及所有图像的名称。现在,让我们为所有图像创建符号。

滚动到符号部分,您会找到一个用于guidImagesGUIDSymbol条目。Visual Studio 默认会在包中添加一个位图条。删除该部分并添加以下内容

<GuidSymbol name="guidIcons" value="{1097bc53-206b-4232-a166-1dfe7cdaedf4}" >
  <IDSymbol name="bmpZero" value="1" />
  <IDSymbol name="bmpOne" value="2" />
    ...
  <IDSymbol name="bmpNine" value="10" />
  <IDSymbol name="bmpNumbers" value="11" />
</GuidSymbol>

请注意,图像是从 1 开始编号的,而不是从 0 开始。value 部分包含图像在位图条中从左侧的索引。

每个菜单都包含在一个菜单组中。这样我们就有了两个菜单组(惊讶?)。让我尝试解释一下。我们在“工具”菜单下添加一个“编号书签”菜单,并在其下方添加一个子菜单。所以我们有两个菜单,“编号书签”菜单和子菜单。两者都应该有相应的菜单组。让我们分别将它们命名为MyMenuGroupSubMenuGroup。最终所有菜单选项都添加到SubMenuGroup

Menus and groups

我们将此步骤分为五个部分

  • 首先,让我们为所有命令(前面创建的)、菜单和菜单组添加 GUID 符号。确保所有值都与之前创建的命令 ID 匹配,否则事件处理程序将不会被触发。
  • <GuidSymbol name="guidNumberedBookmarksCmdSet" 
            value="{c74fc9bd-32e1-4135-bddd-779021cc3630}">
      <IDSymbol name="MyMenuGroup" value="0x1020" />
      <IDSymbol name="SubMenuGroup" value="0x1150"/>
      <IDSymbol name="SubMenu" value="0x1100"/>
      <IDSymbol name="cmdBookmark0" value="0x0005"/>
        ...
      <IDSymbol name="cmdBookmark9" value="0x0095"/>
      <IDSymbol name="cmdClearBookmarks" value="0x0105"/>
    </GuidSymbol>
  • 其次,添加一个Groups部分,并添加两个组的条目(如前所述)。IDM_VS_MENU_TOOLS是“工具”菜单的常量。
  • <Groups>
      <Group guid="guidNumberedBookmarksCmdSet" id="MyMenuGroup" priority="0x0600">
        <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
      </Group>
    
      <Group guid="guidNumberedBookmarksCmdSet" id="SubMenuGroup" priority="0x0000">
        <Parent guid="guidNumberedBookmarksCmdSet" id="SubMenu"/>
      </Group>
    </Groups>
  • 第三,添加一个Menus部分,并为“编号书签”添加一个菜单条目。添加菜单时,您可以提供命令名称和显示的标题。是的,您无法为子菜单提供图标。叹气!
  • <Menus>
      <Menu guid="guidNumberedBookmarksCmdSet" id="SubMenu" 
                  priority="0x0100" type="Menu">
        <Parent guid="guidNumberedBookmarksCmdSet" id="MyMenuGroup"/>
          <Strings>
            <ButtonText>Numbered Bookmarks</ButtonText>
            <CommandName>Numbered Bookmarks</CommandName>
          </Strings>
        </Menu>
    </Menus>
  • 第四,让我们现在将子菜单项添加到文件中。为了添加菜单,我们在Buttons标签内使用Button标签。我们可以为每个菜单提供命令名称、标题和图标。其他条目可以类似示例如下添加
  • <Button guid="guidNumberedBookmarksCmdSet" id="cmdBookmark1" 
              priority="0x0000" type="Button">
    <Parent guid="guidNumberedBookmarksCmdSet" id="SubMenuGroup" />
    <Icon guid="guidIcons" id="bmpOne" />
    <Strings>
      <CommandName>cmdBookmark1</CommandName>
      <ButtonText>Bookmark 1</ButtonText>
    </Strings>
    </Button>
  • 最后,让我们为菜单创建键盘快捷键。这称为键绑定。添加键绑定时,您可以提供两个修饰符和两个键。再次奇怪。让我们回想一下,解决方案资源管理器的快捷键是(Ctrl+W, S),这可以分为两部分。第一部分是 Ctrl+W,第二部分是 S。Ctrl 是 modifier1,W 是 key1,同样 S 是 key2。修饰符可以是 Ctrl、Alt 或 Shift 的组合,用空格分隔。
  • <KeyBindings>
      <KeyBinding guid="guidNumberedBookmarksCmdSet" id="cmdBookmark0"
      editor="guidVSStd97" key1="0" mod1="Control Alt" />
      <KeyBinding guid="guidNumberedBookmarksCmdSet" id="cmdClearBookmarks"
      editor="guidVSStd97" key1="VK_BACK" mod1="Control Alt" />
    </KeyBindings>

最后,我们到达可以绑定命令到事件处理程序的地点。在包类(在此例中为NumberedBookmarksPackage)的Initialize函数中,我们可以将命令与事件处理程序绑定,并在同一类中为它创建一个事件处理程序。

CommandID menuCommandID = new CommandID(GuidList.guidNumberedBookmarksCmdSet,
                            (int)PkgCmdIDList.cmdIdNumberedBookmarks);
MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
mcs.AddCommand( menuItem );

// event handler for the command
private void MenuItemCallback(object sender, EventArgs e) { }

对于编号书签,为所有 10 个条目(数字 0 到 9)添加书签的功能是相同的,在事件处理程序中,我们调用一个带有int参数的函数AddBookmark来指定要添加哪个书签。因此,我选择了匿名方法而不是事件处理程序。

// hoook up command for all bookmarks (0-9)
CommandID menuCommandBookmark0 = new CommandID(
                                   GuidList.guidNumberedBookmarksCmdSet,
                                   (int)PkgCmdIDList.cmdBookmark0);
MenuCommand subItemBookmark0 = new MenuCommand(
                                  new EventHandler(
                                    delegate(object sender, EventArgs args) 
                                      { AddOrMoveToBookmark(0); }), 
                                  menuCommandBookmark0);
mcs.AddCommand(subItemBookmark0);

// hook up command for clearing bookmarks
CommandID menuCommandClearBookmark = new CommandID(
                                       GuidList.guidNumberedBookmarksCmdSet,
                                       (int)PkgCmdIDList.cmdClearBookmarks);
MenuCommand subItemClearBookmark = new MenuCommand(
                                     new EventHandler(
                                       delegate(object sender, EventArgs args)
                                         { ClearAllBookmarks(); }),
                                     menuCommandClearBookmark);
mcs.AddCommand(subItemClearBookmark);

// add dummy methods
private void ClearAllBookmarks() { }
private void AddOrMoveToBookmark(int bookmarkNumber) { }

创建书签边距

创建边距或书签边距是一个双重过程。我们需要创建两个类,第一个类继承自IWpfTextViewMarginProvider,第二个类继承自IWpfTextViewMargin。我们需要将我们的工厂(提供程序)类导出到 MEF。根据 MEF 的哲学:“您导出,我们导入。我们导出,您导入。”这里我们导出提供程序类,MEF 导入它,然后这个类告诉 MEF 如何创建一个边距。第二个类实际上创建了边距。我们继承了Border以提供一些基本的 WPF 功能,如背景颜色、宽度、高度等。我们也可以选择其他控件或自定义控件。

[Export(typeof(IWpfTextViewMarginProvider))]
[Name(BookmarkMargin.MarginName)]

[Order]
[MarginContainer(PredefinedMarginNames.Right)]
[ContentType("code")]
[TextViewRole(PredefinedTextViewRoles.Document)]
internal sealed class MarginFactory : IWpfTextViewMarginProvider
{
  public IWpfTextViewMargin CreateMargin(IWpfTextViewHost textViewHost, 
         IWpfTextViewMargin containerMargin)
  {
    return new BookmarkMargin(textViewHost.TextView, bookmarkManager);
  }
}

让我们尝试理解应用于MarginFactory类的所有属性

  • Export:指示此类提供何种类型的导出,在本例中为IWpfTextViewMarginProvider
  • Name:指示提供的导出的名称,在本例中为BookmarkMargin.MarginName(一个常量)。
  • Order:指示 MEF 如何对扩展的多个实例进行排序/排列。
  • MarginContainer:此属性指定容器的名称(预定义常量),在本例中为PredefinedMarginNames.Right。其他选项可以是LeftRightTopBottomScrollBarZoomControlLineNumberSpacerSelectionGlyph等。
  • ContentType:声明扩展与特定类型内容(在本例中为代码)的关联。
  • TextViewRole:指定扩展应与哪种类型的视图关联,在本例中为Document。其他选项可以是EditableDebuggableZoomable等。
class BookmarkMargin : Border, IWpfTextViewMargin
{
  ...
}

IWpfTextViewMargin表示附加到 Visual Studio 编辑器边缘(IWPFTextView)的边距。

创建书签

书签是一个非常简单的 WPF 自定义控件,添加了一个椭圆。为了改善书签(称为图标)的整体外观和感觉,我们添加了一些资源进行了优化。我们还为工具提示创建了一个样式,当鼠标悬停在书签上时会显示该工具提示。

<UserControl ...>
<Canvas>
  <Ellipse Stroke="OrangeRed" Height="16" Width="16" 
         x:Name="ellipse" Canvas.Left="0" Canvas.Top="0">
    <Ellipse.Fill>
      <RadialGradientBrush 
        GradientOrigin="0.25, 0.15">
        <GradientStop Color="Orange" Offset="0.2"/>
        <GradientStop Color="OrangeRed" Offset="0.9"/>
      </RadialGradientBrush>
    </Ellipse.Fill>
  </Ellipse>
</Canvas>
</UserControl>
  
<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
  <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="HasDropShadow" Value="True"/>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ToolTip">
          <Border Name="Border"
            Background="{StaticResource LightBrush}"
            BorderBrush="{StaticResource SolidBorderBrush}"
            BorderThickness="1"
            CornerRadius="5"
            Width="{TemplateBinding Width}"
            Height="{TemplateBinding Height}">
            <ContentPresenter
              Margin="4" 
              HorizontalAlignment="Left"
              VerticalAlignment="Top" />
          </Border>
          <ControlTemplate.Triggers>
            <Trigger Property="HasDropShadow" Value="true">
              <Setter TargetName="Border" 
                 Property="CornerRadius" Value="5"/>
              <Setter TargetName="Border" 
                 Property="SnapsToDevicePixels" Value="true"/>
            </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
      </Setter.Value>
  </Setter>
</Style>

收集文件、行和列信息

为了获取当前打开的文档、行号和列号,我们将使用DTE2对象。DTE2对象代表 Visual Studio .NET IDE,是自动化模型层次结构中的最高对象。让我们获取DTE2的实例

private DTE2 GetDTE2()
{
  DTE dte = (DTE)GetService(typeof(DTE));
  DTE2 dte2 = dte as DTE2;

  if (dte2 == null)
  {
    return null;
  }

  return dte2;
}

我们可以使用GetService函数获取 Visual Studio 自动化模型 (DTE) 的实例(记住,它是Package类的一部分,并且我们的包继承自Package)。我们需要将 DTE 对象强制转换为DTE2对象。由于一些历史原因(MSDN 如此解释),我们必须执行这两个操作:首先获取 DTE 对象,然后将其转换为DTE2。我们可以使用DTE2对象的ActiveDocument属性来获取已打开文件的名称。

对于行号和列号,我们需要从 DTE2 对象的ActiveDocument.Selection.ActivePoint属性中提取VirtualPoint对象(当前光标位置)。行号和列号都从 1 开始(再次是历史原因,因为它来自 VB 脚本世界)。

string documentName = GetDTE2().ActiveDocument.Name;

VirtualPoint point = GetDTE2().ActiveDocument.Selection.ActivePoint;
int lineNumber = point.Line;
int columnNumber = point.DisplayColumn;

计算书签位置

计算书签位置相当简单,但书签位置究竟是什么意思?实际上,当用户选择在特定位置创建书签时,我们希望在边距中的相对位置创建书签。假设行号是 5,文档中的总行数是 20,那么我们希望书签放置在边距高度的大约四分之一(1/4th)处。为了计算位置,我们需要文档中的总行数和窗口的高度(即视口)。我们可以从IWpfTextView中获取这两项。现在的问题是,如何获得IWpfTextView实例。很简单,可以从IWpfTextViewHostTextView属性中检索。现在的问题是,如何获得IWpfTextViewHost。使用GetService函数获取SVsTextManager的实例;将其ActiveView作为IVsTextView提取。将IVsTextView强制转换为IVsUserData,并通过调用IVsUserDataGetData函数(使用预定义的DefGuidList.guidIWpfTextViewHost常量)来获取IWpfTextViewHost实例。这难道不简单吗?

private IWpfTextViewHost GetIWpfTextViewHost()
{
  IVsTextManager txtMgr = (IVsTextManager)GetService(typeof(SVsTextManager));
  IVsTextView vTextView = null;
  int mustHaveFocus = 1;
  txtMgr.GetActiveView(mustHaveFocus, null, out vTextView);

  IVsUserData userData = vTextView as IVsUserData;
  if (userData == null)
  {
    Trace.WriteLine("No text view is currently open");
    return null;
  }

  IWpfTextViewHost viewHost;
  object holder;
  Guid guidViewHost = DefGuidList.guidIWpfTextViewHost;
  userData.GetData(ref guidViewHost, out holder);
  viewHost = (IWpfTextViewHost)holder;
  return viewHost;
}

既然我们有了IWpfTextViewHost,让我们来计算书签的 Y 坐标。从TextViewTextSnapshot中获取行数和视口高度,然后计算位置。

// assuming that textView is an ojbect of IWpfTextView
private double GetYCoordinateFromLineNumber(int lineNumber)
{
  int totalLines = this.textView.TextSnapshot.LineCount;
  double ratio = (double)lineNumber / (double)totalLines;
  double yPos = ratio * textView.ViewportHeight;
  return Math.Ceiling(yPos);
}

所以我们现在有了位置;让我们谈谈计算和定位书签在边距上的复杂性。如果

  • 位置小于零或大于 ViewPortHeight:通过添加或减去几个像素来修复它,很简单。
  • private double AdjustYCoordinateForBoundaries(double position)
    {
      double currentPosition = position;
      double viewPortHeight = Math.Ceiling(textView.ViewportHeight);
      // here goes code to check boundary values and update 
      // y position accordingly
    
      return currentPosition;
    }
  • 同一个位置已经有另一个书签:在这种情况下,尝试找到下一个可用的位置。再次,如果那个位置已经有另一个书签,那么呢?然后找到下一个可用的位置。总的来说,编号书签在这种情况下所做的是,它会不断地在底部寻找下一个可用的位置,如果仍然找不到任何可用位置,它就开始在窗口顶部寻找位置,通过这种方式,它会为书签位置进行计算。另一个问题是如何检查那个位置是否已经有一个书签?我们遍历书签边距中的所有元素,并将 Y 坐标与 Y 位置进行比较。
  • private double AdjustYCoordinateForExistingBookmarks(double position)
    {
      return FindNextAvailableYCoordinate(position, 1);
    }
    
    public double FindNextAvailableYCoordinate(double position, int multiplier)
    {
      double currentPosition = position;
    
      foreach (UIElement item in marginCanvas.Children)
      {
        double topOfThisElement = Canvas.GetTop(item);
    
        if (Math.Abs(currentPosition - topOfThisElement) < BookmarkManager.BookmarkGlyphSize)
        {
          // here goes our code to handle managing clashing of positions
          // see source code for more details
        }
      }
    
      return currentPosition;
    }

管理所有书签

在讨论所有书签之前,让我们先简单谈谈书签。我们创建了一个Bookmark类来表示书签。它是一个非常简单的类,具有公有属性Number(书签编号)、LineNumberColumnNumberFileName,以及两个重载的构造函数来初始化变量。

为了管理所有书签,我们创建了一个BookmarkManager类。该类维护一个字典(书签编号作为键,Bookmark类的对象作为值)的所有书签,这些书签可通过公有属性Bookmarks访问。它还声明了一个BookmarksUpdated事件,该事件在字典发生任何更改时触发。为什么是这样?仅仅是为了在添加或删除另一个书签时更新边距。它还提供了转到特定书签编号(存储在其位置)的功能。我们只需使用DTE2和书签中存储的FileName找到ProjectItem。打开文档并激活它,然后将光标移动到文档的开头,然后移动到LineNumberColumnNumber提供的偏移量。

public void GotoBookmark(int position)
{
  Bookmark bookmark = Bookmarks[position];
  EnvDTE.ProjectItem document = dte2.Solution.FindProjectItem(bookmark.FileName);
  document.Open(BookmarkMargin.vsViewKindCode).Activate();
  EnvDTE.TextSelection selection = dte2.ActiveDocument.Selection;
  selection.StartOfDocument();
  selection.MoveToLineAndOffset(bookmark.LineNumber, bookmark.ColumnNumber);
}

AddBookmarkRemoveBookmark函数只需向 Bookmarks 字典添加一个条目并触发事件。

BookmarkGlyph(我们的 WPF 自定义控件)订阅MouseLeftButtonDownMouseRightButtonDown事件,并调用 bookmark manager 的GoToBookmarkRemoveBookmark函数来提供功能。

添加书签并处理事件

BookmarkMargin类通过向其中添加或删除书签来处理边距的更新。基本上,此类继承自IWpfTextViewMarginBorder类。我们将一个Canvas添加到其子项中。添加书签时,我们将它们添加到Canvas对象。BookmarkMargin类持有一个BookmarkManager的实例。在边距的构造函数中,我们调用UpdateBookmarks函数,该函数依次为每个书签调用UpdateBookmark。我们删除Canvas中的所有子项,然后创建所有书签并将其添加到子项中。

private void UpdateBookmarks()
{
  if (marginCanvas.Children.Count > 0)
  {
    marginCanvas.Children.Clear();
  }

  if (bookmarkManager != null)
  {
    foreach (Bookmark bookmark in bookmarkManager.Bookmarks.Values)
    {
      UpdateBookmark(bookmark);
    }
  }
}

private void UpdateBookmark(Bookmark bookmark)
{
  double yPos = GetYCoordinateForBookmark(bookmark);
  yPos = AdjustYCoordinateForBoundaries(yPos);
  yPos = AdjustYCoordinateForExistingBookmarks(yPos);
  BookmarkGlyph glyph;
  if (bookmark.Number != BookmarkManager.HelpBookmarkNumber)
  {
    glyph = CreateBookmarkGlyph(bookmark, yPos);
  }
  else
  {
    glyph = CreateHelpGlyph(bookmark);
  }
  marginCanvas.Children.Add(glyph);
}

CreateBookmarkGlyphCreateHelpGlyph使用特定属性创建BookmarkGlyph类的实例并返回它。

添加帮助书签

帮助书签就像其他书签一样,具有一些默认/固定的值。其书签编号为 99(为什么?因为我喜欢它),并且始终放置在书签边距的中间。在创建BookmarkGlyph对象时,我们识别书签并更改其属性,例如将文本更改为问号,将其填充颜色更改为绿色渐变,并提供与常规书签不同的工具提示(不提供代码,因为它非常直接)。

导出包和书签边距

这是创建包最重要的步骤之一。如果我们未能指定包的内容,它将无法按预期工作(书签边距将不会被创建)。这里需要注意的一个重要点是Description字段。如果此字段超过 280 个字符,则无法将其上传到 Visual Studio Gallery。您还可以在清单中提供两个图像,Visual Studio Gallery 将使用它们来显示。

让我们回到内容部分。单击“添加内容”按钮,选择 MEF 组件作为内容类型,选择项目作为源,然后从下拉列表中选择 NumberedBookmarks。

Add content dialog

确保将VS 包MEF 组件都添加到内容列表中。

Contents list

整合

让我们尝试从整体上理解这个系统。MarginFactory创建BookmarkMargin,并将BookmarkManager与此BookmarkMargin的特定实例关联起来。BookmarkMargin创建BookmarkGlyph并将书签图标添加到边距实例。BookmarkManager则维护所有书签的列表,并在添加或删除书签时触发BookmarksUpdated事件。BookmarkGlyph则跟踪关联的BookmarkManager,并调用其函数来处理鼠标按下事件。很简单!

试运行

是时候试试了。生成并启动解决方案(有或无调试),这将启动一个专门用于测试扩展的 Visual Studio 实例,称为实验实例。可以在此实例中调试扩展。

Debugging the extension

下载和安装

该扩展可以通过以下方式安装

  • 生成并安装:生成附加的解决方案,然后双击Release/Debug文件夹中的NumberedBookmarks.vsix。这将开始安装扩展。这通常是一键式安装。
  • 使用扩展管理器:转到工具->扩展管理器。在左侧导航面板中单击在线库,然后在搜索在线库文本框中键入 Numbered Bookmarks,然后按Enter。它将显示扩展,旁边有一个下载按钮。单击下载并按照步骤安装扩展。
  • 从 VS Gallery 下载:从 URL 下载扩展,然后双击下载的vsix文件(或者,您可以选择运行该应用程序)。

您可以在扩展管理器中卸载该扩展,方法是单击扩展旁边的卸载按钮。重新启动 Visual Studio 以使更改生效。

摘要

为 Visual Studio 创建扩展非常简单明了(并非总是如此),但有时也会很棘手。编号书签是我在这方面(特别是针对 VS 2010)的第一次尝试。请提供您的反馈和建议。不要忘记在 Visual Studio Gallery 上下载并评价我的扩展。

历史

  • 2010 年 2 月 25 日:初稿。
© . All rights reserved.