Logo
HOWTO: Prevent dead CommandBarButtons when Visual Studio or an add-in crashes.

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio .NET 2002
Date: March 2009   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

When an add-in creates commandbar buttons (CommandBarButton class) on built-in toolbars or context menus of Visual Studio using a temporary user-interface approach, it should remove them when unloaded (using a permanent user-interface approach they should remain, though). To learn the difference between a temporary and a permanent user-interface approach see HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in.

CommandBar buttons are typically removed using the CommandBarButton.Delete method. However, if Visual Studio or the add-in crashes before the add-in is unloaded, it doesn't have the chance of removing those buttons and they would remain there forever.

More Information

An approach to prevent this problem is to delete the command (rather that the commandbar button) and re-create it when the add-in is unloaded. Since deleting a command removes all buttons created from it, the next time that the add-in can be unloaded successfully even dead buttons will be removed.

Notes:

  • The command must be recreated to avoid losing user-defined keyboard bindings.
  • While this approach doesn't prevent duplicated buttons the next time the add-in is loaded after a crash, it prevents duplicated buttons to stay forever.

The sample code below shows this approach. To test the code:

  • Create, run and load the add-in. Two buttons are created on the Standard toolbar of Visual Studio.
  • Close Visual Studio. You are asked to simulate a crash. Answer "yes".
  • Open Visual Studio and load the add-in. Notice that now there are duplicated buttons, two from the previous session, two from the current one.
  • Close Visual Studio. You are asked to simulate a crash. Answer "no". (the four buttons are removed in this step)
  • Open Visual Studio and load the add-in. Notice that there are no longer duplicated buttons.
