Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Numbered Bookmarks - Visual Studio Extension VSX 2010

0.00/5 (No votes)
1 Mar 2010 1  
A Visual Studio 2010 extension for creating numbered bookmarks.

Numbered bookmarks screenshot

Contents

Introduction

The bookmarks feature is something that I never liked much in Visual Studio. I never wanted 100’s of bookmarks. I just wanted some shortcuts for the most frequently used lines/sections. I didn’t find anything similar and even Microsoft didn’t want to add this feature to Visual Studio, so I went ahead and created this Visual Studio extension.

Numbered Bookmarks is a Visual Studio 2010 extension to create and recall bookmarks by using numbers. It allows the user to create 10 numbered bookmarks (starting from 0 to 9). The user can add or navigate to the particular bookmark by using the same shortcut key. The tool adds a bookmark margin to the Visual Studio editor, next to the scrollbar. Whenever a bookmark is created, a visual glyph is placed on the bookmark margin. The user can also create/navigate/clear bookmarks from the Numbered Bookmarks menu under the Tools menu.

Using Numbered Bookmarks is relatively easy:

  • Create bookmark: Press key combination Ctrl+Alt+Number to create a bookmark, where Number can be any number from 0 to 9.
  • Navigate to bookmark: Press key combination Ctrl+Alt+Number to move to the bookmark location.
  • Delete a bookmark: Right click on the bookmark, in the bookmark margin.
  • Delete all bookmarks: Press key combination Ctrl+Alt+Backspace.
  • Information about a bookmark: Mouse hover over the bookmark shows basic information about it, which includes the bookmark number, file name, line number, and column number.
  • How to use information: Mouse hover over the Green colored bookmark in the bookmark margin shows the basic how to.

Numbered bookmarks screenshot

Prerequisites

For developing Numbered Bookmarks, we need the following applications pre-installed:

Divide and conquer

The task of explaining a full-flexed Visual Studio extension can be very overwhelming depending on the complexity of the extension. Numbered Bookmarks is though a simple extension but yes it is a little complicated to explain (more so for a beginner). Let us try to break it into smaller tasks and complete them one by one, resulting in finding our goal. So, what are we waiting for? Let's get started.

Create the basic infrastructure

We can use the Visual Studio Package Wizard to generate the basic infrastructure. So let's go ahead and create the basics; the steps that you need to follow are:

  • Go to File->New and in the New Project dialog, select Other Project Types->Extensibility and select Visual Studio Package in the right pane. Name the package NumberedBookmarks and click OK.
  • Create new extensibility package

  • Click Next on the Welcome page of the Visual Studio Package Wizard. Select Visual C# as your programming language and select Generate a new key to sign the assembly option and click Next.
  • In the next step, provide information about the package. Modify the icon, company name, package name, package version, and package information, and click Next.
  • Package information page

  • Select the Menu Command option in the next step. It creates a menu entry in the Tools menu with the caption Numbered Bookmarks. Click Next.
  • In the next step, change the Command Name to Numbered Bookmarks and Command ID to cmdIDNumberedBookmarks and click Next.
  • Command information page

  • In the Test Project Options page, de-select both Unit Test Project and Integration Test Project and click Finish. Now our basic infrastructure is ready.
  • Basic infrastructure

Add menus and submenus

Apart from keyboard shortcuts, the user can also use menu options to create, move to, or remove all bookmarks. Let's add the submenus to the Numbered Bookmarks menu created in the earlier step. Finally our menus should look like this:

Numbered bookmarks sub menu

First of all, we will add command IDs to PkgCmdIDList.cs, these IDs are used to hook event handlers to the menu entries. There is already one entry in this class for the Numbered Bookmarks menu. We are not going to use this menu, rather we will add a submenu to the Tools menu, so delete the entries and create the command IDs for all menu entries. Finally, it looks like:

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

Now, before going further to make changes to the VSCT (Visual Studio Command Table) file for more menu options, let's create a bitmap menu strip for the menu icons (each icon of 16x16). Yes, we have to create a bitmap strip. For creating this bitmap strip, place all your images side by side in a new image and save the final image as a bitmap. Below is a sample of the bitmap strip which is used for Numbered Bookmarks. Don't forget to add it to Resources.

