Export (0) Print
Expand All

Frequently Asked Questions About Visual Studio .NET Automation

Visual Studio .NET 2003
 

Kemp Brown
Visual Studio .NET Team
Microsoft Corporation

June 2003

Summary: Since the release of Visual Studio .NET, users have been creating add-ins and using the automation model to create productivity tools and simplify their programming tasks. But as with any evolving technology, time and use have revealed some sticking points and areas that call for clarification and additional information. This article addresses some of the most frequently asked questions about Visual Studio .NET automation and extensibility, which are listed in the following Contents section. (26 printed pages)

Contents

How do I change the default icon used by add-in toolbar commands and buttons?

How do I reference and handle programming language-specific project objects, collections, and events?

How do I create custom tool windows?

How Can I Add a Command for an Add-in to the Shortcut (Context/Right-Click) Menu, or the Submenu of Another Command?

Miscellaneous tips and issues

Conclusion

Version Information

The following article is targeted specifically to Visual Studio .NET 2003. Many of the techniques will work with earlier versions, however, if you slightly change some component directories.

For general information about automation and add-ins, see the overview white paper "Automation and Extensibility Overview" (search the MSDN library index for "automation [Visual Studio .NET], about automation") or visit the automation and extensibility portal page "Creating Add-ins and Wizards" (search the MSDN library index for "automation [Visual Studio .NET], automation model"). You can also find a number of very useful automation samples on http://msdn.microsoft.com/vstudio/downloads/samples/automation.aspx.

How Do I Change the Default Icon Used By Add-in Toolbar Commands and Buttons?

When you create an add-in using the Visual Studio .NET Add-in project type (which you can access by clicking Other Projects, then Extensibility Projects in the New Project dialog box), you have the option of creating a UI for the add-in by checking the "Would you like to create a UI for the user to interact with your Add-in" box. When you do this, the template creates a Tools menu item that causes your add-in to load when a user clicks it. The menu item uses a default smiley-face icon (☺) next to the command, which you might want to change by using one of two methods. You can simply change the default icon index number (59) to the number of another standard icon from the Microsoft Office Object Library (MSO.DLL), which contains nearly 3,000 icons and is included in Visual Studio .NET. Your other option is to define a custom bitmap, such as a company logo or custom icon, in a satellite DLL and then change your code to point to that new bitmap. The former method is quicker and easier, but you have fewer icons to choose from. The latter method involves a bit more work, but you get to use the exact icon you want. Both methods are outlined below.

