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

Extending Visual Studio Part 2 - Creating Addins

, 17 Jun 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Create an amazingly useful 'Switch' addin to switch between cpp/h, designer/code, XAML/codebehind and more!

SwitchSetup

Extending Visual Studio

This article is part of the series 'Extending Visual Studio'.

Introduction

This is the second in my series of articles on extending Visual Studio. In this article we're going to look into a way to extend Visual Studio that has been around for a while - addins.

We'll create a very funky addin called 'Switch'. This addin will allow us to switch between related files - C++ header and source, WinForms code and designer, XAML and code-behind and so on.

In this article we'll start with one of the simplest ways to extend Visual Studio - creating Code Snippets.

Keep up-to-date

The addin created in this article, Switch, has become quite popular - you can keep up to date with the code and the project on GitHub, at github.com/dwmkerr/switch.

What is an Addin?

An add-in is a DLL that Visual Studio loads into memory to perform a task or present functionality to the user. What is most useful about an add-in, in terms of development, is that we have access to the DTE2 object. DTE2 is the top-level object in the Visual Studio Automation Model. This is a set of interfaces and objects that you can use to interact with Visual Studio.

So what can we do with DTE2? Here are some examples:

  • Access and invoke the commands visual studio offers.
  • Perform builds
  • Go through the items in a solution
  • Add controls to the user interface
  • ...and much more...

Essentially with the Visual Studio Automation model, if Studio can do it, so can you.

The Brief

So let's look at the brief for this add-in project. What do we want to do? First we can outline our requirements.

  1. Switch should add a command named 'Switch' to Visual Studio 2008 or Visual Studio 2010 that we can invoke with the mouse or keyboard.
  2. Switch should switch between a C or CPP source file and it's header, and vice versa.
  3. Switch should switch between the Code View and Design View of a C# form.
  4. Switch should switch between the XAML and Code Behind of a WPF/WP7/Silverlight XAML file.
  5. Switch should have an installer to make deployment straightforward. 

We can get all of the functionality required for the first four items from a Visual Studio add-in - and we can use a standard Deployment project for the fifth item. So without further delay, let's get started. As an aside, all of the screenshots and code in this article will be based on Visual Studio 2010. The source code actually has the same add-in for 2008 as well - however the code is essentially the same (main classes are added as links in both projects, so the functional code is in fact exactly the same).

Creating the Snippet

Kick of Visual Studio 2010 and choose File > New > Project... 

We'll create a new Addin project, which is in the 'Extensibility' group of 'Other Project Types'.

324611/Screenshot_NewProject.jpg

Now we must specify our project settings. The ones we need to be careful about are - ensure you're using C# or VB, make sure that we choose Visual Studio as the host, but not Visual Studio Macros and make sure you choose 'Yes, create a tools menu item'.

Now that we have created the project, we have a good starting point for our addin. The most key file that we have created is 'Connect.cs' - this is what is actually doing the work when the addin is loaded.

The Connect Class

The Connect class is the class that handles the integration of the addin into Visual Studio. So let's take a look at it in a bit more detail.

324611/ClassDiagram_Connect.jpg

Connect

The constructor, here we can perform any initialization we may need to do. However, in general try and defer anything complex to one of the later functions.

Exec

This function is called to actually execute the addin command. An addin may actually add many commands to Visual Studio, this function will be used to make sure we perform the correct behaviour.

OnAddinsUpdate

When the set of Visual Studio Addins is changed, this function will be called.

OnBeginShutdown

This function is called when Visual Studio is about to be closed.

OnConnection

This function is called when the addin is being loaded. It is in this function that we add the 'Switch' command to the Tools menu.

OnDisconnection

This function is called when the addin is being unloaded.

OnStartupComplete

When the host application (Visual Studio) is fully loaded, this function is called.

QueryStatus

Visual Studio will call this function to see if the addin should be shown, whether it should be enabled or disabled and so on.

So all together, the Connect class is not very complicated - it has to do some work to add the Switch command to the appropriate menu, but asides from that it's very basic. It has as member variables the addin instance, and _applicationObject - this is the DTE2 class that allows us to interface with Visual Studio.

To keep things clean, we will create a new class called 'Switcher' which will perform all of the switching functionality. This class will be a singleton, so all we need to do in preparation is modify the Exec function, as below:

public void Exec(string commandName, vsCommandExecOption executeOption, 
            ref object varIn, ref object varOut, ref bool handled)
{
    handled = false;
    if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
        if(commandName == "Switch.Connect.Switch")
        {
                        //  Use the Switcher object to perform the switch functionality
                        //  on the application object - switching the Active Document.
                        Switch2010.Switcher.Instance.Switch(_applicationObject,
                            _applicationObject.ActiveDocument);
                    
                        //  We've handled the command.
            handled = true;
            return;
        }
    }
}

This has put us into a good place. All we need to do now is create a singleton that switches the active document and we've got the core functionality we need.

The Switcher Class

Let's now create a class that'll actually take a document and attempt to switch it to a related document. We'll call this class Switcher. I've implemented it as a singleton, it could just be a class with static methods, it's really down to personal preferences. The first thing we can do is make it 'singleton-ish' by having a private static instance, a static property to get the instance, and a private constructor to do anything that it's nice to delay:

/// <summary>
/// The Switcher switches between related files.
/// </summary>
public class Switcher
{
    /// <summary>
    /// Prevents a default instance of the
    /// <see cref="Switcher"/> class from being created.
    /// </summary>
    private Switcher()
    {
        //  Perform any initialisation...
    }

    /// <summary>
    /// The singleton instance.
    /// </summary>
    private static Switcher instance = null;

    /// <summary>
    /// Gets the singleton instance.
    /// </summary>
    public static Switcher Instance
    {
        get
        {
            //  If the instance doesn't already exist, create it.
            if (instance == null)
                instance = new Switcher();

            //  Return the instance.
            return instance;
        }
    }

This is the basic layout for a singleton. This is not an article on the singleton pattern, I'm just using it so that from here on I don't have to bloat the code in the article with static modifiers, which should make it clearer. Look at Martin Lapierre's excellent article on generic singletons in C# for a really good article on singletons - Generic Singleton Pattern using Reflection in C#.

The first thing the switcher will do is try and work out if the active document has a designer - and if it does try and toggle between the code and design view. Let's put some functions together to do this. The first will try and determine whether the file has a designer.

/// <summary>
/// Determines whether a document has a designer.
/// </summary>
/// <param name="path">The path of the document to check.</param>
/// <returns>True if the document has a designer.</returns>
private bool DoesDocumentHaveDesigner(string path)
{
    //  Create the designer path.
    string designerPath = Path.Combine(Path.GetDirectoryName(path),
        Path.GetFileNameWithoutExtension(path)) + ".designer.cs";

    //  Is there a designer document?
    return File.Exists(designerPath);
}

This is a slightly clunky way of checking whether a solution item has a designer - is there a file of the same name that ends in 'designer.cs'. Determining whether a document can have a designer is very hard in Studio (but not as hard as determining whether a window is a designer) so this will do the trick with the minimum amount of effort. Now we need a function to try and toggle between the code and design view.

/// <summary>
/// Tries to the toggle between the code and design view.
/// </summary>
/// <param name="activeDocument">The active document.</param>
private void TryToggleCodeDesignView(Document activeDocument)
{
    //  If we're showing the designer, show the code view.
    if (activeDocument.ActiveWindow.Caption.Contains("[Design]"))
        activeDocument.ProjectItem.Open(vsViewKindCode).Activate();
    else
        activeDocument.ProjectItem.Open(vsViewKindDesigner).Activate();
}

This is the real fudge. There is no programmatic way (that I have found) to determine whether the active document is open in the designer or not. However, designer windows have the text '[Design]' at the end of the title. We can check to see if we are in a designer window, and then re-open the project item in the code view (or vice versa). This is the first thing we must note for testing and further development - in other languages this trick may not work!

These functions will let us switch between code and designer, but what about switching between cpp/h or xaml/xaml.cs files? To do this, we'll create a class called SwitchTarget. A SwitchTarget will just represent what we go From, to what we go To. If the document path ends in 'From', we'll try and find a document that ends in 'To' and open it. We can then create as many switch targets as we like - and even potentially chain them so that we can switch between three or more documents in order.

Here's the SwitchTarget class - it stores the From and To and can map a path that ends in From to one that ends in To:

/// <summary>
/// A Switch Target defines what we switch from, to.
/// </summary>
public class SwitchTarget
{
    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchTarget"/> class.
    /// </summary>
    public SwitchTarget()
    {

    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SwitchTarget"/> class.
    /// </summary>
    /// <param name="from">From.</param>
    /// <param name="to">To.</param>
    public SwitchTarget(string from, string to)
    {
        From = from;
        To = to;
    }

    /// <summary>
    /// Maps the from path to the to path.
    /// </summary>
    /// <param name="path">The path.</param>
    /// <returns>The mapped path</returns>
    public string MapPath(string path)
    {
        //  Replace the path 'from' with the part 'to'.
        if (path.Length < From.Length)
            return null;

        return path.Substring(0, path.Length - From.Length) + To;
    }

    /// <summary>
    /// Gets from.
    /// </summary>
    public string From
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets to.
    /// </summary>
    public string To
    {
        get;
        private set;
    }
} 

Now we can add a list of SwitchTargets to the Switcher class and set them up in the constructor. Change the constructor of Switcher to the code below, and add the collection:

/// <summary>
/// The switch targets.
/// </summary>
private List<SwitchTarget> switchTargets = new List<SwitchTarget>();

/// <summary>
/// Prevents a default instance of the
/// <see cref="Switcher"/> class from being created.
/// </summary>
private Switcher()
{
    //  Create the switch targets.
    switchTargets.Add(new SwitchTarget("c", "h"));
    switchTargets.Add(new SwitchTarget("cpp", "h"));
    switchTargets.Add(new SwitchTarget("h", "c"));
    switchTargets.Add(new SwitchTarget("h", "cpp"));
    switchTargets.Add(new SwitchTarget("xaml", "xaml.cs"));
    switchTargets.Add(new SwitchTarget("xaml.cs", "xaml"));
}

Now we have a way to define what we switch from, to what we switch to. With this, we can now build the final Switch function:

/// <summary>
/// Switches the specified active document.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="activeDocument">The active document.</param>
public void Switch(DTE2 application, Document activeDocument)
{
    //  Does the active document have a designer?
    if (DoesDocumentHaveDesigner(activeDocument.FullName))
    {
        //  It does, so just switch between the designer and code view.
        TryToggleCodeDesignView(activeDocument);
    }
    
    //  Go through each switch target - if we have
    //  one for this file, we can attempt to switch.
    List<SwitchTarget> targets = new List<SwitchTarget>();
    foreach (var target in switchTargets)
        if (activeDocument.FullName.EndsWith(target.From))
            targets.Add(target);

    //  Go through each potential target, try and open
    //  the document. If it opens, we're done.
    foreach (var target in targets)
        if(TryOpenDocument(application, target.MapPath(activeDocument.FullName)))
            break;
}

And that's it! The project includes a StringExtensions.cs file that includes an extension method named 'EndsWith', in case you are wondering where this function comes from! The only function that we haven't got is 'TryOpenDocument' - so here it is. Remember that there's no guarantee the switch target will exist, so we can only try to open it - we can't guarantee.

/// <summary>
/// Try to open the document with the specified path.
/// </summary>
/// <param name="application">The application.</param>
/// <param name="path">The path.</param>
/// <returns>True if the document was opened.</returns>
private bool TryOpenDocument(DTE2 application, string path)
{
    //  Go through each document in the solution.
    foreach(Document document in application.Documents)
    {
        if(string.Compare(document.FullName, path) == 0)
        {
            //  The document is open, we just need to activate it.
            if (document.Windows.Count > 0)
            {
                document.Activate();
                return true;
            }
        }
    }

    //  The document isn't open - does it exist?
    if (File.Exists(path))
    {
        try
        {
            application.Documents.Open(path, "Text", false);
            return true;
        }
        catch
        {
            //  We can't open the document, that's fine.
        }
    }

    //  We couldn't open the document.
    return false;
}

We've got the core functionality done - if you hit F5 you can run up Visual Studio and switch between related documents.

Building an Installer

An installer for an addin is very trivial - just make sure that the *.addin file doesn't have a path to the assembly, just the assembly name, then put the addin file and the assembly in the folder 'My Documents/Visual Studio 2010/Addins'. Here's a screenshot of how the MSI File System screen should look (the main project actually supports Visual Studio 2008 as well, that's why there's more folders!)

324611/Screenshot_FileSystem.jpg

Using a Custom Icon for the Command

If you've been following the code and building your own project, you'll notice that the command has a big smiley face for an icon. While this is very jolly, we may want to use our own icon. This is a rather tricky thing to do as it turns out, so I have left this to the end.

How is the Icon set?

Take a look at the OnConnection function of the Connect object - the key line is below:

//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance, 
                        "Switch",
                        "Switch",
                        "Switch between related files",
                        true, // use a visual studio icon
                        59, // the smiley icon. 
                        ref contextGUIDS,
                        (int)vsCommandStatus.vsCommandStatusSupported+
                        (int)vsCommandStatus.vsCommandStatusEnabled,
                        (int)vsCommandStyle.vsCommandStylePictAndText,
                        vsCommandControlType.vsCommandControlTypeButton);

The boolean parameter set to true tells the environment that the following value (59) is the index of the visual studio icon to use. There are thousands to choose from, this article will show you how to see them all: http://msdn.microsoft.com/en-us/library/aa159658(office.11).aspx.

However, what if we don't want to use an icon from this set but our own? Here's how we do that. First, add a new resources file to the solution, named it Resources.resx and delete the associated designer file. Your 16x16 icon bitmap to the solution. If you need to have a transparent part of the icon, use the color RGB(0, 254, 0). The screenshot below shows the switch icon with the transparent special colour:

324611/Screenshot_PaintingIcon.jpg

Now add the icon to the resources file, and ensure it has the name '1'. The resources code should have an entry like this:

<data name="1" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>Resources\Switch.bmp;System.Drawing.Bitmap, System.Drawing, 
      Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> 

Make sure the build action is set to 'no action' - we'll build the satellite assembly ourselves. The resources must be built in a satellite assembly or Visual Studio won't pick up on them. We can build the satellite with the commands below: 

"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\resgen.exe" Resources.resx
"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\al.exe" /t:lib 
   /embed:Resources.resources /culture:en-US /out:Switch.Resources.dll 

Now when we deploy the Switch assembly - we must make sure the Switch.resources.dll is in a subfolder named 'en-US' or Visual Studio won't pick up on it! There are batch files in the download to perform these commands.

The final thing to do is add the .vscontent and .snippet file into a new *.zip file and rename it from zip to vsi. This creates a VSI installer - double click on it and you get the below:

Final Thoughts 

Now that we have a Switch add-in, we can add it anywhere to the Visual Studio UI and bind a keyboard command to it. In my setup I have Switch super-accessible at the right of the main menu and bind it to Ctrl+Alt+S. Now whether I'm in C++ code, XAML or a WinForms project I can flick between related files. 

There are loads of potential improvements to the project - making switching configurable, fixing the fudges that won't work in other languages and so on - but a basic project like this is a great way to get started with Visual Studio Extensions.

I hope you have enjoyed this article and found it useful, keep up to date with my writing by following my blog at www.dwmkerr.com - as always any suggestions are welcome.

Update History  

