Lessons learned from Copy to Html Add-in

Published: 21 Dec 2006
By: Derek Smyth

This article contains some lessons learned from creating an add-in for Visual Studio 2005 that added functionality to the context menu of the code editor window which allowed source code to be copied as HTML.

Introduction

I wrote the Copy-To-Html add-in to learn more about extending the Visual Studio IDE – it’s a perfect mix of usefulness and simplicity. This article uses the addin as the foundation to learning about VS.NET’s extensibility capabilities. The original Copy-To-HTML addin was written by Colin Coller and although I’ve copied its name and concept, credit for the idea squarely goes to him (apologies if someone did it before Colin).

Commands in Visual Studio

Before starting the walkthrough there is a concept relating to add-ins that needs to be understood. Add-ins in Visual Studio work using the concepts of commands. Commands, or named commands as they are referred to, are an encapsulation of functionality that can be attached to a menu, keyboard shortcut, or a toolbar button. Menus and toolbars are referred to as Command Bars in the Visual Studio environment and that makes sense, commands are mapped to command bars. If you are familiar with the command design pattern this philosophy should be familiar to you.

The whole of the Visual Studio environment appears to have been developed along this approach. Each action performed in the Visual Studio IDE has an associated command. You can view these commands and their mapping to keyboard shortcuts by selecting Tools->Options->Keyboard. You can also type the commands into the command window to invoke them, note that the command window supports full intellisense. The commands are basically found by the name of the menu that they appear in. For example the Edit->Copy command can be ran by typing Edit.Copy into the command window.

Although they do not need to use the command approach the majority of add-ins you might want to create will follow the named command approach so it’s important to get your head around the idea.

Interfaces of an add-in and they’re methods

All add-ins are required to implement one interface called IDTExtensibility2. This interface defines five methods that are used to set up and tear down the add-in from within the IDE. The methods are:

  • OnConnection
  • OnStartupComplete
  • OnBeginShutdown
  • OnAddInsUpdate
  • OnDisconnection

OnConnection is the most important method as it is where you add your own named commands to Visual Studio and where you map them to command bars. The other methods are generally self explanatory but for more information on them see the MSDN documentation.

Another interface that needs to be implemented if the add-in supports named commands is the IDTCommandTarget interface. This defines two methods, which are:

  • QueryStatus
  • Exec

QueryStatus is used by Visual Studio to determine and set the properties of the command based on the Visual Studio environment’s current state. It’s were you place code to disable the commands button and so forth.

Exec is called whenever a command is executed. This method is where you place the code that calls the relevant command for the action performed by the user in Visual Studio.

The QueryStatus and Exec methods for a single add-in are called for each command you might have in your add-in. So although a single add-in may have more than one command it doesn’t have more than one QueryStatus and Exec method. In these methods you need to place a switch/select statement that determines what action was performed and from that decides what command needs to run. You will see this at work in the example add-in.

OnConnection method in more detail

Three things should happen in the OnConnection method; these are and should happen in the order listed.

  1. Object references to Visual Studio IDE should be set up.
  2. Custom command(s) are added to Visual Studio command list.
  3. The custom command(s) are added to a command bar(s).

1. Object references to Visual Studio IDE should be set up

There are two important objects that every add-in should have.

  • DTE2
  • Addin

DTE2 is an object that you use to reference the Visual Studio IDE. This object lets you get access to the command bars, the tool windows, the code editors, and other components of the Visual Studio IDE.

Addin is an object that lets you reference the instance of your add-in. It contains information about your add-in that can be used during runtime.

The references to these objects must be set up in the OnConnection method. The first couple of lines of your add-ins OnConnection method should look like this:

//instance of the visual studio enviroment
private DTE2 m_applicationObject;
//instance of the addin 
private AddIn m_addInInstance;

