Click here to Skip to main content
Click here to Skip to main content

Visual Studio Add-in Library

, 15 Sep 2010
Rate this:
Please Sign up or sign in to vote.
This library allows you to quickly and simply create add-ins for VS2008 and VS2010 (VS2005?).

Introduction

It has been a long time I wanted to natively integrate all our tools into Visual Studio, but due to lack of time and desire, I always thought: "OK, I'll see this later", and nothing was done... So this summer, I started to dig the Visual Studio plug-in APIs (EnvDTE), and while browsing CodeProject and more generally the Web, I discovered that in fact no one has a clear and simple solution to create add-ins for Visual Studio. For older versions, people where talking about packages and pure COM calls; for new ones (VS2005+), the Add-in Project template was used, and each article I read had its own implementation and limitation. Moreover, even the MS documentation wasn't clear, giving sometimes samples with different implementations for the same requirement. So, this library is a compilation of practices (that work!) that I picked around and reorganized to simplify the creation of add-ins and the associated menu items/commands.

Using the code

First of all .. create a new plug-in project like in figure 1 (click Next until the end)!

Figure 1

Then you finish with a structure like figure 2, with the file "Connect.cs" opened, if you choose C# for language.

Figure 2

Note:

  • "TestAddin - For Testing.AddIn" is a shortcut to your Addins Visual Studio 2008 folder. We'll see next how to configure it.
  • "CommandBar.resx" is a resource file containing menu translations. You need it if you want your add-in to find the right VS menu to add your own commands.
  • "Connect.cs" is the main class containing methods called by VS.

If you look at the source code of "connect.cs", you'll see that it implements only one interface, "IDTExtensibility2".

public class Connect : IDTExtensibility2 

It seems enough to create a plug-in? Yes? No, it doesn't; if you search on the web, you'll see that some samples implement "IDTCommandTarget" to gain more control on commands. Have a look at this article: How to: Expose an Add-In on the Tools Menu (Visual C#) ... This will show you all the code you need to just add one command to the "Tools" menu.

So the main idea is how to simplify all of this.

Here are the steps I followed:

  1. Create a class "CommandManager.cs" that will be the base class for the "Connect.cs" generated class.
  2. Move methods and Interfaces from "Connect.cs" to "CommandManager.cs".
  3. ...
    
    public class CommandManager : IDTCommandTarget, IDTExtensibility2
    {
      ...
    }
    
    ...

    Result: Compilation OK / Execution failed.

  4. The class is not COM visible, so modify the class declaration ...
  5. ...
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    public class CommandManager : IDispatch, IDTCommandTarget, IDTExtensibility2
    {
      ...
    }
    ...

    Result: Compilation OK / Execution OK!

  6. Create a simple architecture for the library users.

Now add the reference to the library (Figure 3).

Figure 3

Note:

  • "VStudioHelpers.cs" contains a few methods to access the active solution, project files ...
  • "BaseMenu.cs" is the base class that you need to derive from to create a new VS command.
  • "CommandManager.cs" is the base class that your plug-in "Connect" class must derive from.

Prepare the resource files for localization.

  1. This will allow your plug-in to select the correct translations based upon the VS language. From the Plugin Add New Item ...
  2. Warning: the resource file must follow this filename convention: Resources.[language].resx.

    Add at least two resource language files (e.g.: en and fr).

  3. Add these strings to the resources files (they will be used later):
  4. ...
    <data name="MY_DESCRIPTION" xml:space="preserve">
    
      <value>Desc EN</value>
    </data>
    <data name="MY_INFOS" xml:space="preserve">
    
        <value>no infos EN</value>
    </data>
    <data name="MY_NAME" xml:space="preserve">
    
        <value>Plugin EN</value>
    </data>....
  5. Add a bitmap (16x16/32 bits/BMP format) to the resource files and rename it "2".
  6. ...
    
    <data name="2" type="System.Resources.ResXFileRef, System.Windows.Forms">
      <value>Resources\2.bmp;System.Drawing.Bitmap, System.Drawing, 
         Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
    </data>

Now, we'll use the library.

  1. Remove the body of the Connect class, derive it from "VisualStudio.AddInLibrary.CommandManager", and remove the unnecessary "using" clauses:
  2. using System;
    using VisualStudio.AddInLibrary;
    
    namespace TestAddin
    {
        public class Connect : CommandManager
        {
            /// <summary>Implements the constructor for the Add-in object.
            /// Place your initialization code within this method.</summary>
            public Connect()
            {
    
            }
        }
    }
  3. You must override the base "CreateCommands" method and implement the parameters initialization before creating your commands (here, "ToolsMenu"):
  4. // <summary>
    /// creates plugin commands
    /// </summary>
    
    protected override void CreateCommands()
    {
        // we store a reference to the plugin assembly for use in the library
    
        PluginAssembly = this.GetType().Assembly;
    
          // parameters used to for each commands
          BaseMenuParams _params = new BaseMenuParams();
          _params.AddInInstance = _addInInstance;
          _params.ResourceAssembly = PluginAssembly;      
          _params.VisualStudioInstance = _applicationObject;
    
          // !!!!!!!!!!!!!!!!!! create your menus HERE  !!!!!!!!!!!!!!!!!!!!!!
          AddCommand(new ToolsMenu(_params));
          // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
    }

Your Connect class is now ready!

How to add commands to the "Tools" menu: ToolsMenu.cs

Add a new class named "ToolsMenu" to the plug-in and derive it from BaseMenu and implement the "IMenuImplementation" interface:

using System;
using VisualStudio.AddInLibrary.Menus;

namespace TestAddin
{
  class ToolsMenu : BaseMenu, IMenuImplementation
  {
    #region IMenuImplementation Members
    public void CreateCommands()
    {
      throw new NotImplementedException();
    }

    public void Install()
    {
      throw new NotImplementedException();
    }

    public void UnInstall()
    {
      throw new NotImplementedException();
    }

    public MenuState GetState(string command, 
           EnvDTE.vsCommandStatusTextWanted NeededText, 
           ref object CommandText)
    {
      throw new NotImplementedException();
    }

    public bool IsItMycommand(string command)
    {
      throw new NotImplementedException();
    }

    #endregion

  }
}

Add the constructor and call the base constructor.

public ToolsMenu(BaseMenuParams _params): base(_params)
{
}

Now we'll add two commands. We need to create them in the CreateCommands method:

public void CreateCommands()
{
      // first clear list
      ClearCommands(this);

      // add commands individually
      AddCommand(new VSCommandInfo()
      {
        Name = "NEW",
        Owner = this,
        HaveSeparator = false,
        Caption = "New",
        Position = 1,
        ToolTip = "",
        BitmapResourceId = 2,
        UseOfficeResources = false    
        Action = new PerformActionForVSCommandDelegate(RunCommand_New)   
      });

      AddCommand(new VSCommandInfo()
      {
        Name = "EDIT",
        Owner = this,
        HaveSeparator = false,
        Caption = "Edit",
        Position = 2,
        ToolTip = "",
        BitmapResourceId = 59,
        UseOfficeResources = true

      });
    }

Here's some explanation about the parameters:

  • Name: the command name used by the system to identify a command
  • Owner: instance of the class owning the command
  • HaveSeparator: does this command need a separator?
  • Caption: menu title of the command
  • Position: order of the menu title in the menu list
  • ToolTip: tooltip of the command
  • BitmapResourceId: ID (only numbers) of the BMP that will be shown in the menu
  • UseOfficeResources: if yes, search BitmapResourceId in Office DLLs; otherwise search bitmap in plug-in resources
  • Action: method called when command is invoked (menu item clicked)

Implement the Install method:

/// <summary>
/// Creates main Menu and subMenus
/// </summary>

public void Install()
{
  // build and store commands

  CreateCommands();

  // create our menu with associated commands
  try
  {
    AddMenu(this, // the class instance
            "Tools",  // the VS Menu / Command we want to attach too

            // flag telling base class that
            // we attach to main menu (File,Edit ...)
            true,
            // Our Main Menu name ...
            // here extracted from plugin resources

            ResourceMANAGER.GetString("MY_NAME"),
            // Popup = create a menu with each commands
            // as submenus under choosed VS menu
            MenuType.Popup,
            1, // position or our main menu
            true); // true = add separator

  }
  catch (Exception ex)
  {
    // do what you want ...
  }
}

Implement all the remaining methods:

/// <summary>
/// Remove menu and submenus
/// </summary>

public void UnInstall()
{
  RemoveMenu(this);
}

/// <summary>
/// Returns information about a command to VS
/// </summary>
/// <param name="command">command name</param>
/// <param name="NeededText">VS query status</param>
/// <param name="CommandText">command caption</param>
/// <returns></returns>

public MenuState GetState(string command, 
       vsCommandStatusTextWanted NeededText, ref object CommandText)
{
      MenuState m = MenuState.Enabled;

      bool bsupport = VStudioHelpers.IsActiveProjectSupported(VStudioApplication);
      /* --------------- INFO --------------------------
       * bsupport will be set to true if when running the plugin 
       * - a csharp project is opened
       * - a Web or Winform application project is opened
      ---------------------------------------------*/

      // translate VS command in "readable" status
      QueryMenuVSCommandStatus status = TranslateCommandState(NeededText);

      // perform action ...
      switch (status)
      {
        // 2: need to change command caption ?
        case QueryMenuVSCommandStatus.Step2_GetText:
          if (!bsupport)
          {
            VSCommandInfo cmd = GetCommand(command);
            if (cmd != null)
              CommandText = cmd.Caption + " - NOT SUPPORTED";
            else

              CommandText = "NOT SUPPORTED";
          }
          break;

        // 3 : do we enable/disable/hide the command in the menu ?
        case QueryMenuVSCommandStatus.Step3_FinalState:
          if (!bsupport)
            m = MenuState.Disabled;
          else
            m = MenuState.Enabled;
          break;
      }
      return m;
}

/// <summary>

/// Does this command belongs to this instance ?
/// </summary>
/// <param name="command">command</param>

/// <returns></returns>
public bool IsItMycommand(string command)
{
  return IsItMyCommand(this, command);
}

Final steps are to create the methods to be run individually by each command.

/// <summary>
/// Code associated with command "NEW"
/// see "Action = new PerformActionForVSCommandDelegate(RunCommand_New)"
/// </summary>

private void RunCommand_New()
{
      // do what you want after running custom actions
      bool bSolutionLoaded = VStudioHelpers.isSolutionLoaded(VStudioApplication);

      if (bSolutionLoaded)
      {
        ProjectType pfound = null;
        EnvDTE.Project p = VStudioHelpers.GetActiveProject(VStudioApplication);
        if (p != null)
        {
          if (VStudioHelpers.IsSupportedProject(p, out pfound))
          {
            System.Windows.Forms.MessageBox.Show("NEW");
          }
          else

            System.Windows.Forms.MessageBox.Show("Unsupported Project !");
        }
        else
          System.Windows.Forms.MessageBox.Show("No project loaded !");
      }
      else
        System.Windows.Forms.MessageBox.Show("No Solution loaded !");
}

Your code is now ready!

Editing the add-in config file

Here is the original file content:

<?xml version="1.0" encoding="UTF-16" standalone="no"?>

<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
    <HostApplication>
        <Name>Microsoft Visual Studio Macros</Name>
        <Version>9.0</Version>

    </HostApplication>
    <HostApplication>
        <Name>Microsoft Visual Studio</Name>
        <Version>9.0</Version>

    </HostApplication>
    <HostApplication>
        <Name>Microsoft Visual Studio Macros</Name>
        <Version>8.0</Version>

    </HostApplication>
    <HostApplication>
        <Name>Microsoft Visual Studio</Name>
        <Version>8.0</Version>

    </HostApplication>
    <Addin>
        <FriendlyName>TestAddin - No Name provided.</FriendlyName>
        <Description>TestAddin - No Description provided.</Description>

        <Assembly>D:\Documents and Settings\path\TestAddin\bin\TestAddin.dll</Assembly>
        <FullClassName>TestAddin.Connect</FullClassName>
        <LoadBehavior>0</LoadBehavior>

        <CommandPreload>0</CommandPreload>
        <CommandLineSafe>0</CommandLineSafe>
    </Addin>
</Extensibility>

Adding support for Visual Studio 2010

Copy the file in the Visual Studio 2010 add-in folder and just add this:

<HostApplication>
    <Name>Microsoft Visual Studio Macros</Name>
    <Version>10.0</Version>

</HostApplication>

<HostApplication>
    <Name>Microsoft Visual Studio</Name>
    <Version>10.0</Version>

</HostApplication>

Getting strings from resources

Replace the code by this one below ... VS has a special syntax: @, that indicates to load from a resource ID. Note that these are the same IDs we used in our resource files.

<FriendlyName>@MY_NAME</FriendlyName>
<Description>@MY_DESCRIPTION</Description>
<AboutBoxDetails>@MY_INFOS</AboutBoxDetails>

How to test?

  1. Warning: the project name (Solution Explorer) must match the project assembly name (project properties) to allow the plug-in to find resources.
  2. Put the "TestAddin.AddIn" file into the VS add-in folder.
  3. Edit the file and replace:
  4. <Assembly>TestAddin.dll</Assembly>

    with:

    <Assembly>[full path to ]\TestAddin.dll</Assembly>
  5. Run the project and view the results.

Results

The plug-in appears in the Tools menu, but is deactivated because no C# project was loaded (this is our rule defined in the GetState method).

The plug-in menus are now active when a C# project is loaded:

And now if we want to change the way we want the menus to appear, replace:

AddMenu(this, // the class instance
    "Tools",  // the ENGLISH Visual Studio Menu / Command we want to attach too
    true, // flag telling base class that we attach to main menu (File,Edit ...)
    ResourceMANAGER.GetString("MY_NAME"), //  Our Main Menu name ...
                                          // here extracted from plugin resources
    MenuType.Popup, // Popup = create a menu with each
                    // commands as submenus under choosed VS menu
    1, // position or our main menu
    true); // true = add separator

with:

AddMenu(this, // the class instance
    "Tools",  // the ENGLISH Visual Studio Menu / Command we want to attach too
    true, // flag telling base class that we attach to main menu (File,Edit ...)
    ResourceMANAGER.GetString("MY_NAME"), //  Our Main Menu name ... 
                                          //  here extracted from plugin resources
    MenuType.Default, // <--- HERE
    1, // position or our main menu
    true); // true = add separator

We get the result below. You will notice that we do not have a sub-menu any more.

Conclusion

When I started, this project was just for "fun", and when I crawled the Web and saw that no one had a simple solution to create add-ins, I decided to try it myself... and finished by creating this library. It was very difficult to determine what were the best approaches, but this is mine... You may think that some pieces of code could be optimized, and you are welcome if you have any suggestions, advices ...to enhance them. Browse the source code, it will give you more ideas than me! I hope that this will help you a lot.

History

  • 2010 September 10: Initial version.
  • 2010 September 15: Updated article and source code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

KL@DotNet
Program Manager Digitas
France France
No Biography provided

Comments and Discussions

 
GeneralRe: Downloads for article? PinmemberKL@DotNet15-Sep-10 22:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 15 Sep 2010
Article Copyright 2010 by KL@DotNet
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid