HOWTO: Capturing commands events from Visual Studio .NET add-ins

Author: Carlos J. Quintero (Microsoft MVP) Applies to: Microsoft Visual Studio .NET 2002
Date: August 2005   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 describes how to capture command events in the Visual Studio .NET IDE.

More Information

Visual Studio .NET exposes several events in its automation model (EnvDTE.DTE.Events property) for add-ins to be notified when some action happens (such as Build events, etc.), but sometimes you need to be notified before or after a Visual Studio .NET command is executed.

Commands in Visual Studio .NET are identified by a Guid and an ID, and the EnvDTE.Command class has properties to get those values. The Guid identifies the "owner" of the command (a package or subsystem of the IDE), while the ID allows the owner to distinguish its own commands. For commands created by add-ins, the Guid is always the value of the constant EnvDTE.Constants.vsAddInCmdGroup and the ID is increased each time that an add-in calls the AddNamedCommand method. Since the Guid and ID pair is not very user-friendly, commands may have also a name, such as "File.Exit". So, the EnvDTE.Commands collection allows you to retrieve a command through its method Commands.Item(ByVal index As Object, Optional ByVal ID As Integer = -1) by three ways:

  • Passing a numerical index in the first parameter and omitting the second (or passing the value -1)
  • Passing the full name of the command in the first parameter and omitting the second (or passing the value -1)
  • Passing the Guid of the command in the first parameter and the ID in the second.

You can trap command events using the EnvDTE.CommandEvents class and its events BeforeExecute and AfterExecute. The EnvDTE.DTE class provides an Events property which returns an EnvDTE.Events class (interface), which in turn has a CommandEvents property that returns an instance of the EnvDTE.CommandEvents class. The following code shows how an add-in can handle the BeforeExecute event of all commands of the IDE:

Private m_objDTE As EnvDTE.DTE
Private WithEvents m_objCommandEvents As CommandEvents

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
   m_objDTE = DirectCast(application, EnvDTE.DTE)
   m_objCommandEvents = m_objDTE.Events.CommandEvents
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, _
   ByRef custom As System.Array) _
   Implements Extensibility.IDTExtensibility2.OnDisconnection
   m_objCommandEvents = Nothing

End Sub
Private Sub m_objCommandEvents_BeforeExecute(ByVal Guid As String, ByVal ID As Integer, _
   ByVal CustomIn As Object, ByVal CustomOut As Object, ByRef CancelDefault As Boolean) _
   Handles m_objCommandEvents.BeforeExecute
     Dim objCommand As EnvDTE.Command
     Dim sCommandName As String
     objCommand = m_objDTE.Commands.Item(Guid, ID)
     If Not (objCommand Is Nothing) Then
         sCommandName = objCommand.Name
         If sCommandName = "" Then
             sCommandName = "<unnamed>"
         End If
         MessageBox.Show("Before executing command with Guid=" & Guid & " and ID=" & ID _
            & " named " & sCommandName)
     End If
End Sub

Notice that the Events.CommandEvents property has two optional parameters, Guid and ID (omitted in the example above) that allow you to capture the events of only the command whose Guid and ID you pass, rather than capturing the events of all commands. You should use this filtering capability whenever possible, specially because of an unfortunate bug of Visual Studio .NET 2002 / 2003 (fixed in VS 2005) that causes that only the last add-in that sets a command event handler receives its events. If all add-ins capture all command events without previous filtering, only the last one loaded would receive the events, but if each add-in uses filtering, if they are not interested in the same command no conflict would happen.

So, if you are interested in trapping all command events related to a certain kind of operation, such as a build or a source code control operation, first you need to get a list of the commands that can be invoked for that operation. The same operation can be executed through different commands (or not) depending on if they are used to created context menus, or if they are on a toolbar or main menu. To get this list:

  • You can guess some named commands clicking the "Tools", "Customize..." menu, "Keyboard" button and typing in the textbox "Show commands containing" some word related to the operation, such as "Build" (without quotes) for build operations or "Check" for source code control operations. The list will show the commands that match that word.
  • To guess if there are unnamed commands related to the operation, you need to set a global command event handler as in the example above and try to execute the operation using as many user interface elements (buttons, context menus, dialogs, etc.) as possible. In the event handler you can guess if the command has a name or not as shown in the example.

Once you have the Guids and IDs of all the commands of your interest, you would set command event handlers for them.

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