Logo
HOWTO: Create a dockable toolwindow from a Visual Studio .NET add-in

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio 2005
Created: January 2006   Microsoft Visual Studio 2008
Updated: March 2013   Microsoft Visual Studio 2010
      Microsoft Visual Studio 2012
Introduction

A Visual Studio .NET add-in can show two kind of windows:

  • Modal windows: these windows are regular Windows forms that are shown by calling the ShowDialog method.
  • Modeless windows: although you could use regular Windows forms for this purpose by calling the Show method, these windows would not be dockable and they have some problems with the keyboard focus. The proper way of creating a modeless dockable window is using the EnvDTE80.Windows2.CreateToolWindow2() function. This article explains how to do this.

More Information

A toolwindow created with the CreateToolWindow2 function has the EnvDTE.Window type, as any other built-in toolwindow of the IDE. The toolwindow works hosting a usercontrol, provided by the add-in, with the actual content of the window.

The CreateToolWindow2 function has the following signature:

Language: C#   Copy Code Copy Code (IE only)
EnvDTE.Window CreateToolWindow2(EnvDTE.AddIn Addin, string Assembly,
   string Class, string Caption, string GuidPosition, out Object ControlObject)
Language: VB.NET   Copy Code Copy Code (IE only)
Function CreateToolWindow2(ByVal Addin As EnvDTE.AddIn, ByVal Assembly As String, _
   ByVal Class As String, ByVal Caption As String, ByVal GuidPosition As String, _
   ByRef ControlObject As Object) As EnvDTE.Window

The function returns the EnvDTE.Window created, and it receives the following parameters:

  • AddIn (EnvDTE.AddIn type): the instance of the add-in. This instance was passed on the OnConnection method.
  • Assembly (String type): the file path to the assembly that contains the usercontrol that will be hosted by the toolwindow. Usually it is the assembly of the add-in, so you should pass System.Reflection.Assembly.GetExecutingAssembly().Location.
  • Class (String type): the full name (that is, including namespace) of the class of the usercontrol. Rather than hardcoding it ("Namespace1.Usercontrol1"), it is better to get the Type of the usercontrol and use its FullName property. That way if you change the name of the usercontrol or its namespace, the compiler will detect the change.
  • Caption (String type): the caption of the toolwindow.
  • GuidPosition (String type): a unique GUID that identifies the toolwindow. Visual Studio uses this identifier to persist the size and position of the toolwindow. So, each toolwindow should have a different GUID.
  • ControlObject (Object type): after calling the function, it returns the instance of the usercontrol that has been created. This instance can be also retrieved at any time with the EnvDTE.Window.Object property. In both cases the usercontrol is returned as Object type, so you must cast it to the actual type of your usercontrol. Note: the class of the usercontrol must be visible to COM, otherwise both this parameter or the EnvDTE.Window.Object value are returned null. See the article PRB: EnvDTE80.Windows2.CreateToolWindow2 doesn't return the created usercontrol in the last parameter.

You need to create a toolwindow only once (the first time that it is required) in the lifetime of your add-in. Since toolwindows are never destroyed (when you close them you are really making them invisible), the next time that a toolwindow is required to show you simply make it visible by calling Window.Visible = True. This implies that you need to keep the instances of the created toolwindows at class level, not a method level. Notice also the add-in needs to store the visibility of the toolwindow when unloaded to restore it when loaded again.

The following sample shows the code of an add-in that:

  • Creates a button on the "Standard" toolbar.
  • When the button is clicked, it shows the toolwindow (creating it the first time).
  • Stores in the Windows registry (HKEY_CURRENT_USER\Software\MyToolWindow registry key, MyToolwindowVisible value) the visibility of the toolwindow when the add-in is unloaded.
  • Restores the visibility of the toolwindow when the add-in is loaded.

You need to add a UserControl named "UserControl1" to the add-in project.

Language: C#   Copy Code Copy Code (IE only)
using System;
using Microsoft.VisualStudio.CommandBars;
using Extensibility;
using EnvDTE;
using EnvDTE80;

namespace ToolWindowAddIn
{
   public class Connect : Extensibility.IDTExtensibility2, IDTCommandTarget
   {
      private const int TOOLWINDOW_INVISIBLE = 0;
      private const int TOOLWINDOW_VISIBLE = 1;

      private const string MY_COMMAND_NAME = "MyCommand";
      private const string MY_COMMAND_CAPTION = "My toolwindow";
      private const string MY_COMMAND_TOOLTIP = "Show the toolwindow of the add-in";