Bitmap menu strip

It's time to get our hands dirty. Open the NumberedBookmarks.vsct file. Scroll to the Bitmaps section and modify the section as:

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

In this section, we specify the location of the bitmap strip and also the names of all the images. Now, let's create symbols for all the images.

Scroll to the symbols section, you'll find a GUIDSymbol entry for guidImages. By default Visual Studio adds a bitmap strip to the package. Remove the section and add the following to it:

<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>

Notice that the images are numbered from 1 and not from 0, the value portion contains the index of the image in the bitmap strip from the left.

Each menu is contained inside a menu group. This way we have two menu groups (shocked?). Let me try to explain. We are adding a menu Numbered Bookmarks under the Tools menu and another sub menu beneath it. So we have two menus, Numbered Bookmarks menu and submenu. Both of these should have corresponding menu groups too. Let's name them MyMenuGroup and SubMenuGroup, respectively. All menu options are added to SubMenuGroup finally.

Menus and groups

We will divide this step in five parts:

  • First of all, let's add GUID symbols for all our commands (created earlier) and menus and menu groups. Make sure that all the values match the command IDs created earlier, otherwise event handlers will not be fired.
  • <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>
  • Secondly, add a Groups section and add entries for both the groups (as explained earlier). IDM_VS_MENU_TOOLS is a constant for the Tools menu.
  • <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>
  • Thirdly, add a Menus section and add a menu entry for Numbered Bookmarks. While adding a menu, you can provide a command name and caption to display. Yes, you cannot provide an icon for a submenu. Sigh!
  • <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>
  • Fourthly, let's add our sub menu entries to the file now. In order to add a menu, we use a Button tag inside a Buttons tag. We can provide the command name, caption, and icon for each of the menus. Other entries can also be added similar to the sample below:
  • <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>
  • Last, let's create keyboard shortcuts for our menus. This is known as key-binding. While adding a key binding, you can provide two modifiers and two keys. Strange again. Let's try to recall the key combination for Solution Explorer is (Ctrl+W, S), which can be divided in two parts. First being Ctrl+W and second being S. Ctrl is modifier1 and W is key1, similarly S is key2. The modifier can be a combination of Ctrl, Alt, or Shift separated by a space.
  • <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>

Finally, we reach the place where we can bind our commands to the event handlers. In the Initialize function of the package class (in this case, NumberedBookmarksPackage), we can bind the command with an event handler and in the same class, we can create an event handler for the same.

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) { }

In the case of Numbered Bookmarks, the functionality of adding bookmarks is the same for all 10 entries (numbers 0 to 9) and in the event handler, we call a function AddBookmark with an int parameter to specify which bookmark to add. So, I opted for anonymous methods in place of event handlers.

// 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) { }

Create bookmark margin

Creating a margin or bookmark margin is a twofold process. We need to create two classes, the first one inherited from IWpfTextViewMarginProvider, and the second one inherited from IWpfTextViewMargin. We need to export our factory (provider) class to MEF. As per the MEF philosophy 'You export, we import. We export, you import.' Here we export our provider class and MEF imports it, and this class tells MEF how to create a margin. The second class actually creates the margin. We have inherited it from Border to provide some basic WPF functionality like background color, width, height, etc. We could have opted for some other control or a custom control as well.

[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);
  }
}

Let's try to understand all the attributes applied to the MarginFactory class:

  • Export: Tells what type of export this class provides, which is IWpfTextViewMarginProvider in our case.
  • Name: Tells the name of the export provided, which is BookmarkMargin.MarginName (a constant) in our case.
  • Order: Tells MEF to order/arrange multiple instances of the extension.
  • MarginContainer: This attribute tells the name of the container (pre-defined constant), in our case it is PredefinedMarginNames.Right. Other options can be Left, Right, Top, Bottom, ScrollBar, ZoomControl, LineNumber, Spacer, Selection, Glyph, etc.
  • ContentType: Declares an association of the extension with a particular type of content, which is code in our case.
  • TextViewRole: Specifies what type of view the extension should be associated with, in our case it is Document. Other options can be Editable, Debuggable, Zoomable, etc.
