HOWTO: Controlling the state of command in a Visual Studio add-in

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


This article explains how to control the state (enabled, disabled, invisible) of an add-in command.

More Information

Commands (EnvDTE.Command class) of an add-in are not visible elements (buttons are), so they can only be in one of two states: enabled or disabled.

Buttons (CommandBarButton class) of an add-in are visible elements, that are created from commands using the Command.AddControl method. Therefore, a button can be in one of three states: enabled, disabled (but visible) or invisible. The state of a button is controlled through the state of the command that created it. A command can create buttons on several commandbars, such as on a toolbar and on a context menu, and the state of all those buttons are controlled by the command. That means that you don't have to use the CommandBarButton.Enabled property.

Note: buttons can be also checked/unchecked if they are intended to show a status rather than to perform an action when clicked. To see how to create such buttons see HOWTO: Create a checked/unchecked command in a Visual Studio add-in.

The state of a command is defined in the EnvDTE.vsCommandStatus enum, whose values must be combined as follows:

  • Command enabled: vsCommandStatus.vsCommandStatusSupported + vsCommandStatus.vsCommandStatusEnabled
  • Command disabled: vsCommandStatus.vsCommandStatusSupported
  • Command invisible: vsCommandStatus.vsCommandStatusSupported + vsCommandStatus.vsCommandStatusInvisible

The vsCommandStatus.vsCommandStatusLatched value is used to create checked/unchecked buttons (see article above), the value vsCommandStatus.vsCommandStatusNinched value is rarely used and the vsCommandStatus.vsCommandStatusUnsupported value is used to denote that the command does not belong to the add-in.

An add-in can be in one of two states:

  • Loaded: in this case, the state of a command is controlled exclusively through the QueryStatus method of the IDTCommandTarget interface that the Connect class of the add-in must implement. For the command name passed to the QueryStatus method, you must return in the statusOption output parameter the desired state (some of the vsCommandStatus combinations above). You can check some conditions before returning a state. For example, you may want a command enabled only if a solution is loaded in the IDE, and have it disabled or invisible otherwise. You can use the automation model to check conditions (DTE.Solution.IsOpen in this example).
  • Unloaded: in this case, since the add-in is not loaded, its IDTCommandTarget.QueryStatus method can not be called by Visual Studio to know the command state (it will be disabled by default). There are two possibilities here (see HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in):
    • The add-in uses a temporary user interface, that is, it creates the buttons when loaded and it destroys them when unloaded. In this case, there will not be buttons belonging to the add-in when it is unloaded and the default disabled state is fine in this case.
    • The add-in uses a permanent user interface, that is, the buttons are created once and for good, and appear in the IDE even if the add-in is not loaded (the add-in can be loaded when the user clicks on a button, without going through the Add-in Manager). In this case, the Commands.AddNamedCommand method used to create commands provides two parameters to specify the state of the buttons when the add-in is not loaded and therefore its QueryStatus method can not be called: ContextUIGUIDs and vsCommandDisabledFlagsValue.
      • The ContextUIGUIDs parameter allows you to specify an array of conditions in which the command should be enabled. Some of these conditions are declared in the EnvDTE80.ContextGuids class, for example vsContextGuidDebugging, vsContextGuidDesignMode, vsContextGuidSolutionExists, vsContextGuidSolutionHasMultipleProjects, vsContextGuidSolutionHasSingleProject, vsContextGuidNoSolution, etc.
      • The vsCommandDisabledFlagsValue parameter allows you to specify the state of the command if the condition of the ContextUIGUIDs parameter is not met. The state can be disabled or invisible, depending on your preference.

      For this kind of add-ins with a permanent user interface, since you will want the same state for a command independently of whether the add-in is loaded or not, you must ensure that the logic of your ContextUIGUIDs / vsCommandDisabledFlagsValue parameters in the AddNamedCommand call and the logic of your QueryStatus method are the same. For example, if you use the vsContextGuidSolutionExists condition for the ContextUIGUIDs parameter in the AddNamedCommand call, you should check the DTE.Solution.IsOpen method in your QueryStatus method, to return a consistent result. For some conditions (such as vsContextGuidSolutionExistsAndNotBuildingAndNotDebugging) it can be difficult to find a direct equivalent in the automation model to use in the QueryStatus method. In this case you can use the IVsMonitorSelection interface of the Visual Studio SDK to retrieve the if context Guid is active (see HOWTO: Use the IVsMonitorSelection interface from a Visual Studio add-in).

The code below shows how to create an add-in with three permanent buttons on the "Standard" toolbar: one of the is always enabled, and the others are enabled only if a solution is loaded in the IDE; otherwise one of the buttons is disabled and the other is invisible.

