Logo
HOWTO: Create a toolwindow with a ToolWindowPane class in a Visual Studio package

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio 2010
Date: February 2015   Microsoft Visual Studio 2012
      Microsoft Visual Studio 2013
      Microsoft Visual Studio 2015
Introduction

Toolwindows are modeless, dockable windows you can create in your Visual Studio extension, like the Solution Explorer, Error List, etc. There are some important details regarding toolwindows:

  • Toolwindows are composed of several pieces:
    • A usercontrol (Windows Forms or Windows Presentation Foundation- WPF) with the actual content. This usercontrol is provided by the package.
    • A window frame which hosts a toolwindow pane which in turn hosts inside the usercontrol. Both the window frame and toolwindow pane are provided by Visual Studio, but the package must provide a class that inherits the pane and initializes it in its constructor with the caption, bitmap and usercontrol.
  • Toolwindows can be in several states. See the article HOWTO: Understanding toolwindow states in Visual Studio.
  • Each toolwindow is identified by a unique Guid and instance Id. Visual Studio uses that information to store persistence about the size, position, state, etc. of the toolwindow (this is done either for toolwindows created by packages or for toolwindows created by add-ins).
  • Once a toolwindow has been created, it is never destroyed until Visual Studio is closed. When the users closes a toolwindow with the "X" button in the upper-right corner, the toolwindow is actually hidden, but not destroyed. You can verify this noting that the pane constructor is called only once, not each time that you show the toolwindow after having closed it. If a package is interested in knowing when the state of a toolwindow changes it can use the IVsWindowFrameNotify3.OnShow method, which receives a __FRAMESHOW type parameter with the state (show, hidden, closed, maximized, etc.).
  • Toolwindows created by packages can be shown in two scenarios:
    • The user clicks on a button to show the toolwindow (the first time or after having closed a toolwindow, to show it again). This was the only scenario for toolwindows created by add-ins.
    • Visual Studio, when launched, shows automatically the toolwindows that were visible when the last Visual Studio session was closed. This scenario is not provided by Visual Studio for toolwindows created by add-ins, it is available only for toolwindows created by packages. Note: Visual Studio can also show toolwindows in some UI contexts (see dynamic toolwindows).

The first scenario works as follows:

  • A command and a button are provided by the package to show the toolwindow.
  • When the command is executed, the package tells Visual Studio to show the toolwindow, indicating that if the toolwindow has not been shown before, it must create it (creation happens only once, as stated above, because toolwindows are never destroyed, only hidden).

The second scenario works as follows:

  • When Visual Studio is closed, it stores somewhere the Guids of the toolwindows that were visible.
  • When Visual Studio is launched again, it retrieves the Guids of the toolwindows that were visible and for each one:
    • It locates its owner package. For this purpose it uses the registry key HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\<version>_Config\ToolWindows. For each toolwindow Guid, its package Guid is associated.
    • It loads the package. Notice that while some packages are marked to load on startup and others aren't (see HOWTO: Autoload a Visual Studio package), if a toolwindow must be shown, its package is loaded when Visual Studio is launched even if the package was not marked to load on startup.
    • It creates the toolwindow pane corresponding to that Guid. As explained above, the constructor of the pane initializes the caption, bitmap and usercontrol.
    • It shows the toolwindow pane.

More Information