class BookmarkMargin : Border, IWpfTextViewMargin
{
  ...
}

IWpfTextViewMargin represents a margin which is attached to an edge of the Visual Studio Editor (IWPFTextView).

Create bookmark

The bookmark is a very simple WPF custom control with an ellipse added to it. To improve the overall look and feel of the bookmark (called glyph), we polished it by adding a few resources. We also created a style for the tooltip, which is shown when the mouse moves over the bookmark.

<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>

Collect file, line, and column information

In order to get the currently opened document, line number, and column number, we will use the DTE2 object. The DTE2 object represents the Visual Studio .NET IDE and is the top-most object in the automation model hierarchy. Let's get an instance of DTE2:

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

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

  return dte2;
}

We can get an instance of the Visual Studio automation model (DTE) using the GetService function (remember, it is part of the Package class and our package is inherited from Package). We need to type cast the DTE object to a DTE2 object. Due to some historical reasons (this is how MSDN explains it), we have to perform these two operations, get DTE object first and then convert it to DTE2. We can get the name of the opened file using the ActiveDocument property of the DTE2 object.

For line and column number, we need to extract the VirtualPoint object (current cursor location) from the ActiveDocument.Selection.ActivePoint property of the DTE2 object. Both line and column number starts from 1 (again historical reasons, because it came from the VB scripting world).

string documentName = GetDTE2().ActiveDocument.Name;

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

Calculate bookmark positions

Calculating bookmark positions is fairly simple but what do we exactly mean with bookmark position? Actually, when the user opts for creating a bookmark at a particular position, we want to create a bookmark in the margin in a relative place. Let us assume that the line number is 5 and the total number of lines in the document is 20, then we want our bookmark to be placed somewhere around one-fourth (1/4th) the height of the margin. For calculating the position, we need the total number of lines in the document and the height of the window (which is the ViewPort). We can get both these things from IWpfTextView. Now the question comes, how to get the IWpfTextView instance. Simple, it can be retrieved from the TextView property of IWpfTextViewHost. Now the question is how to get IWpfTextViewHost. Get the instance of SVsTextManager using the GetService function; extract its ActiveView as IVsTextView. Cast IVsTextView as IVsUserData and get an IWpfTextViewHost instance by calling the GetData function of IVsUserData with the DefGuidList.guidIWpfTextViewHost pre-defined constant. Isn't it simple?

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;
}

As we have the IWpfTextViewHost now, let's calculate the Y position of the bookmark. Take the line count and viewport height from the TextSnapshot from TextView and calculate the position.

// 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);
}

So we have the position now; let's talk about some complexities in calculating and positioning the bookmark on the margin. What if:

  • Position is less than zero or greater than ViewPortHeight: Fix it by adding or subtracting a few pixels, as simple as that.
  • 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;
    }
  • We already have another bookmark at the same position: In this case, try to find the next available position for the bookmark. Again, if we have another bookmark at that position, then? Then find the next available position. Overall, what Numbered Bookmarks does in this case is, it keeps on searching for the next available location towards the bottom and if it is still not able to find any available location, then it starts finding a position towards the top of the window and this way, it makes calculations for the bookmark position. Another question is how to check if a bookmark is already there at that position? We iterate through all the elements in the bookmark margin and compare the y-coordinate with the y-position.
  • 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;
    }

Manage all bookmarks

Before coming to all bookmarks, let's talk a little bit about the bookmark. We created a Bookmark class that represents a bookmark. It is a very simple class with public properties for Number (bookmark number), LineNumber, ColumnNumber, and FileName, and two overloaded constructors to initialize the variables.