Unfortunately, there are a couple of bugs in the AddNamedCommand function and the code does not work as expected:

  • When the add-in is not loaded the ContextUIGUIDs parameter of the AddNamedCommand call is not honored (when the add-in is loaded it works fine because the QueryStatus method is used instead). You have to set the vsCommandDisabledFlagsValue to STATUS_ENABLED (rather than STATUS_DISABLED or STATUS_INVISIBLE) for it to work as expected, but that's against the MSDN documentation of the AddNamedCommand (and against the common sense).
  • Neither the AddNamedCommand nor the QueryStatus methods honor the invisible state: the button that must be invisible when a solution is not loaded remains disabled rather than invisible.
Imports System
Imports Microsoft.VisualStudio.CommandBars
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80
Public Class Connect
   Implements Extensibility.IDTExtensibility2
   Implements IDTCommandTarget
   Private Const STATUS_INVISIBLE As vsCommandStatus = _
      vsCommandStatus.vsCommandStatusSupported Or vsCommandStatus.vsCommandStatusInvisible
   Private Const STATUS_UNSUPPORTED As vsCommandStatus = vsCommandStatus.vsCommandStatusUnsupported
   Private Const STATUS_DISABLED As vsCommandStatus = vsCommandStatus.vsCommandStatusSupported
   Private Const STATUS_ENABLED As vsCommandStatus = _
      vsCommandStatus.vsCommandStatusSupported Or vsCommandStatus.vsCommandStatusEnabled
   Private Const NAME_INVISIBLE_WHEN_NO_SOLUTION_LOADED As String = "InvisibleWhenNoSolutionLoaded"
   Private Const NAME_DISABLED_WHEN_NO_SOLUTION_LOADED As String = "DisabledWhenNoSolutionLoaded"
   Private Const NAME_ALWAYS_ENABLED As String = "AlwaysEnabled"
   Private applicationObject As DTE
   Private addInInstance As AddIn
   Private myInvisibleWhenUnsupportedCommandBarControl As CommandBarControl
   Private myDisabledWhenUnsupportedCommandBarControl As CommandBarControl
   Private myAlwaysEnabledCommandBarControl As CommandBarControl
   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 standardCommandBar As CommandBar
      Dim commandBars As CommandBars
      Dim myCommand As Command = Nothing
      Dim commands As Commands
         applicationObject = CType(application, DTE)
         addInInstance = CType(addInInst, AddIn)
         Select Case connectMode
            Case ext_ConnectMode.ext_cm_UISetup
               commandBars = DirectCast(applicationObject.CommandBars, CommandBars)
               standardCommandBar = commandBars.Item("Standard")
               commands = applicationObject.Commands
               myCommand = commands.AddNamedCommand(addInInstance, _
                     59, New Object() {ContextGuids.vsContextGuidSolutionExists}, STATUS_INVISIBLE)
               myInvisibleWhenUnsupportedCommandBarControl = DirectCast(myCommand.AddControl( _
                  standardCommandBar, 1), CommandBarControl)
               myCommand = commands.AddNamedCommand(addInInstance, _
                  59, New Object() {ContextGuids.vsContextGuidSolutionExists}, STATUS_DISABLED)
               myDisabledWhenUnsupportedCommandBarControl = DirectCast(myCommand.AddControl( _
                 standardCommandBar, 1), CommandBarControl)
               myCommand = commands.AddNamedCommand(addInInstance, _
                  NAME_ALWAYS_ENABLED, NAME_ALWAYS_ENABLED, "", True, 59, Nothing, STATUS_ENABLED)
               myAlwaysEnabledCommandBarControl = DirectCast(myCommand.AddControl( _
                  standardCommandBar, 1), CommandBarControl)
         End Select
      Catch e As System.Exception
      End Try
   End Sub
   Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, _
      ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnDisconnection
   End Sub
   Public Sub OnStartupComplete(ByRef custom As System.Array) _
      Implements Extensibility.IDTExtensibility2.OnStartupComplete
   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.StartsWith(addInInstance.ProgID & ".") Then
            handled = True
            System.Windows.Forms.MessageBox.Show("Command " & cmdName & " executed.")
         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
         Select Case cmdName
            Case addInInstance.ProgID & "." & NAME_INVISIBLE_WHEN_NO_SOLUTION_LOADED
               If IsSolutionLoaded() Then
                  statusOption = STATUS_ENABLED
                  statusOption = STATUS_UNSUPPORTED
               End If
            Case addInInstance.ProgID & "." & NAME_DISABLED_WHEN_NO_SOLUTION_LOADED
               If IsSolutionLoaded() Then
                  statusOption = STATUS_ENABLED
                  statusOption = STATUS_DISABLED
               End If
            Case addInInstance.ProgID & "." & NAME_ALWAYS_ENABLED
               statusOption = STATUS_ENABLED
         End Select
      End If
   End Sub
   Private Function IsSolutionLoaded() As Boolean
      Dim result As Boolean = False
      If applicationObject.Solution.IsOpen Then
         result = True
      End If
      Return result
   End Function
End Class

Related articles

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