  • 11/06/2013 - Updated the article with the 2.0 version of the Setup program, which supports new features.
  • 26/08/2012 - Updated the Switch Setup download with support for Visual Studio 2012.

License

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

Share

About the Author

Dave Kerr
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.
Follow on   Twitter

Comments and Discussions

 
QuestionTrouble with installer PinmemberMember 78102301-Nov-13 11:04 
QuestionPart 1 PinmemberKevin Marois18-Jun-13 7:28 
AnswerRe: Part 1 PinmvpDave Kerr18-Jun-13 9:12 
GeneralRe: Part 1 PinmemberKevin Marois18-Jun-13 12:07 
GeneralRe: Part 1 Pinmemberpaul_cheung18-Jun-13 22:44 
GeneralMy vote of 5 Pinmemberfredatcodeproject18-Jun-13 2:32 
GeneralRe: My vote of 5 PinmvpDave Kerr18-Jun-13 2:39 
Sounds like a good idea - just need to find the time! Wink | ;)

GeneralRe: My vote of 5 Pinmemberfredatcodeproject18-Jun-13 3:22 
GeneralMy vote of 5 Pinmemberwsc091813-Jun-13 16:59 
GeneralRe: My vote of 5 PinmvpDave Kerr13-Jun-13 21:31 
GeneralRe: My vote of 5 Pinmemberwsc091813-Jun-13 22:18 
GeneralRe: My vote of 5 PinmvpDave Kerr13-Jun-13 22:49 
GeneralRe: My vote of 5 Pinmemberwsc091813-Jun-13 23:00 
GeneralRe: My vote of 5 PinmvpDave Kerr16-Jun-13 4:58 
GeneralRe: My vote of 5 Pinmemberwsc091817-Jun-13 21:28 
QuestionVS2012 - Setup code Pinmemberraghs_raghav26-Nov-12 1:30 
AnswerRe: VS2012 - Setup code PinmvpDave Kerr26-Nov-12 1:34 
GeneralCool PinmemberShemeer NS24-Sep-12 12:00 
GeneralRe: Cool PinmvpDave Kerr24-Sep-12 21:50 
GeneralMy vote of 5 PinmemberJohn B Oliver24-Sep-12 0:41 
GeneralRe: My vote of 5 PinmvpDave Kerr24-Sep-12 0:47 
GeneralRe: My vote of 5 PinmvpDave Kerr24-Sep-12 0:48 
GeneralMy vote of 4 PinmemberLionhunter xplus1-Sep-12 23:47 
GeneralRe: My vote of 4 PinmvpDave Kerr24-Sep-12 0:46 
GeneralMy vote of 4 Pinmemberpradiprenushe27-Aug-12 3:36 
GeneralRe: My vote of 4 PinmvpDave Kerr24-Sep-12 0:46 
QuestionSmashing! PinmemberEdo Tzumer26-Aug-12 22:39 
AnswerRe: Smashing! PinmvpDave Kerr26-Aug-12 22:46 
QuestionVisual Studio 2012 (VS2012) support ? PinmemberRolf Kristensen23-Aug-12 4:38 
AnswerRe: Visual Studio 2012 (VS2012) support ? PinmvpDave Kerr23-Aug-12 4:49 
AnswerRe: Visual Studio 2012 (VS2012) support ? PinmvpDave Kerr26-Aug-12 5:47 
GeneralRe: Visual Studio 2012 (VS2012) support ? PinmemberRolf Kristensen26-Aug-12 23:04 
GeneralRe: Visual Studio 2012 (VS2012) support ? PinmvpDave Kerr27-Aug-12 0:27 
GeneralMy vote of 5 PinmemberMihai MOGA12-May-12 19:30 
GeneralRe: My vote of 5 PinmvpDave Kerr10-Jul-12 1:46 
GeneralGreat!!! Pinmemberraananv18-Apr-12 12:49 
GeneralRe: Great!!! PinmvpDave Kerr18-Apr-12 23:45 
GeneralMy vote of 5 PinmemberAndreas Gieriet15-Apr-12 4:01 
GeneralRe: My vote of 5 PinmvpDave Kerr23-Aug-12 4:50 
GeneralMy vote of 5 PinmemberKamyar13-Feb-12 9:26 
GeneralRe: My vote of 5 PinmvpDave Kerr14-Feb-12 0:41 
GeneralMy vote of 5 Pinmemberjawed.ace7-Feb-12 2:48 
GeneralRe: My vote of 5 PinmvpDave Kerr7-Feb-12 6:02 
QuestionNice PinmvpSacha Barber6-Feb-12 2:17 
AnswerRe: Nice PinmvpDave Kerr6-Feb-12 4:00 
GeneralMy vote of 5 PinmemberBrian Pendleton6-Feb-12 1:27 
GeneralRe: My vote of 5 PinmvpDave Kerr6-Feb-12 4:01 
GeneralRe: My vote of 5 PinmemberBrian Pendleton6-Feb-12 4:22 
GeneralRe: My vote of 5 PinmvpDave Kerr6-Feb-12 5:48 
GeneralRe: My vote of 5 PinmemberAnna-Jayne Metcalfe13-Feb-12 12:23 

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 | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 18 Jun 2013
Article Copyright 2012 by Dave Kerr
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid