Logo
HOWTO: Create a context menu using a Visual Studio commandbar popup from an add-in

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio .NET 2002
Date: August 2011   Microsoft Visual Studio .NET 2003
Updated: March 2013   Microsoft Visual Studio 2005
      Microsoft Visual Studio 2008
      Microsoft Visual Studio 2010
      Microsoft Visual Studio 2012
Introduction

This article shows how to create and display a context menu with the same look and feel than the ones of Visual Studio to be used in your own add-in user-interface.

More Information

The following add-in creates two commands and shows a modal window when loaded that, when right-clicked, shows a context menu using a commandBar popup of Visual Studio. This is done creating a CommandBar of the type popup and calling its ShowPopup method:

Code for the Connect class:

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

namespace MyAddin
{
   public class Connect : Extensibility.IDTExtensibility2, IDTCommandTarget, IWin32Window
   {
      // Constants for command properties
      private const string MY_COMMAND_NAME1 = "MyCommand1";
      private const string MY_COMMAND_NAME2 = "MyCommand2";
      private const string MY_COMMAND_CAPTION1 = "My command 1";
      private const string MY_COMMAND_CAPTION2 = "My command 2";
      private const string MY_COMMAND_TOOLTIP1 = "My command tooltip 1";
      private const string MY_COMMAND_TOOLTIP2 = "My command tooltip 2";

      // Variables for IDE and add-in instances
      private EnvDTE.DTE applicationObject;
      private EnvDTE.AddIn addInInstance;

      // CommandBar that will be created by the add-in
      // We must keep it at class level to remove it when the add-in is unloaded
      private CommandBar myTemporaryCommandBar;

      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
                  InitializeAddIn();
                  break;
            }
         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }
      }

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

      private EnvDTE.Command CreateCommand(string commandName, string commandCaption, string commandTooltip)
      {
         EnvDTE.Command command = null;

         // 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
         {
            command = applicationObject.Commands.Item(addInInstance.ProgID + "." + commandName);
         }
         catch
         {
         }

         // Add the command if it does not exist
         if (command == null)
         {
            command = applicationObject.Commands.AddNamedCommand(addInInstance, commandName, commandCaption, commandTooltip, true,
               59, null, (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));

         }

         return command;
      }

      private void InitializeAddIn()
      {
         // Constant for the name of the commandbar created by the add-in
         const string MY_TEMPORARY_COMMANDBAR_POPUP_NAME = "MyTemporaryCommandBarPopup";

         // The commands that will be created
         Command myCommand1 = null;
         Command myCommand2 = null;
         Form1 myForm1;

         // Buttons that will be created on the commandbar popup created by the add-in
         // We don't need to keep them at class level to remove them when the add-in is unloaded 
         // because we will remove the whole commandbar popup
         CommandBarButton myCommandBarPopupButton1;
         CommandBarButton myCommandBarPopupButton2;

         // The collection of Visual Studio commandbars
         CommandBars commandBars;

         try
         {
            // Create the commmands
            myCommand1 = CreateCommand(MY_COMMAND_NAME1, MY_COMMAND_CAPTION1, MY_COMMAND_TOOLTIP1);
            myCommand2 = CreateCommand(MY_COMMAND_NAME2, MY_COMMAND_CAPTION2, MY_COMMAND_TOOLTIP2);

            // Retrieve the collection of commandbars
            // Note:
            // - In VS.NET 2002/2003 (which uses the Office.dll reference) DTE.CommandBars returns directly a CommandBars type, so a cast 
            //   to CommandBars is redundant
            // - In VS 2005 or higher (which uses the new Microsoft.VisualStudio.CommandBars.dll reference) 
            //   DTE.CommandBars returns an Object type, so we do need a cast to CommandBars
            commandBars = (CommandBars)applicationObject.CommandBars;

            // Add a new popup commandbar 
            myTemporaryCommandBar = commandBars.Add(MY_TEMPORARY_COMMANDBAR_POPUP_NAME, MsoBarPosition.msoBarPopup, null, true);

            // Add new buttons on that commandbar popup
            myCommandBarPopupButton1 = (CommandBarButton)myCommand1.AddControl(myTemporaryCommandBar, 1);
            myCommandBarPopupButton2 = (CommandBarButton)myCommand2.AddControl(myTemporaryCommandBar, 2);

            myForm1 = new Form1();
            using (myForm1)
            {
               myForm1.ShowDialog(this, myTemporaryCommandBar);
            }
         }
         catch (System.Exception e)
         {
            System.Windows.Forms.MessageBox.Show(e.ToString());
         }
      }

      public void OnDisconnection(Extensibility.ext_DisconnectMode RemoveMode, ref System.Array custom)
      {
         try
         {
            switch (RemoveMode)
            {
               case ext_DisconnectMode.ext_dm_HostShutdown:
               case ext_DisconnectMode.ext_dm_UserClosed:

                  if ((myTemporaryCommandBar != null))
                  {
                     myTemporaryCommandBar.Delete();
                  }

                  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)
      {
      }

      private string GetCommandShortName(string cmdFullName)
      {
         string cmdShortName;

         // Remove the ProgId part from the full name
         cmdShortName = cmdFullName.Substring(cmdFullName.LastIndexOf(".") + 1);

         return cmdShortName;
      }

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

         handled = false;

         if ((executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault))
         {
            cmdShortName = GetCommandShortName(cmdName);

            switch (cmdShortName)
            {
               case MY_COMMAND_NAME1:

                  handled = true;
                  MessageBox.Show("Command1 executed.");
                  break;

               case MY_COMMAND_NAME2:

                  handled = true;
                  MessageBox.Show("Command2 executed.");
                  break;
            }
         }
      }

      public void QueryStatus(string cmdName, vsCommandStatusTextWanted neededText,
         ref vsCommandStatus statusOption, ref object commandText)
      {
         string cmdShortName;

         if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
         {

            cmdShortName = GetCommandShortName(cmdName);

            switch (cmdShortName)
            {
               case MY_COMMAND_NAME1:

                  statusOption = (vsCommandStatus)(vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported);
                  break;

               case MY_COMMAND_NAME2:

                  statusOption = (vsCommandStatus)(vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported);
                  break;

               default:

                  statusOption = vsCommandStatus.vsCommandStatusUnsupported;
                  break;
            }
         }
      }

      public System.IntPtr Handle
      {
         get
         {
            // Wraps the Win32 handle of the Visual Studio main window
            return new System.IntPtr(applicationObject.MainWindow.HWnd);
         }
      }
   }
}
Code for the Form1 class:

Language: C#   Copy Code Copy Code (IE only)
using Microsoft.VisualStudio.CommandBars;
using System.Windows.Forms;

namespace MyAddin
{
   public partial class Form1 : Form
   {
      public Form1()
      {
         InitializeComponent();
      }

      CommandBar myContextMenu;

      public DialogResult ShowDialog(IWin32Window parent, CommandBar myCommandBar)
      {
         this.Text = "Right-click on me";
         this.StartPosition = FormStartPosition.CenterScreen;
         this.ShowInTaskbar = false;

         myContextMenu = myCommandBar;

         return base.ShowDialog(parent);

      }

      private void Form1_MouseClick(object sender, MouseEventArgs e)
      {
         System.Drawing.Point point;

         if (e.Button == MouseButtons.Right)
         {
            try
            {
               // The ShowPopup method requires screen coordinates
               point = this.PointToScreen(new System.Drawing.Point(e.X, e.Y));

               myContextMenu.ShowPopup(point.X, point.Y);
            }
            catch (System.Exception ex)
            {
               MessageBox.Show(ex.ToString());
            }
         }
      }
   }
}
Code for the Connect class:

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

Public Class Connect
   Implements Extensibility.IDTExtensibility2
   Implements IDTCommandTarget
   Implements IWin32Window

   ' Constants for command properties
   Private Const MY_COMMAND_NAME1 As String = "MyCommand1"
   Private Const MY_COMMAND_NAME2 As String = "MyCommand2"
   Private Const MY_COMMAND_CAPTION1 As String = "My command 1"
   Private Const MY_COMMAND_CAPTION2 As String = "My command 2"
   Private Const MY_COMMAND_TOOLTIP1 As String = "My command tooltip 1"
   Private Const MY_COMMAND_TOOLTIP2 As String = "My command tooltip 2"

   ' Variables for IDE and add-in instances
   Private applicationObject As EnvDTE.DTE
   Private addInInstance As EnvDTE.AddIn

   ' CommandBar that will be created by the add-in
   ' We must keep it at class level to remove it when the add-in is unloaded
   Private myTemporaryCommandBar As CommandBar

   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
               InitializeAddIn()

         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

      InitializeAddIn()

   End Sub

   Private Function CreateCommand(ByVal commandName As String, ByVal commandCaption As String, ByVal commandTooltip As String) As EnvDTE.Command

      Dim command As EnvDTE.Command = Nothing

      ' 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
         command = applicationObject.Commands.Item(addInInstance.ProgID & "." & commandName)
      Catch
      End Try

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

         command = applicationObject.Commands.AddNamedCommand(addInInstance, commandName, commandCaption, commandTooltip, True, _
            59, Nothing, vsCommandStatus.vsCommandStatusSupported Or vsCommandStatus.vsCommandStatusEnabled)

      End If

      Return command

   End Function

   Private Sub InitializeAddIn()

      ' Constant for the name of the commandbar created by the add-in
      Const MY_TEMPORARY_COMMANDBAR_POPUP_NAME As String = "MyTemporaryCommandBarPopup"

      ' The commands that will be created
      Dim myCommand1 As Command = Nothing
      Dim myCommand2 As Command = Nothing
      Dim myForm1 As Form1

      ' Buttons that will be created on the commandbar popup created by the add-in
      ' We don't need to keep them at class level to remove them when the add-in is unloaded 
      ' because we will remove the whole commandbar popup
      Dim myCommandBarPopupButton1 As CommandBarButton
      Dim myCommandBarPopupButton2 As CommandBarButton

      ' The collection of Visual Studio commandbars
      Dim commandBars As CommandBars

      Try

         ' Create the commmands
         myCommand1 = CreateCommand(MY_COMMAND_NAME1, MY_COMMAND_CAPTION1, MY_COMMAND_TOOLTIP1)
         myCommand2 = CreateCommand(MY_COMMAND_NAME2, MY_COMMAND_CAPTION2, MY_COMMAND_TOOLTIP2)

         ' Retrieve the collection of commandbars
         ' Note:
         ' - In VS.NET 2002/2003 (which uses the Office.dll reference) DTE.CommandBars returns directly a CommandBars type, so a cast 
         '   to CommandBars is redundant
         ' - In VS 2005 or higher (which uses the new Microsoft.VisualStudio.CommandBars.dll reference) 
         '   DTE.CommandBars returns an Object type, so we do need a cast to CommandBars
         commandBars = DirectCast(applicationObject.CommandBars, CommandBars)

         ' Add a new popup commandbar 
         myTemporaryCommandBar = commandBars.Add(MY_TEMPORARY_COMMANDBAR_POPUP_NAME, MsoBarPosition.msoBarPopup, Nothing, True)

         ' Add new buttons on that commandbar popup
         myCommandBarPopupButton1 = DirectCast(myCommand1.AddControl(myTemporaryCommandBar, 1), CommandBarButton)
         myCommandBarPopupButton2 = DirectCast(myCommand2.AddControl(myTemporaryCommandBar, 2), CommandBarButton)

         myForm1 = New Form1()
         Using (myForm1)
            myForm1.ShowDialog(Me, myTemporaryCommandBar)
         End Using

      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

      Try

         Select Case RemoveMode

            Case ext_DisconnectMode.ext_dm_HostShutdown, ext_DisconnectMode.ext_dm_UserClosed

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

         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

   Private Function GetCommandShortName(ByVal cmdFullName As String) As String

      Dim cmdShortName As String

      ' Remove the ProgId part from the full name
      cmdShortName = cmdFullName.Substring(cmdFullName.LastIndexOf(".") + 1)

      Return cmdShortName

   End Function

   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

      Dim cmdShortName As String

      handled = False

      If (executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault) Then

         cmdShortName = GetCommandShortName(cmdName)

         Select Case cmdShortName

            Case MY_COMMAND_NAME1

               handled = True
               MessageBox.Show("Command1 executed.")

            Case MY_COMMAND_NAME2

               handled = True
               MessageBox.Show("Command2 executed.")

         End Select

      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

      Dim cmdShortName As String

      If neededText = vsCommandStatusTextWanted.vsCommandStatusTextWantedNone Then

         cmdShortName = GetCommandShortName(cmdName)

         Select Case cmdShortName

            Case MY_COMMAND_NAME1

               statusOption = CType(vsCommandStatus.vsCommandStatusEnabled + vsCommandStatus.vsCommandStatusSupported, vsCommandStatus)

            Case MY_COMMAND_NAME2

               statusOption = CType(vsCommandStatus.vsCommandStatusEnabled + vsCommandStatus.vsCommandStatusSupported, vsCommandStatus)

            Case Else

               statusOption = vsCommandStatus.vsCommandStatusUnsupported

         End Select

      End If

   End Sub

   Public ReadOnly Property Handle As System.IntPtr Implements System.Windows.Forms.IWin32Window.Handle
      Get
         ' Wraps the Win32 handle of the Visual Studio main window
         Return New System.IntPtr(applicationObject.MainWindow.HWnd)
      End Get
   End Property

End Class
Code for the Form1 class:

Language: VB.NET   Copy Code Copy Code (IE only)
Imports Microsoft.VisualStudio.CommandBars
Imports System.Windows.Forms

Public Class Form1

   Private myContextMenu As CommandBar

   Public Overloads Function ShowDialog(ByVal parent As IWin32Window, ByVal myCommandBar As CommandBar) As DialogResult

      Me.Text = "Right-click on me"
      Me.StartPosition = FormStartPosition.CenterScreen
      Me.ShowInTaskbar = False

      myContextMenu = myCommandBar

      Return MyBase.ShowDialog(parent)

   End Function

   Private Sub Form1_MouseClick(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseClick

      Dim point As System.Drawing.Point

      If e.Button = Windows.Forms.MouseButtons.Right Then

         Try

            ' The ShowPopup method requires screen coordinates
            point = Me.PointToScreen(New System.Drawing.Point(e.X, e.Y))

            myContextMenu.ShowPopup(point.X, point.Y)

         Catch ex As Exception
            MessageBox.Show(ex.ToString)
         End Try

      End If

   End Sub

End Class

Related articles


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


Top