      private EnvDTE.DTE applicationObject;
      private EnvDTE.AddIn addInInstance;

      private CommandBarButton myStandardCommandBarButton;
      private EnvDTE.Window myToolWindow;

      public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode,
         object addInInst, ref System.Array custom)
      {
         try
         {
            applicationObject = (EnvDTE.DTE)application;
            addInInstance = (EnvDTE.AddIn)addInInst;

            switch (connectMode)
            {
               case ext_ConnectMode.ext_cm_UISetup:

                  // Do nothing for this add-in with temporary user interface
                  break;

               case ext_ConnectMode.ext_cm_Startup:

                  // The add-in was marked to load on startup
                  // Do nothing at this point because the IDE may not be fully initialized
                  // Visual Studio will call OnStartupComplete when fully initialized
                  break;

               case ext_ConnectMode.ext_cm_AfterStartup:

                  // The add-in was loaded by hand after startup using the Add-In Manager
                  // Initialize it in the same way that when is loaded on startup
                  AddTemporaryUI();
                  break;
            }
         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }
      }

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

      public void AddTemporaryUI()
      {
         const string VS_STANDARD_COMMANDBAR_NAME = "Standard";

         Command myCommand = null;
         CommandBar standardCommandBar = null;
         CommandBars commandBars = null;
         Microsoft.Win32.RegistryKey registryKey;

         object[] contextUIGuids = new object[] { };

         try
         {
            // Try to retrieve the command, just in case it was already created, ignoring the 
            // exception that would happen if the command was not created yet.
            try
            {
               myCommand = applicationObject.Commands.Item(addInInstance.ProgID + "." + MY_COMMAND_NAME, -1);
            }
            catch
            {
            }

            // Add the command if it does not exist
            if (myCommand == null)
            {
               myCommand = applicationObject.Commands.AddNamedCommand(addInInstance,
                  MY_COMMAND_NAME, MY_COMMAND_CAPTION, MY_COMMAND_TOOLTIP, true, 59, ref contextUIGuids,
                  (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));
            }

            // Retrieve the collection of commandbars
            commandBars = (CommandBars)applicationObject.CommandBars;

            // Retrieve some built-in commandbars
            standardCommandBar = commandBars[VS_STANDARD_COMMANDBAR_NAME];

            // Add a button on the "Standard" toolbar
            myStandardCommandBarButton = (CommandBarButton)myCommand.AddControl(standardCommandBar,
               standardCommandBar.Controls.Count + 1);

            // Change some button properties
            myStandardCommandBarButton.Caption = MY_COMMAND_CAPTION;
            myStandardCommandBarButton.BeginGroup = true;

            // Get if the toolwindow was visible when the add-in was unloaded last time to show it
            registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\MyToolWindow");
            if (registryKey != null) 
            {
               if ((int)registryKey.GetValue("MyToolwindowVisible") == TOOLWINDOW_VISIBLE)
               {
                  ShowToolWindow();
               }
               registryKey.Close();
            }

         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }
      }

      public void OnDisconnection(Extensibility.ext_DisconnectMode RemoveMode, ref System.Array custom)
      {
         Microsoft.Win32.RegistryKey registryKey;
         int myToolWindowVisible;

         try
         {
            switch (RemoveMode)
            {
               case ext_DisconnectMode.ext_dm_HostShutdown:
               case ext_DisconnectMode.ext_dm_UserClosed:

                  if ((myStandardCommandBarButton != null))
                  {
                     myStandardCommandBarButton.Delete(true);
                  }

                  
                  // Store in the Windows Registry if the toolwindow was visible when unloading the add-in
                  myToolWindowVisible = TOOLWINDOW_INVISIBLE;
                  if (myToolWindow != null) 
                  {
                     if (myToolWindow.Visible)
                     {
                        myToolWindowVisible = TOOLWINDOW_VISIBLE;
                     }
                  }

                  registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\MyToolWindow");
                  registryKey.SetValue("MyToolwindowVisible", myToolWindowVisible);
                  registryKey.Close();

                  break;
            }
         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }
      }

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

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

      public void Exec(string cmdName, vsCommandExecOption executeOption, ref object varIn,
         ref object varOut, ref bool handled)
      {

         handled = false;

         if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
         {
            if (cmdName == addInInstance.ProgID + "." + MY_COMMAND_NAME)
            {
               handled = true;
               ShowToolWindow();
            }
         }
      }