To change the default icon to another standard icon

  1. Load the solution created by the Add-in Wizard.
  2. In the OnConnection event, look for the following line:
    ' Visual Basic .NET
    CommandObj = applicationObject.Commands.AddNamedCommand(objAddIn, _
    "MyAddin", "MyAddin", "Executes the command for MyAddin", True, 59, _
    Nothing, 1 + 2)
    
    // Visual C# .NET
    Command command = commands.AddNamedCommand(addInInstance, "MyAddin1", 
    "MyAddin1", "Executes the command for MyAddin1", true, 59, ref contextGUIDS, 
    (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled);
    
  3. Notice the number 59 in the AddNamedCommand line. This is the index to the default icon (a smiley-face). Change this to the number of the desired icon. For example, if you wanted to change it to a red star, you would change 59 to 6743.
  4. Reset the command bar information by typing devenv /setup at the Visual Studio .NET Command Prompt. This ensures that your new icon will display on the toolbar.
    Caution   Doing this will delete all customizations you have made to Visual Studio .NET command bars. The command bars are rebuilt according to the loaded add-ins and packages.

When you run your add-in, the command appears on the Tools menu next to a red star icon.

To obtain a complete list of the MSO icons, see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dno97ta/html/faceid.asp. This article in the MSDN library contains a small application that lets you view all of the icons and obtain their ID numbers so you can choose the icon you want.

If you cannot find an appropriate icon in the MSO library, you must use a custom bitmap for the add-in's command icon. The following procedure demonstrates how to do this. The basic procedure is to create a Class Library that contains a resource file that in turn contains the custom icon you want to use. You then point AddNamedCommand to the custom icon. (Note that the following procedure assumes that your project creates the command on the Tools menu.) The procedure is similar for both Visual Basic .NET and Visual C# .NET.

Note   The MSDN Automation Samples site has a downloadable example project demonstrating how to do this in Visual Basic .NET and Visual C# .NET. Go to http://msdn.microsoft.com/vstudio/downloads/samples/automation.aspx and click the "Custom Bitmap Add-in" link.

To change the default icon to a custom bitmap

  1. Load your add-in solution.
  2. Add a new Class Library project to the solution. For purposes of this example, call it ClassLibrary1.

    This will house the resource file that contains your custom icon. (Note: Do not confuse this with a class module. A Class Library is a type of project.)

  3. Add the desired image file to Class Library project. To do this, select the Class Library node in Solution Explorer, and then on the Project menu, click Add Existing Item and browse to the image file.
  4. Build the Class Library project by clicking Build ClassLibrary1 on the Build menu.
  5. Open the resultant Class Library .dll file in Visual Studio .NET.
  6. Add a Bitmap resource to this DLL. To do this, click the Icon node and then click Add Resource on the Edit menu.
  7. Select Bitmap in the list of resource types, click the Import button, and then save the DLL. Note the ID number assigned to the image in the DLL — you will need it later in step 11. If there are no other bitmaps in the DLL, the value is usually "101."
  8. Click the setup project and select Registry on the Editor submenu on the View menu. Locate the HKEY_CURRENT_USER/Software/Microsoft/VisualStudio/7.1/AddIns/<add-in name> node and add two new keys with the following string values (without quotes):
    SatelliteDllPath  "[TARGETDIR]"
    SatelliteDllName  "classlibrary.dll"
    
  9. Click the setup project and select File System on the Editor submenu on the View menu to display the project's file system. In File System, click the Application Folder node (in the left pane) and select Folder on the Add submenu on the Action menu. Name the new folder "1033." (This is the English locale ID — use the appropriate locale ID for your language.)
  10. Click the new folder, select Assembly on the Add submenu on the Action menu, and then add the Class Library DLL you created in step 4. You will need to click the Browse button in the Component Selector dialog box to navigate to the DLL.
  11. If you are using Visual Basic .NET, modify the connect.vb module (connect.cs for Visual C# .NET) to point to the new bitmap. In the AddNamedCommand statement in the OnConnection method, change the fourth argument (MSOButton) default value from True to False and set the bitmap ID parameter to the numeric ID value created in step 7.
  12. Build the addin and addinSetup projects.
  13. Install the add-in by running its setup.
  14. At the Visual Studio .NET Command Prompt, type devenv /setup.

    This forces Visual Studio .NET to reinitialize the toolbar buttons, allowing it to recognize your new custom bitmap.

  15. Start a new instance of Visual Studio .NET.

    You now see your new custom icon next to the add-in's command on the Tools menu.

How Do I Reference and Handle Programming Language-Specific Project Objects, Collections, and Events?

There are two answers to the question of referencing and handling project objects, collections, and events. The first answer deals with how to reference project-specific objects and collections, while the second answer deals with how to respond to project-specific events.

Referencing Language-Specific Project Objects and Collections

Visual Studio .NET provides a default, generic project object model — represented in the EnvDTE assembly primarily by the Project object and Projects collection — that allows you to programmatically query and control projects and project items in the IDE. This works well enough for most applications, but in some cases, you might need a finer degree of control, or to be able to access language-specific properties and methods. To give you such access, Visual Studio .NET supplies additional assemblies that support this. The VSLangProj assembly (namespace) also provides the VSProject object and VSProjectItems collection which give you access to the Visual Basic .NET and Visual C# .NET project structure, as well as additional related objects, collections, and constants. The Visual C++ .NET-specific VCProject and VCProjectItem objects and other supporting items are located in an assembly named VCProjectEngine. (Its full namespace is Microsoft.VisualStudio.VBProjectEngine.)

Because the language-specific project objects are late-bound, they do not automatically appear in the IntelliSense menus while you are programming against them. So if you want IntelliSense to fill in values as you type your code, you should reference the appropriate assembly before coding. The following is a list of the language-specific project objects and collections:

  • Basic
  • CSharpProjects
  • Deployment
  • DataService
  • Debugger
  • Help
  • TextEditor
  • VBProjects
  • VCLanguageManager
  • VCProjects

You can also view them using the Extensibility Browser. The Extensibility Browser (ExtBrws.dll) can be downloaded at http://msdn.microsoft.com/vstudio/downloads/samples/automation.aspx under "Unsupported Tools." After activating it in the Add-in Manager, it creates a command named "Extensibility Browser" on the Tools menu. When clicked, the Extensibility Browser displays all of the late-bound objects available under DTE and DTE.Properties, as well as the late-bound events under DTE.Events.

The following is a short macro example of how to use the late-bound VBProjects property — which returns a Projects object containing all of the Visual Basic projects in the solution — to list all of the Visual Basic .NET projects in the solution.

Sub ListVBProjs()
   ' VSMacro
   ' Lists all VB projects in the solution.
   Dim vbPrjs As Projects
   vbPrjs = CType(DTE.GetObject("VBProjects"), Projects)
   Dim Prj As Project
   For Each Prj In vbPrjs
      MsgBox(Prj.FullName)
   Next
End Sub

As I mentioned earlier, you can also use the generic Project/Projects to view and manipulate projects in your solution. The following example walks through the current solution and lists the type of each project.

' Visual Basic .NET
Imports EnvDTE
Imports VSLangProj

Module Module1
    Sub ProjTypesExample()
        Dim soln As Solution
        Dim Prj As Project
        Dim Prjs As Projects
        Dim DTE As EnvDTE.DTE

        ' Get an instance of the currently running Visual Studio .NET IDE.
        DTE = System.Runtime.InteropServices.Marshal. _
         GetActiveObject("VisualStudio.DTE")
        ' Set references to the solution and projects.
        soln = DTE.Solution
        Prjs = soln.Projects
        ' Walk through all of the projects in the solution
        ' and list the type of each project.
        For Each Prj In Prjs
            ' The PrjKind constants are in VSLangProj.
            If (Prj.Kind = PrjKind.prjKindVBProject) Then
                MsgBox(Prj.Kind & vbCr & Prj.Name & " is a VB project.")
            ElseIf (Prj.Kind = PrjKind.prjKindCSharpProject) Then
                MsgBox(Prj.Kind & vbCr & Prj.Name & " is a C# project")
            Else
                MsgBox(Prj.Name & " Is a C++ or other project type")
            End If
        Next
    End Sub
End Module 
// Visual C# .NET
using System;
using EnvDTE;
using VSLangProj;
using System.Windows.Forms;

namespace WindowsApplication8
{
   public class Module1
   {
      public static void ProjTypesExample()
      {
         Solution soln;
         Projects Prjs;
         EnvDTE.DTE DTE;

         // Get an instance of the currently running Visual Studio .NET 
         //IDE.
         DTE = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.
         GetActiveObject("Visual Studio.DTE.7.1");         
         // Set references to the solution and projects.
         soln = DTE.Solution;
         Prjs = soln.Projects;
         // Walk through all of the projects in the solution
         // and list the type of each project.
         foreach (Project Prj in Prjs )
         {
            // The PrjKind constants are in VSLangProj.
            if (Prj.Kind==PrjKind.prjKindVBProject)
            {
               MessageBox.Show(Prj.Kind + Prj.Name+" is a VB project.");
            }
            else if((Prj.Kind==PrjKind.prjKindCSharpProject))
            {
               MessageBox.Show(Prj.Kind + Prj.Name+" is a C# project");
            }
            else
            {
               MessageBox.Show(Prj.Name + " Is a C++ or other project 
               type");
            }
         }
      }
   }
}

As you can see, you can perform generic iteration and query/manipulation operations using Project/Projects, but you cannot access project-specific properties such as, for example, the Visual Basic .NET properties AssemblyName, TemplatePath, Imports, and so forth. Using VSProject and VSProjectItem, though — or VCProjectEngine in the case of Visual C++ .NET — you can access these and other project-specific properties and methods. The following is an example of how to reference Visual Basic .NET-specific project properties.

To handle language-specific project properties

  1. Create a new Visual Basic .NET or Visual C# .NET add-in.
  2. To enable IntelliSense, reference the VSLangProj assembly by right-clicking the References node in Solution Explorer and adding the VSLangProj assembly from the .NET tab. Also, for Visual C# .NET, add a reference to System.Windows.Forms to allow message boxes to be used.
  3. At the top of the Connect.vb module (or Connect.cs for Visual C# .NET), add the following:
    ' Visual Basic .NET
    Imports VSLangProj
    
    // Visual C# .NET
    using VSLangProj;
    using System.Windows.Forms;
    
  4. At the top of the class, add the following:
    ' Visual Basic .NET
    Dim soln As Solution
    Dim prj As Project
    Dim prjitem As ProjectItem
    Dim vsprj As VSLangProj.VSProject
    Dim vsprjitem As VSLangProj.VSProjectItem
    Dim i As Integer
    Dim msg As String
    
    // Visual C# .NET
    // Add the following to the bottom of the class.
    private Solution soln;
    private Project prj;
    private ProjectItem prjItem;
    private VSLangProj.VSProject vsPrj;
    private VSLangProj.VSProjectItem vsPrjItem;
    private int i;
    private string msg;
    
  5. To the OnConnection procedure, add the following:
    ' Visual Basic .NET
    ' Create a solution reference.
    soln = applicationObject.Solution
    
    ' Create a new VB Windows Application project based on 
    ' the VB Windows Application project wizard template. The TRUE
    ' flag means that the project automatically creates a solution 
    ' for itself when created.
    soln.AddFromTemplate("C:\Program Files\Microsoft Visual Studio _
    .NET 2003\Vb7\VBProjects\WindowsApplication.vsz", "c:\temp", "A New _
    Project", True)
    
    ' Set generic references to the project and its first item.
    prj = soln.Projects.Item(1)
    prjitem = prj.ProjectItems.Item(1)
    
    ' Convert those generic "Object" return types to 
    ' VSProject/VSProjectItem types.
    vsprj = CType(prj.Object, VSLangProj.VSProject)
    vsprjitem = CType(prjitem.Object, VSLangProj.VSProjectItem)
    
    ' Access the following VSProject and VSProjectItem-specific properties:
    '  - TemplatePath()
    '  - ContainingProject()
    '  - Imports()
     MsgBox("Template path for '" & prj.Name & "' is: " & _
      vsprj.TemplatePath.ToString)
     MsgBox("'" & prjitem.Name & "' is contained in the '" & _
      vsprjitem.ContainingProject.Name & "' project.")
    
     For i = 1 To vsprj.Imports.Count
        msg = msg & vsprj.Imports.Item(i) & vbCr
     Next
     MsgBox("IMPORTS: " & vbCr & msg)
    
    // Visual C# .NET
    // Creates a solution reference.
    soln = applicationObject.Solution;
    
    // Creates a new C# Windows Application project based on 
    // the C# Windows Application project wizard template. The TRUE
    // flag means that the project automatically creates a solution 
    // for itself when created.
    soln.AddFromTemplate("C:\\Program Files\\Microsoft Visual Studio .NET 
    2003\\VC#\\CSharpProjects\\CSharpEXE.vsz", "c:\\temp", "A New 
    Project", true);
             
    // Set generic references to the project and its first item.
    prj = soln.Projects.Item(1);
    prjItem = prj.ProjectItems.Item(1);
    
    // Convert those generic "Object" return types to 
    // VSProject/VSProjectItem types.
    vsPrj = (VSLangProj.VSProject) prj.Object;
    vsPrjItem = (VSLangProj.VSProjectItem) prjItem.Object;
    
    // Access the following VSProject and VSProjectItem-specific 
    // properties:
    //  - TemplatePath()
    //  - ContainingProject()
    //  - Imports()
    MessageBox.Show("Template path for '"+prj.Name+"' is: 
    "+vsPrj.TemplatePath.ToString());
    MessageBox.Show("'"+prjItem.Name+"' is contained in the 
    '"+vsPrjItem.ContainingProject.Name+"' project.");
    
    for(i=1; i<=vsPrj.Imports.Count; i++)
    {
       msg = msg+vsPrj.Imports.Item(i)+"/n";
    }
    MessageBox.Show("IMPORTS: /n"+msg);
    
  6. Run the add-in.
  7. After a new instance of Visual Studio .NET starts, run the add-in by checking its box in the Add-in Manager.

At this point, you see message boxes displaying the values of the various properties.

For more information about accessing project-specific functionality, see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbconintroductiontovisualbasicprojectextensibility.asp and http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vbcon/html/vbconintroductiontovsprojectobject.asp.

Handling Language-Specific Project Events

Once you have set up references to language-specific project objects, you will then most likely want to handle their events. Doing this is basically a two-step process: first declare the event handler and then add the event handler itself to your code.

As I previously mentioned, the language-specific event objects are late-bound, and thus, not visible in the IDE unless you use the Extensibility Browser. Here is a list of the late-bound event objects (for DTE.Events):

  • CodeModelEvents
  • CSharpBuildManagerEvents
  • CSharpReferencesEvents
  • CSharpProjectItemsEvents
  • CSharpProjectsEvents
  • DebuggerEvents
  • VBBuildManagerEvents
  • VBImportsEvents
  • VBProjectItemsEvents
  • VBProjectsEvents
  • VBReferencesEvents
  • VCProjectEngineEventsObject

The following procedure demonstrates how to handle the three Visual Basic .NET project item events: ItemAdded, ItemRemoved, and ItemRenamed. It is followed by code that demonstrates how to do the same thing in Visual C# .NET.

To handle Visual Basic .NET- and Visual C# .NET-specific project events

  1. Create a new add-in project.
  2. At the top of the Connect class module, add the following:
    ' Visual Basic .NET
    Public WithEvents VBProjectItemsEvents As EnvDTE.ProjectItemsEvents
    

    This maps the generic project item events to the Visual Basic-specific project item events.

    For VSMacros, also put the following in the EnvironmentEvents module:

    <System.ContextStaticAttribute()> Public WithEvents VBProjectItemsEvents As EnvDTE.ProjectItemsEvents
    
  3. To ensure that events will stop being handled after the add-in is disconnected, in the OnDisconnection procedure of the Connect class module, add the following:
    VBProjectItemsEvents = Nothing
    
  4. In the OnConnection procedure of the Connect class module, add the following:
    VBProjectItemsEvents = _
     CType(applicationObject.Events.GetObject("VBProjectItemsEvents"), _
     EnvDTE.ProjectItemsEvents)
    

    This reference (VBProjectItemsEvents) becomes the source object for Visual Basic .NET project item events. It retrieves the appropriate event object from the automation model.

    Note that VSMacros do not need to do this step because they automatically connect the method handler as a result of the object variable declaration using the "WithEvents" handler.

  5. In a class module, add the procedures for the item-related events you want to handle. There should be three event handlers, one for the ItemAdded event, another for the ItemRemoved event, and the final one for the ItemRenamed event.
    Private Sub VBProjectItemsEvents_ItemAdded(ByVal ProjectItem As _
    EnvDTE.ProjectItem) Handles VBProjectItemsEvents.ItemAdded
       MsgBox("Item added")
    End Sub
    
    Private Sub VBProjectItemsEvents_ItemRemoved(ByVal ProjectItem As _
    EnvDTE.ProjectItem) Handles VBProjectItemsEvents. ItemRemoved
       MsgBox("Item removed")
    End Sub
    
    Private Sub VBProjectItemsEvents_ItemRenamed(ByVal ProjectItem As _
    EnvDTE.ProjectItem, ByVal OldName As String) Handles _
    VBProjectItemsEvents.ItemRenamed
       MsgBox("Item renamed")
    End Sub
    

When the add-in is run and you create a Visual Basic .NET project and add items to it or remove items from it, a message box displays alerting you to the event.

The following is a similar Visual C# .NET add-in example demonstrating how to handle the Visual C# .NET-specific project item events, ItemAdded, ItemRemoved, and ItemRenamed:

// Visual C# .NET
namespace CSharpEventHandler
{
   using System;
   using Microsoft.Office.Core;
   using Extensibility;
   using System.Runtime.InteropServices;
   using EnvDTE;
   using System.Windows.Forms;

   [GuidAttribute("9BBE7601-DBC7-48F7-8AC3-EB415380D205"), 
   ProgId("CSharpEventHandler.Connect")]
   public class Connect : Object, Extensibility.IDTExtensibility2
   {
      public Connect()
      {
      }

      public void OnConnection(object application, 
      Extensibility.ext_ConnectMode connectMode, object addInInst, ref 
      System.Array custom)
      {
         applicationObject = (_DTE)application;
         addInInstance = (AddIn)addInInst;
         
         MessageBox.Show("Connecting add-in");
         try
         {
            CSharpProjectItemsEvents = (EnvDTE.ProjectItemsEvents) 
            applicationObject.Events.GetObject
            ("CSharpProjectItemsEvents");
            CSharpProjectItemsEvents.ItemAdded += new 
            _dispProjectItemsEvents_ItemAddedEventHandler
            (CSharpProjectItemAdded);
            CSharpProjectItemsEvents.ItemRemoved += new 
            _dispProjectItemsEvents_ItemRemovedEventHandler
            (CSharpProjectItemRemoved);
            CSharpProjectItemsEvents.ItemRenamed += new 
            _dispProjectItemsEvents_ItemRenamedEventHandler
            (CSharpProjectItemRenamed);
         }
         catch(System.Exception)
         {
         }
      }

      public void OnDisconnection(Extensibility.ext_DisconnectMode 
      disconnectMode, ref System.Array custom)
      {
         MessageBox.Show("Disconnecting add-in");
         if(CSharpProjectItemsEvents != null)
         {
            CSharpProjectItemsEvents.ItemAdded -= new 
            _dispProjectItemsEvents_ItemAddedEventHandler
            (CSharpProjectItemAdded);
            CSharpProjectItemsEvents.ItemRemoved -= new  
            _dispProjectItemsEvents_ItemRemovedEventHandler
            (CSharpProjectItemRemoved);
            CSharpProjectItemsEvents.ItemRenamed -= new 
            _dispProjectItemsEvents_ItemRenamedEventHandler
            (CSharpProjectItemRenamed);
         }
      }

      public void OnAddInsUpdate(ref System.Array custom)
      {
      }

      public void OnStartupComplete(ref System.Array custom)
      {
      }

      public void OnBeginShutdown(ref System.Array custom)
      {
      }
      
      private void CSharpProjectItemAdded(EnvDTE.ProjectItem ProjectItem)
      {
         MessageBox.Show("Item added");
      }

      private void CSharpProjectItemRemoved(EnvDTE.ProjectItem 
      ProjectItem)
      {
         MessageBox.Show("Item removed");
      }

      private void CSharpProjectItemRenamed(EnvDTE.ProjectItem 
      ProjectItem, string OldName)
      {
         MessageBox.Show("Item renamed");
      }

      private _DTE applicationObject;
      private AddIn addInInstance;
      private EnvDTE.ProjectItemsEvents CSharpProjectItemsEvents;
   }
}

How Do I Create Custom Tool Windows?

Many of the windows in Visual Studio .NET, such as the Toolbox and Task List, are known as "tool windows." They are similar to other windows, but they can also be docked with other tool windows, pinned, and so forth. Visual Studio .NET allows you to create your own custom tool windows using the CreateToolWindow method. Because the Visual Basic .NET and Visual C# .NET automation model is built in COM, however, CreateToolWindow expects to be passed a COM object, not a .NET assembly. Therefore, you currently must implement a hosting, or "shim," control to site .NET user controls on Windows forms. (Visual C++ .NET tool-window applications, however, do not require the use of a shim control.)

The code for the shim control is available for download on the MSDN Automation Samples site. Before following the steps below, go to http://msdn.microsoft.com/vstudio/downloads/samples/automation.aspx, click the "Tool Window Add-ins" link, and then download the self-extracting .exe file, ToolWindow.exe. This creates four folders: \CPPToolWindow, \CSharpToolWindow, \VBToolWindow, and \VSUserControlHost. The first three folders contain fully working samples (Visual C++ .NET, Visual C# .NET, and Visual Basic .NET, respectively) that demonstrate how to implement a custom tool window. The fourth folder, \VSUserControlHost, contains the code for the shim control.

If you would rather just inspect the code and not type it all yourself, then you can just load one of the fully functioning tool window samples and inspect the code. If, however, you want to know more about how it works, the following procedure takes you step-by-step through creating your own custom tool window project. The basic procedure is to create the shim control, load your add-in, and then add a user control to host the UI for the new tool window.

Note   Building the shim control requires that the .NET Framework SDK be installed on your system. This is included with Visual Studio .NET 2003. If you chose not to install the .NET Framework SDK when you installed Visual Studio .NET (such as by choosing minimal install), then you are most likely missing a file required to build the shim control. The file is named mscoree.h and is located in $<VS Install Directory>$\SDK\v1.1\include. If you do not have this file or directory, you must run Visual Studio .NET setup and install the .NET Framework SDK before proceeding with the following procedure. Otherwise, you will get a compilation error when building the project.

To create a custom tool window

  1. Create the shim control:
    1. Load the project in the \VSUserControlHost directory. (It is a Visual C++ .NET project.)
    2. Before building the project, on the Tools menu, click the Options command. Navigate to the VC++ Directories page by clicking Projects in the left pane of the Options dialog box, and make sure to set the Include folder to the location of the file mscoree.h (usually under C:\Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\include, but the exact location depends on where you installed Visual Studio .NET) so that it can be found during the build.

      The .NET Framework SDK is not added by default to this dialog box because some users do not need to build components that host the .NET Framework; therefore, the .NET Framework SDK Include files are not added by default to the Platform SDK.

    3. Build the shim control project.
  2. Create a new add-in solution, or load an add-in solution in which you want to create the custom tool window.
  3. To enable IntelliSense, add a reference, named VSUserControlHostLib, to the VSUserControlHost shim control you created in step 1.
  4. Add a User Control to the add-in.

    This is the control that will be placed onto the tool window to contain the UI. (The control can be in a separate assembly, but the code below defaults to the add-in's assembly.)

  5. For Visual Basic .NET, add the following code to the Connect.vb module:
    Imports VSUserControlHostLib
    
    ' Objects required to create a tool window.
    Dim windowToolWindow As Window
    Dim objControl As VSUserControlHostLib.IVSUserControlHostCtl
    
    Public Sub OnDisconnection(ByVal RemoveMode As _
      Extensibility.ext_DisconnectMode, ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnDisconnection
       ' Hide the tool window after disconnecting the add-in.
       If TypeOf windowToolWindow Is Window Then
          windowToolWindow.Visible = False
       End If
    End Sub
    
    Public Sub OnConnection(ByVal application As Object, ByVal _
      connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As _
      Object, ByRef custom As System.Array) Implements _
      Extensibility.IDTExtensibility2.OnConnection
    
       Dim objTemp As Object
       ' The following GUID must be unique for each different tool 
       ' window, but you may use the same guid for the same tool window.
       ' This GUID can be used for indexing the windows collection,
       ' for example: applicationObject.Windows.Item(guidstr)
       Dim guidstr As String = "{858C3FCD-8B39-4540-A592-F31C1520B174}"
    
       applicationObject = CType(application, DTE)
       addInInstance = CType(addInInst, AddIn)
       ' Create the tool window. Assign it the GUID specified in 
       ' guidstr. Its handle is windowToolWindow.
        windowToolWindow = _
         applicationObject.Windows.CreateToolWindow(addInInstance, _
         "VSUserControlHost.VSUserControlHostCtl", "Visual Basic .NET _
         Tool window", guidstr, objTemp)
    
       ' When using the hosting control, you must set visible to true 
       ' before calling HostUserControl. Otherwise the UserControl 
       ' cannot be hosted properly.
       windowToolWindow.Visible = True
       ' Link the user control to the tool window's Doc object 
       ' (objTemp).
       objControl = CType(objTemp, _
         VSUserControlHostLib.IVSUserControlHostCtl)
       ' Get the add-in's assembly and host the user control there.
       ' NOTE: Change "ControlClassHere" below to the name of your 
       ' control's class, such as "VBToolWinAddin.Control" or 
       ' "MyToolWin.UserControl1".
       Dim asm As System.Reflection.Assembly = _
         System.Reflection.Assembly.GetExecutingAssembly()
       objControl.HostUserControl(asm.Location, _
         "ControlClassHere")
    End Sub
    

    For Visual C# .NET, add the following line to the top of your add-in's Connect.cs class:

    using VSUserControlHostLib;
    

    and then add the following code to the OnConnection procedure:

    {
       object objTemp = null;
    
       // This GUID must be unique for each different tool window,
       // but you may use the same GUID for the same tool window.
       // This GUID can be used for indexing the windows collection,
       // for example: applicationObject.Windows.Item(guidstr)
       String guidstr = "{858C3FCD-8B39-4540-A592-F31C1520B174}";
             
       applicationObject = (_DTE)application;
       addInInstance = (AddIn)addInInst;
       windowToolWindow = applicationObject.Windows.CreateToolWindow 
       (addInInstance, "VSUserControlHost.VSUserControlHostCtl", "C# 
       Tool window", guidstr, ref objTemp);
             
       // When using the hosting control, you must set visible to true 
       // before calling HostUserControl. Otherwise the UserControl 
       // cannot be hosted properly.
       windowToolWindow.Visible = true;
       objControl = (VSUserControlHostLib.IVSUserControlHostCtl)objTemp;
       System.Reflection.Assembly asm = 
       System.Reflection.Assembly.GetExecutingAssembly();
       objControl.HostUserControl(asm.Location, 
       "ControlClassHere");
    }
    

    Add the following code to the OnDisconnection procedure:

    public void OnDisconnection(ext_DisconnectMode disconnectMode, ref 
    System.Array custom)
    {
       if(windowToolWindow != null)
       {
          windowToolWindow.Visible = false;
       }
    }
    

    At the bottom of the class, add the following:

    private Window windowToolWindow;
    private VSUserControlHostLib.IVSUserControlHostCtl objControl;
    
  6. In both examples, change the text ControlClassHere in the last line of code in the OnConnection procedure to the user control's ProgID.

    The ProgID is a combination of the name of your project and the name of your user control, separated by a period. For example, if your add-in is named "MyToolWin" and the User Control you added is named "UserControl1," the ProgID of the User Control would be MyToolWin.UserControl1.

  7. Add any desired controls and functionality to the user control.

After you build the project and activate the add-in in the Add-In Manager, the new tool window appears.

Note   Be sure that the line windowToolWindow.Visible = true is called before calling HostUserControl. Otherwise, the user control will be displayed in the upper left of your screen, parented to the desktop. You can hide the window after calling HostUserControl if you like.

So how does this work? The shim control acts as a "surface" for the new tool window to attach to. CreateToolWindow(), creates the visible, dockable tool window (based on the shim control), and finally, the User Control displays the UI for the tool window.

How Can I Add a Command for an Add-in to the Shortcut (Context/Right-Click) Menu, or the Submenu of Another Command?

Microsoft Office and Visual Studio .NET offer three kinds of CommandBar objects: toolbars, menu bars, and shortcut (also known as "context" or "popup") menus. Toolbars contain one or more menu bars. Menu bars are commands, such as File, Edit, and View. And finally, shortcut menus pop up on the screen, such as when you right-click a menu or object (such as a file or project). Submenus cascade off menu commands or off shortcut menus.

Shortcut menus are similar to other menus in Visual Studio .NET except that you access them by pointing to an arrow in a menu bar, or by right-clicking an item in the IDE rather than left-clicking. While the Visual Studio .NET Automation model makes it easy to place add-in commands on top-level menus, such as under the Tools menu, you can also add commands to shortcut menus and submenus. To do this, though, you must explicitly define the target shortcut menu and submenu by using the Microsoft Office Command Bar object model, and then call the Visual Studio .NET AddControl method.

To add a command to a shortcut menu (or any menu or toolbar), you must first know its command name. While you could do this by manually searching through the Keyboard node in the Options dialog box on the Tools menu, there is a handy sample add-in available that searches for a particular menu item, and then gives you its command name and other related information. This add-in sample is named "Command Browse Add-in" and it is located at http://msdn.microsoft.com/vstudio/downloads/samples/automation.aspx. (To access the add-in, unpack the project, build it, run the accompanying AddinReg.reg file, and then click the Command Browser command on the Tools menu.)

The following procedure demonstrates how to add an add-in command to a project's shortcut menu.

To add an add-in command to a shortcut menu

  1. Create an add-in project:
    1. In the New Project dialog box, click Other Projects, and then click Extensibility Projects. Name it "ContextMenuAddin."
    2. Select the option to create a UI for the add-in by selecting the "Create a tools menu item" option button.

      This adds some UI code to the OnConnection method. It also adds the Exec and QueryStatus methods, which will handle the event when someone clicks your add-in command and provide information on the status of your add-in, respectively.

  2. Replace the OnConnection method of the Connect class with the following:
    ' Visual Basic .NET
    Public Sub OnConnection(ByVal application As Object, ByVal _
    connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As _
    Object, ByRef custom As System.Array) Implements _
    Extensibility.IDTExtensibility2.OnConnection
       applicationObject = CType(application, EnvDTE.DTE)
       addInInstance = CType(addInInst, EnvDTE.AddIn)
       If connectMode = Extensibility.ext_ConnectMode.ext_cm_UISetup Then
          Dim objAddIn As AddIn = CType(addInInst, AddIn)
          Dim cmdObj As EnvDTE.Command
          Dim cmdBar As Microsoft.Office.Core.CommandBar
          Dim cmdBarCtl As Microsoft.Office.Core.CommandBarControl
          Dim cmdBarPopup As Microsoft.Office.Core.CommandBarPopup
    
          Try
             ' Create the add-in command.
             cmdObj = applicationObject.Commands.AddNamedCommand _
             (objAddIn, "ContextMenuAddin", "ContextMenuAddin", "Executes _
             the command for ContextMenuAddin", True, 6743, Nothing,  _
             1 + 2) '1+2 == vsCommandStatusSupported+vsCommandStatusEnabled
             ' Specify the menu (Project) and submenu (Add) where the add-
             ' in command should go.
             cmdBar = applicationObject.CommandBars.Item("Project")
             cmdBarCtl = cmdBar.Controls.Item("Add")
             ' The Add submenu is type "popup" (also known as context 
             ' menu).
             cmdBarPopup = cmdBarCtl
             cmdBar = cmdBarPopup.CommandBar
             ' Add the command to the pop-up menu.
             cmdObj.AddControl(cmdBar)
    
             Catch e As System.Exception
                MsgBox("Exception occurred: " & vbCr & e.ToString)
          End Try
       End If
    End Sub
    
    // Visual C# .NET
    public void OnConnection(object application, 
    Extensibility.ext_ConnectMode connectMode, object addInInst, ref 
    System.Array custom)
    {
       applicationObject = (_DTE)application;
       addInInstance = (AddIn)addInInst;
       AddIn objAddIn = (AddIn) addInInst;
       EnvDTE.Command cmdObj;
       Microsoft.Office.Core.CommandBar cmdBar;
       Microsoft.Office.Core.CommandBarControl cmdBarCtl;
       Microsoft.Office.Core.CommandBarPopup cmdBarPopup;
    
       object []contextGUIDS = new object[] { };
       Commands commands = applicationObject.Commands;
       _CommandBars commandBars = applicationObject.CommandBars;
    
       try
       {
          // Create the add-in command.
          cmdObj=applicationObject.Commands.AddNamedCommand(addInInstance, 
          "CSharpContextMenuAddin", "CSharpContextMenuAddin", "Executes the 
          command for CSharpContextMenuAddin", true, 6743, ref 
          contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported
          +(int)vsCommandStatus.vsCommandStatusEnabled);
          // Specify the menu (Project) and submenu (Add) where the
          // add-in command should go.
          cmdBar = applicationObject.CommandBars["Project"];
          cmdBarCtl = cmdBar.Controls["Add"];
          // The Add submenu is type "popup" (also known as context menu).
             
          cmdBarPopup=(Microsoft.Office.Core.CommandBarPopup)cmdBarCtl;
          cmdBar = cmdBarPopup.CommandBar;
          // Add the command to the pop-up menu.
          cmdObj.AddControl(cmdBar,1);               
       }
       catch(System.Exception e)
       {
          Console.WriteLine("Error encountered: "+e.Message);
       }
    }
    
  3. Add any code that you want to run when the command is clicked to the Exec procedure. You also must change the Exec and QueryStatus methods to reflect the new command name.
  4. Build the add-in. (There is no need to create a setup project unless you intend to distribute it to others.)
  5. Close the add-in solution.
  6. At the Visual Studio .NET Command Prompt, type devenv /setup.

    This resets all command bars so that the new menu command can display.

    Caution   Doing this will delete all customizations you have made to Visual Studio .NET command bars. The command bars are rebuilt according to the loaded add-ins and packages.
  7. Open or create a solution.
  8. Activate the ContextMenuAddin add-in by checking the box next to it in the Add-In Manager.
  9. Right-click the project node in Solution Explorer and then highlight the Add submenu.

    The ContextMenuAddin command now appears on the Add submenu.

Miscellaneous Tips and Issues

The following is a list of various tips to keep in mind when using Visual Studio .NET automation.

  • To unload a Solution add-in at runtime, use DTE.Addins.Item("MyAddin.Connect").Remove.
  • Visual Studio .NET automation currently does not feature auto-navigation to a code line by double-clicking an item added to the Task List, even if file name/line number are provided. Instead, you must connect to the event handler for the task that was double-clicked and then handle opening and moving to the line yourself.
  • Sometimes you need to use the Object property to access automation functionality, usually to get a reference to an automation object such as a tool window. For example, in Visual Basic .NET, to reference the Task List, you would use the following:
    Dim win As Window = DTE.Windows.Item(Constants.vsWindowKindTaskList).Object
    
  • The Visual Studio 6.0 and Visual Studio .NET automation models are almost entirely different, and thus, incompatible. Although the models are different, however, the functionality is similar, so most existing automation code can be rewritten fairly easily.
  • If your add-in's command bar is acting strangely — such as buttons disappearing, wrong icons appearing next to commands, and so forth — reset the toolbar by shutting down all instances of Visual Studio .NET and then issuing the command devenv /setup at the Visual Studio .NET Command Prompt. If that fails to fix the problem, try running the setup project for your add-in and choose the Repair option.
    Caution   Doing this will delete all customizations you have made to Visual Studio .NET command bars. The command bars are rebuilt according to the loaded add-ins and packages.
  • If old toolbar buttons for add-ins continue to persist on menus even after you unload the add-in in the Add-In Manager, either delete the registry key for the add-in or delete the add-in DLL itself (usually located in the /bin directory of the add-in project).
  • If you create an event handler that works for awhile but then suddenly stops working, then it is most likely because your event variable is scoped as local (function-level) rather than global (class-level). Since an event handler invocation prompts a garbage collection, the value is lost after exiting the event handler. For example, when responding to events using Visual C# .NET, you can add event handlers to many of the DTE events, such as:
    DTE.Events.get_DocumentEvents(null).DocumentOpened += . . . 
    DTE.Events.get_WindowEvents(docWindow).WindowActivated += . . .
    

    Unless you define the get_xxEvents variables outside the scope of these event handlers, though, they will stop responding after a short period of time (10-20 seconds) because both the events and the event handler are garbage-collected (as all temporary and local-level variables eventually are).

Conclusion

This article covers a few of the most frequently asked questions about Visual Studio .NET automation and extensibility, but other questions will undoubtedly come up in the course of your usage of the automation models. As those issues become identified, additional information — perhaps in the form of another article — will be written to address them. Until then, you can always avail yourself of the automation-related Microsoft newsgroups, which are monitored daily by Microsoft staff as well as thousands of other programmers just like you. In addition, a number of resource documents and code samples are available for free on the Web that might provide additional insight. See "For More Information" below for details.

Acknowledgements

This article would not have been possible without the contribution of time and technical expertise of Craig Skibo, Huizhong Long, Junyu Zhou, Ken Hardy, and all of the folks who participated in the technical review. Thank you!

For More Information

Show:
© 2014 Microsoft