public void OnConnection(object application, ext_ConnectMode connectMode, 
  object addInInst, ref Array custom)
{
    m_applicationObject = (DTE2)application;
    m_addInInstance = (AddIn)addInInst;

2. Custom commands are added to Visual Studio’s command list

Adding commands to Visual Studio didn’t quite work the way I had first expected, after thinking about what was actually going on it’s a very powerful approach to dynamically creating controls and displaying them at runtime. If I were adding a button to a command bar in Excel my first thoughts would be to create an instance of a control, set some of its properties, and add it to a control collection of the command bar.

The approach the Visual Studio developers took works differently. Instead of coding directly against the command bar, you create a command object, set it’s display properties, and then let the command object add itself to the command bar. The command is responsible for adding itself to the user interface, rather than the user interface knowing how to add the command.

In the Excel example it’s like creating an instance of a control, setting some properties of it, and then passing the command bar to the control so it can add itself to the control collection of the command bar.

This is an excellent and powerful example of using the Command design pattern in such a way that it can dynamically create the user interface controls used to run the command.

To create a command you use the DTE2.Commands.AddNamedCommand method. DTE2 is the reference to the Visual Studio 2005 environment, and is set up on the first calls to OnConnection().

//named command to perform the copy as html
private Command m_copyAsHtmlCmd;
object[] contextGUIDS = new object[] { };
                 
m_copyAsHtmlCmd = 
    m_applicationObject.Commands.AddNamedCommand(m_addInInstance, 
    m_NAME_COMMAND, "Copy As Html", "Copies the
        selected text formatted as HTML", 
    true, 52, ref contextGUIDS,
    (int)vsCommandStatus.vsCommandStatusSupported+(int)
  vsCommandStatus.vsCommandStatusEnabled);

The AddCreateNamed takes the add-in instance, the command name, the caption of the command, a tooltip, a flag indicating if a button is used to fire the command, and an icon for the button. One important thing to note is the command doesn’t contain any references to the code that’s run when the command is used. The command is not mapped to its code until the command is fired through the Exec method, which we’ll look at briefly.

One point worth noting is the icons used by commands are similar to the FaceID icons used by Office Addins. To be able to select a in build icon for you add-ins command I recommend you using one of the many Excel addins available that let you browse these in build icons and see their relevant index.

3. The custom command(s) are added to command bar(s)

The last thing to do during OnConnection is to add the command to a command bar. This is accompllished by, getting a reference to a command bar using the DTE2.CommandBars collection.

In this example a reference to the ‘Code Window’ command bar is used; this is the context menu that’s displayed on a right click in a code editor. There are a few different command bars available and they generally go by their name, for example to add to the Tools menu use the ‘Tools’ command bar.

//get a reference to the context menu displayed
            in the code window on a right click
//menu items and tool bar items are all of type command bar
CommandBars cmdBars = (_CommandBars)m_applicationObject.CommandBars;
CommandBar cmdBar = cmdBars["Code Window"];

//adds a control for the command onto the command bar of the right
    click context menu
if (cmdBar != null)
{
  //read as add control for copy as html command to the context menu
  m_copyAsHtmlCmd.AddControl(cmdBar, 1);
}

Remember the command adds itself to command bar so once you get a reference to the command bar pass it to the commands AddControl() method.

The QueryStatus method in more detail

The QueryStatus method is used to change the visual properties of your command, also known as its status, based on the IDE state or any other conditions you need. More likely than not you use this to disable or enable the command. Again a select case/switch statement is used to determine which command is being processed.

public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, 
ref vsCommandStatus status, ref object commandText)
{
    switch (commandName)
    {
        //case sensitive, the command must be named exactly         
	//Assembly.Class.Command
        case "CopyAsHtmlAddin.Connect.copyAsHtmlCommand":
           //todo place logic in here that determines if the command button is
               enabled or disabled
           //use logic here to set the status of the command
           status = vsCommandStatus.vsCommandStatusSupported |
                    vsCommandStatus.vsCommandStatusEnabled;
           break;
     }
}

The small example above demonstrates how to code within the QueryStatus method. As you can see setting the status value to a vsCommandStatus enumeration will in effect change the visual properties of the command.

The Exec method in more detail

The Exec method is where the code of your add-in goes. It’s important to remember that this method only gets called once even if your add-in supports multiple commands. To determine which code runs for each of the command you need a switch/select statement based on the running commands name. The command name is passed to the Exec method.

public void Exec(string commandName, vsCommandExecOption executeOption, 
	ref object varIn, ref object varOut, ref bool handled)
{
    handled = false;
    //not sure what this is
    if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
        if (commandName.ToLower() == "copyashtmladdin.connect.copyashtmlcommand")
        {
            //perform the action in here
            handled = true;
            return;
         }
     }
   }

The Exec method must also set a Boolean variable called handled to indicate if the command was processed or not.

Deploying and Add-in

Add-ins has two files which need to be installed. One is the assembly containing the add-ins code while the other is an XML manifest describing the add-in. Add-ins used to only be installed by registering them with COM; the XML manifest replaces that requirement.

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
  <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>CopyAsHtmlAddin</FriendlyName>
    <Description>Copies source code/xml to the clipboard 
		as formatted HTML.</Description>
    <Assembly>CopyAsHtmlAddin.dll</Assembly>
    <FullClassName>CopyAsHtmlAddin.Connect</FullClassName>
    <LoadBehavior>0</LoadBehavior>
    <CommandPreload>1</CommandPreload>
    <CommandLineSafe>0</CommandLineSafe>
  </Addin>
</Extensibility>

This XML describes the addin to Visual Studio and it must be saved with an .Addin extension. You don't generally need to create this file manually. If you select the Addin project type when initially creating your Addin, Visual Studio will, create it for you.

These two files should be copied to the ‘MyDocuments\Visual Studio 2005\Addins’ directory. Once there they become available through the add-in manager in Visual Studio 2005. Installing this way means the add-in is user specific and not shared between all users of Visual Studio 2005.

Summary

To create a Visual Studio addin it’s worthwhile thinking in terms of commands. Implementing two interfaces IDTExtensibility2 and IDTCommandTarget provides the class the methods it needs to act as an Addin.

In the OnConnection method you set up your commands and map them to UI elements in Visual Studio’s IDE. In the QueryStatus method you add logic to enable or disable your command and in the Exec method you add the commands code using a select/switch statement to handle multiple commands.

To install the Addin requires two files, the assembly containing the addin, plus an XML manifest with the .Addin extension. The XML manifest describes the addin to Visual Studio and removes the need to register the Addin with COM.

Download Source Code

CopyAsHtmlAddin

References

Developing Visual Studio .NET Macros and Addins
Jeff Cogswell

Working with Visual Studio 2005
Craig Skibo, Marc Young, Brian Johnston (MSPress)

Frequently Asked Questions About Visual Studio .NET Automation
Kemp Brown

Custom Add-Ins Help You Maximize the Productivity of Visual Studio .NET
Leo A. Notenbooma

Visual Studio Add-In That Converts C# Code To Visual Basic
Scott Swigart

Creating add-ins and Wizards
MSDN

About Derek Smyth

Sorry, no bio is available

View complete profile

Top Articles in this category

Introduction to GhostDoc
GhostDoc is a free add-in for Visual Studio that helps developers writing XML documentation comments.

Book Review: Professional Visual Studio Extensibility
Review of the book: Professional Visual Studio Extensibility.

Top
 
 
 

Please login to rate or to leave a comment.

Product Spotlight