      public void QueryStatus(string cmdName, vsCommandStatusTextWanted neededText,
         ref vsCommandStatus statusOption, ref object commandText)
      {
         if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
         {
            if (cmdName == addInInstance.ProgID + "." + MY_COMMAND_NAME)
            {
               statusOption = (vsCommandStatus)(vsCommandStatus.vsCommandStatusEnabled |
                  vsCommandStatus.vsCommandStatusSupported);
            }
            else
            {
               statusOption = vsCommandStatus.vsCommandStatusUnsupported;
            }
         }
      }

      private void ShowToolWindow()
      { 
         const string TOOLWINDOW_GUID = "{6CCD0EE9-20DB-4636-9149-665A958D8A9A}";

         EnvDTE80.Windows2 windows2;
         string assembly;
         object myUserControlObject = null;
         UserControl1 myUserControl;

         try
         {
            if (myToolWindow == null) // First time, create it
            {
               windows2 = (EnvDTE80.Windows2) applicationObject.Windows;

               assembly = System.Reflection.Assembly.GetExecutingAssembly().Location;

               myToolWindow = windows2.CreateToolWindow2(addInInstance, assembly, 
                  typeof(UserControl1).FullName, "My toolwindow", TOOLWINDOW_GUID, ref myUserControlObject);

               myUserControl = (UserControl1) myUserControlObject;

               // Now you can pass values to the instance of the usercontrol
               // myUserControl.Initialize(value1, value2)

            }

            myToolWindow.Visible = true;
         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }

      }
   }
}
Language: VB.NET   Copy Code Copy Code (IE only)
Imports System
Imports Microsoft.VisualStudio.CommandBars
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80

Public Class Connect
   Implements Extensibility.IDTExtensibility2
   Implements IDTCommandTarget

   Private Const TOOLWINDOW_INVISIBLE As Integer = 0
   Private Const TOOLWINDOW_VISIBLE As Integer = 1

   Private Const MY_COMMAND_NAME As String = "MyCommand"
   Private Const MY_COMMAND_CAPTION As String = "My toolwindow"
   Private Const MY_COMMAND_TOOLTIP As String = "Show the toolwindow of the add-in"

   Private applicationObject As EnvDTE.DTE
   Private addInInstance As EnvDTE.AddIn

   Private myStandardCommandBarButton As CommandBarButton
   Private myToolWindow As EnvDTE.Window

   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

      Try

         applicationObject = CType(application, EnvDTE.DTE)
         addInInstance = CType(addInInst, EnvDTE.AddIn)

         Select Case connectMode

            Case ext_ConnectMode.ext_cm_UISetup

               ' Do nothing for this add-in with temporary user interface

            Case ext_ConnectMode.ext_cm_Startup

               ' The add-in was marked to load on startup
               ' Do nothing at this point because the IDE may not be fully initialized
               ' Visual Studio will call OnStartupComplete when fully initialized

            Case ext_ConnectMode.ext_cm_AfterStartup

               ' The add-in was loaded by hand after startup using the Add-In Manager
               ' Initialize it in the same way that when is loaded on startup
               AddTemporaryUI()

         End Select

      Catch e As System.Exception
         System.Windows.Forms.MessageBox.Show(e.ToString)
      End Try

   End Sub

   Public Sub OnStartupComplete(ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnStartupComplete

      AddTemporaryUI()

   End Sub

   Public Sub AddTemporaryUI()

      Const VS_STANDARD_COMMANDBAR_NAME As String = "Standard"
 
      Dim myCommand As Command = Nothing
      Dim standardCommandBar As CommandBar
      Dim commandBars As CommandBars
      Dim registryKey As Microsoft.Win32.RegistryKey
      
      Try

         ' Try to retrieve the command, just in case it was already created, ignoring the 
         ' exception that would happen if the command was not created yet.
         Try
            myCommand = applicationObject.Commands.Item(addInInstance.ProgID & "." & MY_COMMAND_NAME)
         Catch
         End Try

         ' Add the command if it does not exist
         If myCommand Is Nothing Then

            myCommand = applicationObject.Commands.AddNamedCommand(addInInstance, _
               MY_COMMAND_NAME, MY_COMMAND_CAPTION, MY_COMMAND_TOOLTIP, True, _
               59, Nothing, vsCommandStatus.vsCommandStatusSupported Or _
               vsCommandStatus.vsCommandStatusEnabled)

         End If

         ' Retrieve the collection of commandbars
         commandBars = DirectCast(applicationObject.CommandBars, CommandBars)

         ' Retrieve some built-in commandbars
         standardCommandBar = commandBars.Item(VS_STANDARD_COMMANDBAR_NAME)

         ' Add a button to the built-in "Standard" toolbar
         myStandardCommandBarButton = DirectCast(myCommand.AddControl(standardCommandBar, _
            standardCommandBar.Controls.Count + 1), CommandBarButton)

         ' Change some button properties
         myStandardCommandBarButton.Caption = MY_COMMAND_CAPTION
         myStandardCommandBarButton.BeginGroup = True

         ' Get if the toolwindow was visible when the add-in was unloaded last time to show it
         registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\MyToolWindow")
         If Not (registryKey Is Nothing) Then
            If CType(registryKey.GetValue("MyToolwindowVisible"), Integer) = TOOLWINDOW_VISIBLE Then
               ShowToolWindow()
            End If
            registryKey.Close()
         End If

      Catch e As System.Exception
         System.Windows.Forms.MessageBox.Show(e.ToString)
      End Try

   End Sub

   Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, _
      ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnDisconnection

      Dim registryKey As Microsoft.Win32.RegistryKey
      Dim myToolWindowVisible As Integer

      Try

         Select Case RemoveMode

            Case ext_DisconnectMode.ext_dm_HostShutdown, ext_DisconnectMode.ext_dm_UserClosed

               If Not (myStandardCommandBarButton Is Nothing) Then
                  myStandardCommandBarButton.Delete()
               End If

               ' Store in the Windows Registry if the toolwindow was visible when unloading the add-in
               myToolWindowVisible = TOOLWINDOW_INVISIBLE
               If Not (myToolWindow Is Nothing) Then
                  If myToolWindow.Visible Then
                     myToolWindowVisible = TOOLWINDOW_VISIBLE
                  End If
               End If

               registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Software\MyToolWindow")
               registryKey.SetValue("MyToolwindowVisible", myToolWindowVisible)
               registryKey.Close()

         End Select

      Catch e As System.Exception
         System.Windows.Forms.MessageBox.Show(e.ToString)
      End Try

   End Sub

   Public Sub OnBeginShutdown(ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnBeginShutdown
   End Sub

   Public Sub OnAddInsUpdate(ByRef custom As System.Array) _
       Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
   End Sub

   Public Sub Exec(ByVal cmdName As String, _
      ByVal executeOption As vsCommandExecOption, _
      ByRef varIn As Object, ByRef varOut As Object, ByRef handled As Boolean) _
      Implements IDTCommandTarget.Exec

      handled = False

      If (executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault) Then

         If cmdName = addInInstance.ProgID & "." & MY_COMMAND_NAME Then
            handled = True

            ShowToolWindow()

         End If

      End If

   End Sub

   Public Sub QueryStatus(ByVal cmdName As String, _
      ByVal neededText As vsCommandStatusTextWanted, _
      ByRef statusOption As vsCommandStatus, ByRef commandText As Object) _
      Implements IDTCommandTarget.QueryStatus

      If neededText = vsCommandStatusTextWanted.vsCommandStatusTextWantedNone Then

         If cmdName = addInInstance.ProgID & "." & MY_COMMAND_NAME Then
            statusOption = CType(vsCommandStatus.vsCommandStatusEnabled + _
               vsCommandStatus.vsCommandStatusSupported, vsCommandStatus)
         Else
            statusOption = vsCommandStatus.vsCommandStatusUnsupported
         End If

      End If

   End Sub

   Private Sub ShowToolWindow()

      Const TOOLWINDOW_GUID As String = "{6CCD0EE9-20DB-4636-9149-665A958D8A9A}"

      Dim windows2 As EnvDTE80.Windows2
      Dim assembly As String
      Dim myUserControlObject As Object = Nothing
      Dim myUserControl As UserControl1

      Try

         If myToolWindow Is Nothing Then ' First time, create it

            windows2 = DirectCast(applicationObject.Windows, EnvDTE80.Windows2)

            assembly = System.Reflection.Assembly.GetExecutingAssembly().Location

            myToolWindow = windows2.CreateToolWindow2(addInInstance, assembly, _
               GetType(UserControl1).FullName, "My toolwindow", TOOLWINDOW_GUID, myUserControlObject)

            myUserControl = DirectCast(myUserControlObject, UserControl1)

            ' Now you can pass values to the instance of the usercontrol
            'myUserControl.Initialize(value1, value2)

         End If

         myToolWindow.Visible = True

      Catch e As System.Exception
         System.Windows.Forms.MessageBox.Show(e.ToString)
      End Try

   End Sub

End Class

Related articles


Go back to the 'Resources for Visual Studio .NET extensibility' section for more articles like this


Top