Click here to Skip to main content
15,884,176 members
Articles / Programming Languages / C#

Visual Studio Extensions - from Add-in to VSPackage

Rate me:
Please Sign up or sign in to vote.
4.97/5 (8 votes)
5 Feb 2014CPOL9 min read 46.2K   980   32  
Move your (deprecated) Add-in to VSPackage...

Introduction

With Visual Studio 2013 out - for ages - Microsoft decided to mark Add-ins as deprecated...

Visual Studio add-ins are deprecated in Visual Studio 2013. You should upgrade your add-ins to VSPackage extensions.

This sentence is now on every help page in MSDN, that talks about Add-ins. The last part - not quoted - of the paragraph offers a link to a sample on how to make the move, but IMHO it worthless.

So here is my version of how to move from Add-in to VSPackage...

Background

Just to see where we are, a short summary of the ways one can extend Visual Studio IDE...

Macro

A macro is a series of commands and instructions that you group together as a single command to accomplish a task automatically.

This state - from Microsoft - is clear, I add only that the macro engine is the some you found in the Office product line, and didn't changed since Visual Studio 6. The truth is that I know no-one using it...

Add-in

Add-ins are much more powerful, as the exposed COM interface enable the you to access Visual Studio's object model on different layers - for different purposes. However at the bottom line Add-ins are good to add and handle commands fired from the IDE, nothing more...

VSPackage

With this approach you can do anything (almost). Add new toolbox, editor, menu and toolbar. In fact a large part of the IDE's functionality come from integrated packages made by the Visual Studio Developer Team. It means that from the IDE's side your package is not different from the ones made by Microsoft.

Managed Extensibility Framework (MEF)

MEF(^) is a part of the .NET framework, that originally developed independently, than merged into version 4.0. With Visual Studio 2010 out the whole editor was rewritten using WPF. This became the very first part of the IDE not build on COM ideas, and immediately put in use the MEF abilities. The second - huge - advantage (after not using COM) is the most simple deployment - only copy the resulting dll to the specific folder, and done. Since 2010 this idea was not used in other fields as still today Visual Studio is 99% COM.

Even MEF extensions are grow in numbers its usage is limited by the type of the extension, so as today most of the extensions still using the VSPackage methodology. In the following lines I will show a parallel sample of an Add-in and a VSPackage, both doing the same thing to explain how you may get your knowledge to the next level...

Using the code

In the body of the article I will show some code (a lot?), but these pieces of code not compile into a full, working solution, for that see the attached source code...

The code is written in Visual Studio 2013 - but it backward compatible down to 2010 (the code! not the project).

The most useless extension ever

The sample I will use is truly useless, as it does nothing, but enables to choose some properties for any .cs file in your project - form a drop-down list...

That value will be saved into the project file and will persisted.

XML
<ItemGroup>
  <Compile Include="Default.aspx.cs">
    <DependentUpon>Default.aspx</DependentUpon>
    <SubType>ASPXCodeBehind</SubType>
    <ItemColor>Blue</ItemColor>
  </Compile>
</ItemGroup> 

As much as the extension is useless it already has all the major elements an Add-in usually have:

  • Create a new menu item (drop-down in our case) and add it to the desired menu
  • Handle events from the item and from the IDE, namely:
    • Check status of the menu item (enable/disable, hide/show)
    • In the case of our drop-down, there is a need to fill it with data for selection
    • Set the currently selected value based on the persisted one
    • Handle new selection from the user

Now that we know what the extension have to do I will show the code side-by-side (OK - one below the other), one for the old ways of Add-in and one for the new ways of VSPackage...

Creating menu item

Add-in 

C#
Command oCommand = oCommands.AddNamedCommand2(
    _AddIn,
    "ItemColorAddIn",         // CommandName
    "Item Color",             // ButtonText
    "Select color for Item",  // TooltipText
    false,
    null,
    null,
    ( int )vsCommandStatus.vsCommandStatusSupported + ( int )vsCommandStatus.vsCommandStatusEnabled,
    0,
    vsCommandControlType.vsCommandControlTypeDropDownCombo
); 

It's very simple, you just pass in some parameters - command name, display name, default status, kind of item - and got back a nice object representing the drop down.

VSPackage

XML
<Commands package="ItemColorPackagePkg">
  <Combos>
    <Combo guid="ItemColorPackage" id="Command" idCommandList="CommandList" type="DropDownCombo" defaultWidth="30" priority="0">
      <Parent guid="ItemColorPackage" id="Group" />
      <CommandFlag>DynamicVisibility</CommandFlag>
      <Strings>
        <ButtonText>Item Color</ButtonText>
        <CommandName>ItemColorPackage</CommandName>
        <ToolTipText>Select color for Item</ToolTipText>
      </Strings>
    </Combo>
  </Combos>
</Commands> 

Wow! Now that is a big change. Yes, in VSPackage you define commands in an XML file. The Visual Studio Command Table (^) is the new way of declaring items and there hierarchy (see in next section). You will have to learn it from inside-out before creating your first serious VSPackage. For now it's enough to understand to conversion between the two.  

  • While in Add-in you declared the drop-down directly in code, so no extra identification needed, in VSPackage you assign guid and id to the drop-down to enable later reference to it from the code.
  • There is no default status for the drop-down in VSPackage - no need of this as the IDE assigns visibility by default...
  • The <Parent> element is also part of the declaration while in Add-in it is in the code. 

Assign menu item to the IDE 

Add-in

C#
CommandBar oCommandBar = ( ( CommandBars )_Application.CommandBars )[ "Item" ];
oCommand.AddControl( oCommandBar );

It's pretty easy - pick your menu - Item in our case - and add the item to it...  

VSPackage 

XML
<Groups>
  <Group guid="ItemColorPackage" id="Group" priority="0">
    <Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
  </Group>
</Groups> 

It's also very easy in this side. The main difference is that you do not add the new item directly to the existing IDE menu, but create a container - Group - for that. If you look into the previous sample the Parent element identifies the group in this exactly (guid-id pair). 

Note:  If the menu item you want to extend is not one of the well-known you are in a bit of trouble. The documentation for this is very poor at MSDN, so you may spend a lot of time to figure out which guid to use. Here two links I'm using to start with - GUIDs and IDs of Visual Studio Menus and VsMenus Class

Add event handler for the new item

Add-in 