Language: VB.NET   Copy Code Copy Code (IE only)
Public Class Connect
   Implements Extensibility.IDTExtensibility2, EnvDTE.IDTCommandTarget

   Private Const m_COMMAND1_NAME As String = "MyCommand1"
   Private Const m_COMMAND2_NAME As String = "MyCommand2"

   Private Const m_COMMAND1_CAPTION As String = "My Command 1"
   Private Const m_COMMAND2_CAPTION As String = "My Command 2"

   Private Const m_COMMAND1_TOOLTIP As String = "Executes Command 1"
   Private Const m_COMMAND2_TOOLTIP As String = "Executes Command 2"

   Private Const m_COMMAND1_IMAGE As Integer = 59
   Private Const m_COMMAND2_IMAGE As Integer = 60

   Private m_dte As EnvDTE.DTE
   Private m_addIn As EnvDTE.AddIn

   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

         m_dte = CType(application, EnvDTE.DTE)
         m_addIn = CType(addInInst, EnvDTE.AddIn)

         Select Case connectMode

            Case ext_ConnectMode.ext_cm_UISetup

               ' Create commands in the UI Setup phase. This phase is called only once when the add-in is deployed.
               CreateCommands()

            Case ext_ConnectMode.ext_cm_Startup

               ' Do nothing yet, wait until the IDE is fully initialized (OnStartupComplete will be called)

            Case ext_ConnectMode.ext_cm_AfterStartup

               InitializeAddIn()

         End Select

      Catch objException As Exception
         MessageBox.Show(objException.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
      End Try

   End Sub

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

      InitializeAddIn()

   End Sub

   Private Sub CreateCommands()

      CreateCommand(m_COMMAND1_NAME, m_COMMAND1_CAPTION, m_COMMAND1_TOOLTIP, m_COMMAND1_IMAGE, Nothing)
      CreateCommand(m_COMMAND2_NAME, m_COMMAND2_CAPTION, m_COMMAND2_TOOLTIP, m_COMMAND2_IMAGE, Nothing)

   End Sub

   Private Sub CreateCommand(ByVal commandShortName As String, ByVal commandCaption As String, _
      ByVal commandTooltip As String, ByVal commandImage As Integer, ByVal bindings As Object)

      Dim command As EnvDTE.Command = Nothing

      Try

         command = m_dte.Commands.AddNamedCommand(m_addIn, commandShortName, commandCaption, _
            commandTooltip, True, commandImage)

         If Not (bindings Is Nothing) Then
            command.Bindings = bindings
         End If

      Catch ex As Exception
         ' This should not happen!
      End Try

   End Sub

   Private Sub InitializeAddIn()

      Dim standardWindowCommandBar As CommandBar
      Dim commandBars As CommandBars

      ' Retrieve the "Standard" toolbar
      commandBars = CType(m_dte.CommandBars, CommandBars)
      standardWindowCommandBar = commandBars.Item("Standard")

      ' Create the buttons from the commands
      AddCommandBarButton(standardWindowCommandBar, m_COMMAND1_NAME)
      AddCommandBarButton(standardWindowCommandBar, m_COMMAND2_NAME)

   End Sub

   Private Sub AddCommandBarButton(ByVal commandBar As CommandBar, ByVal commandShortName As String)

      Dim commandBarControl As CommandBarControl
      Dim commandBarButton As CommandBarButton
      Dim command As Command

      ' Retrieve the command created in the ext_cm_UISetup phase of the OnConnection method
      command = m_dte.Commands.Item(m_addIn.ProgID & "." & commandShortName)

      ' Add a control to the command bar
      commandBarControl = CType(command.AddControl(commandBar, commandBar.Controls.Count + 1), CommandBarControl)

      ' Cast it to CommandBarButton to set some properties
      commandBarButton = DirectCast(commandBarControl, CommandBarButton)
      commandBarButton.BeginGroup = True
      commandBarButton.Style = MsoButtonStyle.msoButtonIconAndCaption
      commandBarButton.Visible = True

   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_UserClosed, ext_DisconnectMode.ext_dm_HostShutdown

               If MessageBox.Show("Do you want to simulate the add-in not removing its buttons when unloading?", _
                  "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.Yes Then

                  ' Do nothing. The button will remain on the toolbar even when the add-in is unloaded

               Else

                  ' Recreate the commands to delete the commandbar buttons
                  RecreateCommands()

               End If

         End Select

      Catch objException As Exception
         MessageBox.Show(objException.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
      End Try

   End Sub

   Private Sub RecreateCommands()

      RecreateCommand(m_COMMAND1_NAME, m_COMMAND1_CAPTION, m_COMMAND1_TOOLTIP, m_COMMAND1_IMAGE)
      RecreateCommand(m_COMMAND2_NAME, m_COMMAND2_CAPTION, m_COMMAND2_TOOLTIP, m_COMMAND2_IMAGE)

   End Sub

   Private Sub RecreateCommand(ByVal commandShortName As String, ByVal commandCaption As String, _
      ByVal commandTooltip As String, ByVal commandImage As Integer)

      Dim existingCommand As Command = Nothing
      Dim bindings As Object
      Dim commandFullName As String

      commandFullName = m_addIn.ProgID & "." & commandShortName

      ' Try to get the command if it exists to get the current binding
      Try
         existingCommand = m_dte.Commands.Item(commandFullName)
      Catch
      End Try

      If existingCommand Is Nothing Then

         ' This should not happen!

      Else

         ' We must preserve the command bindings
         bindings = existingCommand.Bindings

         ' Remove the existing command
         existingCommand.Delete()

         ' Create it again
         CreateCommand(commandShortName, commandCaption, commandTooltip, commandImage, bindings)

      End If

   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 QueryStatus(ByVal CmdName As String, _
       ByVal NeededText As EnvDTE.vsCommandStatusTextWanted, _
       ByRef StatusOption As EnvDTE.vsCommandStatus, ByRef CommandText As Object) _
       Implements EnvDTE.IDTCommandTarget.QueryStatus

      Select Case CmdName

         Case m_addIn.ProgID & "." & m_COMMAND1_NAME

            StatusOption = vsCommandStatus.vsCommandStatusSupported Or _
                vsCommandStatus.vsCommandStatusEnabled

         Case m_addIn.ProgID & "." & m_COMMAND2_NAME

            StatusOption = vsCommandStatus.vsCommandStatusSupported Or _
                vsCommandStatus.vsCommandStatusEnabled

      End Select

   End Sub

   Public Sub Exec(ByVal CmdName As String, ByVal ExecuteOption As EnvDTE.vsCommandExecOption, _
      ByRef VariantIn As Object, ByRef VariantOut As Object, ByRef handled As Boolean) _
      Implements EnvDTE.IDTCommandTarget.Exec

      Select Case CmdName

         Case m_addIn.ProgID & "." & m_COMMAND1_NAME

            MessageBox.Show("MyCommand1 executed")

         Case m_addIn.ProgID & "." & m_COMMAND2_NAME

            MessageBox.Show("MyCommand2 executed")

      End Select

   End Sub

End Class
Language: C#   Copy Code Copy Code (IE only)
public class Connect : Extensibility.IDTExtensibility2, EnvDTE.IDTCommandTarget
{
   private const string m_COMMAND1_NAME = "MyCommand1";
   private const string m_COMMAND2_NAME = "MyCommand2";

   private const string m_COMMAND1_CAPTION = "My Command 1";
   private const string m_COMMAND2_CAPTION = "My Command 2";

   private const string m_COMMAND1_TOOLTIP = "Executes Command 1";
   private const string m_COMMAND2_TOOLTIP = "Executes Command 2";

   private const int m_COMMAND1_IMAGE = 59;
   private const int m_COMMAND2_IMAGE = 60;

   private EnvDTE.DTE m_dte;
   private EnvDTE.AddIn m_addIn;

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

         switch (connectMode)
         {
            case ext_ConnectMode.ext_cm_UISetup:

               // Create commands in the UI Setup phase. This phase is called only once when the add-in is deployed.
               CreateCommands();
               break;

            case ext_ConnectMode.ext_cm_Startup:

               // Do nothing yet, wait until the IDE is fully initialized (OnStartupComplete will be called)
               break;

            case ext_ConnectMode.ext_cm_AfterStartup:

               InitializeAddIn();
               break;
         }
      }
      catch (Exception objException)
      {
         MessageBox.Show(objException.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
   }

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

   private void CreateCommands()
   {
      CreateCommand(m_COMMAND1_NAME, m_COMMAND1_CAPTION, m_COMMAND1_TOOLTIP, m_COMMAND1_IMAGE, null);
      CreateCommand(m_COMMAND2_NAME, m_COMMAND2_CAPTION, m_COMMAND2_TOOLTIP, m_COMMAND2_IMAGE, null);
   }

   private void CreateCommand(string commandShortName, string commandCaption, string commandTooltip, int commandImage, object bindings)
   {
      EnvDTE.Command command = null;
      object[] contextUIGuids = new object[] { };

      try
      {
         command = m_dte.Commands.AddNamedCommand(m_addIn, commandShortName, commandCaption, commandTooltip, true,
            commandImage, ref contextUIGuids, (int)vsCommandStatus.vsCommandStatusSupported);

         if (bindings != null)
         {
            command.Bindings = bindings;
         }
      }
      catch (Exception ex)
      {
         // This should not happen!
      }
   }

   private void InitializeAddIn()
   {
      CommandBar standardWindowCommandBar = null;
      CommandBars commandBars = null;

      // Retrieve the "Standard" toolbar
      commandBars = (CommandBars)m_dte.CommandBars;
      standardWindowCommandBar = commandBars["Standard"];

      // Create the buttons from the commands
      AddCommandBarButton(standardWindowCommandBar, m_COMMAND1_NAME);
      AddCommandBarButton(standardWindowCommandBar, m_COMMAND2_NAME);
   }

   private void AddCommandBarButton(CommandBar commandBar, string commandShortName)
   {
      Command command = null;
      CommandBarControl commandBarControl = null;
      CommandBarButton commandBarButton = null;

      // Retrieve the command created in the ext_cm_UISetup phase of the OnConnection method
      command = m_dte.Commands.Item(m_addIn.ProgID + "." + commandShortName, -1);

      // Add a control to the command bar
      commandBarControl = (CommandBarControl)command.AddControl(commandBar, commandBar.Controls.Count + 1);

      // Cast it to CommandBarButton to set some properties
      commandBarButton = (CommandBarButton)commandBarControl;
      commandBarButton.BeginGroup = true;
      commandBarButton.Style = MsoButtonStyle.msoButtonIconAndCaption;
      commandBarButton.Visible = true;
   }

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

               if (MessageBox.Show("Do you want to simulate the add-in not removing its buttons when unloading?",
                  "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
               {
                  // Do nothing. The button will remain on the toolbar even when the add-in is unloaded
               }
               else
               {
                  // Recreate the commands to delete the commandbar buttons
                  RecreateCommands();
               }
               break;
         }
      }
      catch (Exception objException)
      {
         MessageBox.Show(objException.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
      }

   }

   private void RecreateCommands()
   {
      RecreateCommand(m_COMMAND1_NAME, m_COMMAND1_CAPTION, m_COMMAND1_TOOLTIP, m_COMMAND1_IMAGE);
      RecreateCommand(m_COMMAND2_NAME, m_COMMAND2_CAPTION, m_COMMAND2_TOOLTIP, m_COMMAND2_IMAGE);
   }

   private void RecreateCommand(string commandShortName, string commandCaption, string commandTooltip, int commandImage)
   {
      Command existingCommand = null;
      object bindings = null;
      string commandFullName = null;

      commandFullName = m_addIn.ProgID + "." + commandShortName;

      // Try to get the command if it exists to get the current binding
      try
      {
         existingCommand = m_dte.Commands.Item(commandFullName, -1);
      }
      catch
      {
      }

      if (existingCommand == null)
      {
         // This should not happen!
      }
      else
      {
         // We must preserve the command bindings
         bindings = existingCommand.Bindings;

         // Remove the existing command
         existingCommand.Delete();

         // Create it again
         CreateCommand(commandShortName, commandCaption, commandTooltip, commandImage, bindings);
      }
   }

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

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

   public void QueryStatus(string CmdName, EnvDTE.vsCommandStatusTextWanted NeededText,
      ref EnvDTE.vsCommandStatus StatusOption, ref object CommandText)
   {
      if (CmdName == m_addIn.ProgID + "." + m_COMMAND1_NAME)
      {
         StatusOption = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
      }
      else if (CmdName == m_addIn.ProgID + "." + m_COMMAND2_NAME)
      {
         StatusOption = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
      }
   }

   public void Exec(string CmdName, EnvDTE.vsCommandExecOption ExecuteOption, ref object VariantIn,
      ref object VariantOut, ref bool handled)
   {
      if (CmdName == m_addIn.ProgID + "." + m_COMMAND1_NAME)
      {
         MessageBox.Show("MyCommand1 executed");
      }
      else if (CmdName == m_addIn.ProgID + "." + m_COMMAND2_NAME)
      {
         MessageBox.Show("MyCommand2 executed");
      }
   }
}

Related articles



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


Top