For managing all the bookmarks, we created a BookmarkManager class. This class keeps a dictionary (bookmark number is the key and the object of the Bookmark class is the value) of all bookmarks which are accessible through the public property Bookmarks. It also declares an event BookmarksUpdated, which gets fired whenever any changes to the dictionary are made. Why this? Simply to update the margin whenever another bookmark is added or removed. It also provides the functionality to go to a particular bookmark number (location stored with it). We simply find the ProjectItem using DTE2 and the FileName stored in the bookmark. Open the document and activate it and then move, move cursor to the start of the document, and then move to the offset provided by LineNumber and ColumnNumber.

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);
}

The AddBookmark and RemoveBookmark functions simply add an entry to the Bookmarks dictionary and fire the event.

BookmarkGlyph (our WPF custom control) subscribes to the MouseLeftButtonDown and MouseRightButtonDown events and calls GoToBookmark and RemoveBookmark of the bookmark manager to provide the functionality.

Add bookmarks and handle events

The BookmarkMargin class handles updating the margin by adding or removing bookmarks to it. Basically, this class inherits from IWpfTextViewMargin and the Border class. We add a Canvas to its children. While adding bookmarks, we add them to the Canvas object. The BookmarkMargin class holds an instance of BookmarkManager. In the constructor of the margin, we call the UpdateBookmarks function which in turn calls UpdateBookmark for each bookmark. We remove all the children from the Canvas and then create all the bookmarks and add them to the children.

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);
}

CreateBookmarkGlyph and CreateHelpGlyph create an instance of the BookmarkGlyph class with specific properties and return it.

Add help bookmark

Help bookmark is just like any other bookmark with some default/fixed values associated with it. Its bookmark number is 99 (why? because I love it), and it is always placed in the middle of the bookmark margin. While creating a BookmarkGlyph object, we identify the bookmark and change its properties like we change the text to question mark, its fill color to a green colored gradient, and provide a different tooltip than a regular bookmark (not providing the code as it is very straightforward).

Export package and bookmark margin

This is one of the most important steps of creating a package. If we fail to specify the content of the package, then it won't work as specified (bookmark margin will not be created). A most important point to note here is the Description field. If this field is more than 280 characters, then you can't upload it to the Visual Studio Gallery. You can also provide two images in the manifest, which will be used by the Visual Studio Gallery to show with it.

Let's come back to the content part. Click on the Add Content button and select MEF Component as the content type and choose Project as a source and select NumberedBookmarks from the dropdown.

Add content dialog

Make sure that both VS Package and MEF Component are added to the contents list.

Contents list

Bind it up

Let's try to understand the system as a whole. MarginFactory creates a BookmarkMargin and also associates a BookmarkManager to this particular instance of BookmarkMargin. BookmarkMargin creates BookmarkGlyph and adds bookmark glyphs to the margin instance. BookmarkManager in place keeps a list of all the bookmarks and fires the BookmarksUpdated event when a bookmark is added or removed. BookmarkGlyph in place keeps a track of the associated BookmarkManager and calls its functions for handling mouse down events. Simple!

Give it a dry run

It's time to give it a try. Build and start the solution (with or without debugging), this starts an instance of Visual Studio which is specially meant for testing extensions, known as Experimental Instance. Extensions can be debugged in this instance.

Debugging the extension

Download and installation

The extension can be installed in the following ways:

  • Build and install: Build the attached solution and double click NumberedBookmarks.vsix in the Release/Debug folder. This will start the installation of the extension. This is usually a single click installation.
  • Use Extension Manager: Go to Tools->Extension Manager. Click Online Gallery in the left navigation panel and type Numbered Bookmarks in the Search Online Gallery text box and click Enter. It will show the extension with a Download button next to it. Click Download and follow the steps to install the extension.
  • Download from VS Gallery: Download the extension from this URL and double click the vsix file downloaded (alternatively, you can choose to run the application).

You can uninstall the extension from Extension Manager, by clicking the Uninstall button next to the extension. Restart Visual Studio for the changes to take effect.

Summary

Creating extensions for Visual Studio is pretty simple and straightforward (not always) but this can be tricky sometimes. Numbered Bookmarks is my first attempt in this direction (specially for VS 2010). Please provide your feedback and suggestions. Don't forget to download and rate my extension on Visual Studio Gallery.

History

  • Feb 25, 2010: Initial draft.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here