You do not add any event handler. When you created the new item (AddNamedCommand2), the very first parameter identified you as the owner. Every time there will be an event on your item the IDE will call you via the IDTCommandTarget interface (will see it's implementation in a moment). The interface has to methods to implement, Exec and QueryStatus. All the functionality goes via these methods, sometime in an awkward way... 

VSPackage 

OleMenuCommandService oOleMenuCommandService = ( OleMenuCommandService )GetService( typeof( IMenuCommandService ) );

if ( oOleMenuCommandService != null )
{
  CommandID oCommandID = new CommandID( Consts.ItemColorPackage, ( int )Consts.Command );

  OleMenuCommand oCommand = new OleMenuCommand( CommandInvoke, oCommandID );

  oOleMenuCommandService.AddCommand( oCommand );
}

The IDE offers some services to extension developers (^). These services are in fact interfaces that the extension can consume (it should be also a service offered by some other extension and not by the core IDE). The service we use here is the OleMenuCommandService that implements all the functionality need to handle a menu item (command) inside the IDE. The steps to hook up to that service are: 

  • Find the service
  • Create a command object out of the the guid-id info we also used in the VSCT file... 
  • Add the command object to the service  

...  

As mentioned earlier there are three events we interested in. One for fill the drop-down with it's value list, one for provide the status of the drop-down in the current state of the IDE (visibility mostly), one for set or get the current value of the drop-down. Now we will go and see all of them... 

Fill the drop-down 

Add-in

C#
public void Exec ( string CommandName, vsCommandExecOption ExecuteOption, ref object In, ref object Out, ref bool Handled )
{
  if ( CommandName == "ItemColorAddIn.Connect.ItemColorAddIn_1" )
  {
    Out = new string[ ] { "Red", "Green", "Blue" };
    Handled = true;
  }
}  

As you can see, all we do is handling the Exec method with the ItemColorAddIn.Connect.ItemColorAddIn_1 command name, while returning a list of strings for values.

It's maybe the best - and only - place to look into, how command names for Add-in generated. You probably remember that when creating the drop-down we assigned the name ItemColorAddIn. So from where ItemColorAddIn.Connect.ItemColorAddIn_1 comes? The IDE uses the format of namespace.class.command format to uniquely identify your command. That's simple but what that _1 does?! Remember? IDTCommandTarget has only two methods! So overcome this the IDE creates a second command name for drop-down - with the _1 addition - to use when drop-down need values...  

VSPackage 

C#
CommandID oCommandListID = new CommandID( Consts.ItemColorPackage, ( int )Consts.CommandList );

OleMenuCommand oCommandList = new OleMenuCommand( CommandListInvoke, oCommandListID );
oOleMenuCommandService.AddCommand( oCommandList ); 
XML
<Combo guid="ItemColorPackage" id="Command" idCommandList="CommandList" type="DropDownCombo" defaultWidth="90" priority="0"> 
C#
private void CommandListInvoke ( object sender, EventArgs e )
{
  OleMenuCmdEventArgs oOleMenuCmdEventArgs = ( OleMenuCmdEventArgs )e;

  if ( oOleMenuCmdEventArgs.OutValue != IntPtr.Zero )
  {
    Marshal.GetNativeVariantForObject( new string[ ] { "Red", "Green", "Blue" }, oOleMenuCmdEventArgs.OutValue );
  }
}  

IMHO VSPackage has a much better approach - you want a command? Add it. That's exactly what I've done int the code. Inside the VSCT file I attach the very same command to the idCommandList attribute, that is there exactly for that... Now, every time the drop-down needs a list of values the IDE will invoke the command just attached for that purpose. The third part of this code sample shows the handler that here too returns a list of strings as the values for the drop-down.

Query status

Add-in

C#
public void QueryStatus ( string CommandName, vsCommandStatusTextWanted NeededText, ref vsCommandStatus Status, ref object CommandText )
{
  if ( ItemToHandle( ) )
  {
    Status = ( vsCommandStatus )vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
  }
  else
  {
    Status = ( vsCommandStatus )vsCommandStatus.vsCommandStatusInvisible;
  }
} 

It's very simple - if the item we deal with is one that we should handle, show the drop-down, otherwise hide it. 

VSPackage 

C#
oCommand.BeforeQueryStatus += CommandQueryStatus; 
C#
private void CommandQueryStatus ( object sender, EventArgs e )
{
  OleMenuCommand oCommand = ( OleMenuCommand )sender;

  oCommand.Visible = ItemToHandle( );
}
XML
<CommandFlag>DynamicVisibility</CommandFlag> 

First we have to bind some method to BeforeQueryStatus event. From there the only change here that we set visibility directly on the drop-down. Notice that changes on visibility work only because that CommandFlag we have on the drop-down in the VSCT file... 

Note: ItemToHandle is a simplification of how to check if the selected project item is one that we want to deal with. I do not detail it's functionality here, as that will throw the article to a very different path. You can see the exact code in the sample attached... 

Value setter/getter

Add-in

C#
public void Exec ( string CommandName, vsCommandExecOption ExecuteOption, ref object In, ref object Out, ref bool Handled )
{
  if ( CommandName == "ItemColorAddIn.Connect.ItemColorAddIn" )
  {
    uint nItemId = GetSelectedItem();

    if ( string.IsNullOrEmpty( Convert.ToString( In ) ) )
    {
      string szOut;

      GetItemAttribute( nItemId, "ItemColor", out szOut );

      Out = szOut;
    }
    else
    {
      SetItemAttribute( nItemId, "ItemColor", Convert.ToString( In ) );
    }

    Handled = true;
  }
} 

The IDE calls IDTCommandTarget's Exec method - this time with the original command name - and if In parameter has value you do set otherwise it's a get and you should return the current value using Out parameter... 

VSPackage

C#
private void CommandInvoke ( object sender, EventArgs e )
{
  OleMenuCmdEventArgs oOleMenuCmdEventArgs = ( OleMenuCmdEventArgs )e;
  uint nItemId = GetSelectedItem();

  if ( oOleMenuCmdEventArgs.OutValue != IntPtr.Zero )
  {
    string szOut;

    GetItemAttribute( nItemId, "ItemColor", out szOut );

    Marshal.GetNativeVariantForObject( szOut, oOleMenuCmdEventArgs.OutValue );
  }
  else if ( oOleMenuCmdEventArgs.InValue != null )
  {
    SetItemAttribute( nItemId, "ItemColor", Convert.ToString( oOleMenuCmdEventArgs.InValue ) );
  }
} 

This part is almost identical - CommandInvoke is the event handler assigned to the new menu item, and replaces the Exec method from the Add-in. There is a minor difference in how we decide if it's a getter or setter event, but... 

Note: GetSelectedItem, GetItemAttribute and SetItemAttribute are the same as ItemToHandle in the previous section, see the attached demo...  

Summary

Move you extension development to VSPackage is a good thing (regardless of the deprecated thing). You got much more flexibility and control over you code. However VSPackage is a not-too-tight wrapper around the COM based heart of Visual Studio, so when you do VSPackage you will step into COM world, and you will have to learn about marshaling and how COM uses guid. You also have code and declarative approach inside the same pack, and you will have to take care for the very same guid's while you use them from code and VSCT (in fact the same guid will be defined in two places, so you will to be extra careful while changing and adding new ones).

As for myself , I like to work from a single environment, so I used to write a lot of simple and small extensions to fulfill my needs without leaving Visual Studio IDE. If you are the same and got a big pile of Add-ins this article may help to move them easily to VSPackage... 

Points of Interest

When you will be deep inside the upgrade you may found out that Visual Studio extensions can go a bit too complicated very easily. If you new to the subject do some reading before:

Microsoft.VisualStudio.Shell 

DTE (Development Tools Environment) 

Microsoft.Build.Evaluation 

These links will teach you about the ways you can access the IDE from inside your extension and may help to do jobs in a better way...  

History 

5th of February, 2014 - Original post 

License

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


Written By
Software Developer (Senior)
Israel Israel
Born in Hungary, got my first computer at age 12 (C64 with tape and joystick). Also got a book with it about 6502 assembly, that on its back has a motto, said 'Try yourself!'. I believe this is my beginning...

Started to learn - formally - in connection to mathematics an physics, by writing basic and assembly programs demoing theorems and experiments.

After moving to Israel learned two years in college and got a software engineering degree, I still have somewhere...

Since 1997 I do development for living. I used 286 assembly, COBOL, C/C++, Magic, Pascal, Visual Basic, C#, JavaScript, HTML, CSS, PHP, ASP, ASP.NET, C# and some more buzzes.

Since 2005 I have to find spare time after kids go bed, which means can't sleep to much, but much happier this way...

Free tools I've created for you...



Comments and Discussions

 
-- There are no messages in this forum --