The steps that the package wizard performs when you select the "Toolwindow" checkbox in the page of options are the following (and these are also the steps that you need to perform manually if you want to add a second toolwindow to your package):

  • It generates the declaration for a command and button (under the "View" > "Other Windows" menu) in the .vsct file. Notice that a package only requires a Guid for the package and a Guid for its command set. To add a second toolwindow button, you only need a new cmdid, not a new guid for a new command set.
  • The command is bound to the ShowToolWindow() method in the Initialize() method of the package:
    protected override void Initialize()
    {
       base.Initialize();
             
       // Add our command handlers for menu (commands must exist in the .vsct file)
       OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
      
       if ( null != mcs )
       {
          // Create the command for the tool window
          CommandID toolwndCommandID = new CommandID(GuidList.guidVSMyPackageCmdSet, (int)PkgCmdIDList.cmdidMyTool);
             
          MenuCommand menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
             
          mcs.AddCommand(menuToolWin);
       }
    } 
    
  • It generates a new, unique Guid for the toolwindow (you can get a new Guid using the "Tools" > "Create GUID" external tool). It declares this Guid in the static class GuidList of the file Guids.cs:
    public const string guidToolWindowPersistanceString = "<guid>";
    
  • It creates a usercontrol ToolWindowControl.xaml (WPF, but you can use a Windows Forms usercontrol also. See HOWTO: Host a Windows Forms usercontrol in a toolwindow from a Visual Studio package.)
  • It creates a class MyToolWindow.cs that inherits from ToolWindowPane.
    • The constructor of this class initializes the caption, bitmap and usercontrol:
      public MyToolWindow() : base(null)
      {
         this.Caption = Resources.ToolWindowTitle;
         this.BitmapResourceID = 301
         this.BitmapIndex = 1;
         base.Content = new MyControl();
      } 
      
    • The Guid attribute of this class identifies the toolwindow. Notice that while Visual Studio hard-codes the Guid, it is much better practice to reference the constant in the GuidList class (see: BUG: Toolwindow guid attribute value hardcoded in code generated by Visual Studio package wizard):
      // [Guid("<hard-coded-guid>")]
      [Guid(GuidList.guidToolWindowPersistanceString)]
      public class MyToolWindow : ToolWindowPane
      {
      ...
      }
      
  • It decorates the package class with the ProvideToolWindow attribute, passing as parameter in the constructor the type of the class that inherits from ToolWindowPane:
    [ProvideToolWindow(typeof(MyToolWindow))]
    public sealed class VSMyPackage : Package
    {
    ...
    }
    
    This attribute is used for two purposes:
    • When the package is compiled, the .pkgdef file generated in the output folder contains the association between the toolwindow guid and the package guid:
      [$RootKey$\ToolWindows\{<toolwindowGuid>}]
      @="{<packageGuid>}"
      "Name"="MyCompany.VSMyPackage.MyToolWindow"
      
      And when the package is registered inside Visual Studio that association in the .pkgdef file is stored in the registry key HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\<version>_Config\ToolWindows

      This information is required for the second scenario mentioned above (to locate and load the package of a toolwindow that must be shown when Visual Studio is launched because it was visible when Visual Studio was closed in the last session).
    • To provide optional properties that allows you to specify the initial size, position, orientation, style, etc. These properties of the attribute are queried when the toolwindow is created. The creation fails if this attribute is missing.
  • In the ShowToolWindow method that is bound to the command, it tries to find the toolwindow and if not found, it creates it:
    private void ShowToolWindow(object sender, EventArgs e)
    {        
       // Get the instance number 0 of this tool window. This window is single instance so this instance is actually the only one.
       // The last flag is set to true so that if the tool window does not exists it will be created.
             
       ToolWindowPane window = this.FindToolWindow(typeof(MyToolWindow), 0, true);
             
       if ((null == window) || (null == window.Frame))
       {
          throw new NotSupportedException(Resources.CanNotCreateWindow);
       }
       IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
       Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
    }
    
    The FindToolWindow method works internally as follows:
    • First, it tries to find the toolwindow (searching by toolwindow guid and toolwindow instance id) in the collection of toolwindows that the Package base class keeps with the created toolwindows.
    • If not found, it creates the toolwindow (which adds it to the collection of toolwindows for subsequent searches). It happens that the method that creates the toolwindow needs the ProvideToolWindow attribute (to get the optional properties mentioned above). Since the ProvideToolWindow attribute type is not passed to the FindToolWindow method, it is retrieved using System.Attribute.GetCustomAttributes(toolWindowType). That means that the ProvideToolWindow attribute is required for the creation of a toolwindow (the creation fails if missing).

Related articles



Go to the 'Visual Studio Extensibility (VSX)' web site for more articles like this (Articles section)