Logo
HOWTO: Migrate macro methods to a Visual Studio add-in

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

Visual Studio 2012 removed support for macros. This article shows how to migrate your existing macro methods of Visual Studio 2010 (or lower version) to a Visual Studio 2012 add-in.

More Information

A macro method is a subroutine with no parameters that you recorded or created by hand in the macros editor ("Tools", "Macros", "Macros IDE..." menu). Macro methods assume the existence of a global variable named DTE that represents the Visual Studio IDE. For example:

Language: VB.NET   Copy Code Copy Code (IE only)
Public Module Module1

    Public Sub MyMacro1()

        System.Windows.Forms.MessageBox.Show(DTE.Version)

    End Sub

    Public Sub MyMacro2()

        System.Windows.Forms.MessageBox.Show(DTE.Edition)

    End Sub

End Module

Each macro method has an associated Visual Studio command (with the "Macros." prefix) and can be executed through several mechanisms, for example:

  • Using the Macro Explorer ("Tools", "Macros", "Macro Explorer" menu), locating a macro and executing the "Run" context menu entry.
  • Typing the full name of the command (ex: Macros.MyMacros.Module1.MyMacro1) in the Command window ("View", "Other Windows", "Command Window").
  • Associating a keyboard shortcut (binding) to the macro command ("Tools", "Customize..." menu, "Keyboard" button, "Show commands containing" list).

An add-in is a compiled assembly (dll), created from a class library project with a wizard that adds the references to the automation model (EnvDTE, EnvDTE80) and creates a "Connect" class that implements a couple of interfaces (IDTExtensibility2, IDTCommandTarget) to communicate with Visual Studio. The add-in assembly needs to be registered as an add-in for Visual Studio using an .AddIn file (XML format internally) generated by the wizard that is placed in one of the well-known folders where Visual Studio looks for .AddIn files. See the article INFO: Default .AddIn file locations for Visual Studio add-ins. The .AddIn file describes the add-in name, assembly, location and Visual Studio version(s) that it supports.

The Connect class has a class variable of EnvDTE.DTE (or EnvDTE80.DTE2) type (received in the OnConnection method). This Connect class can create commands too (with the name of the add-in as prefix), so you can migrate your macro methods to an add-in. While you lose the Macro Explorer, and add-in can create buttons associated to commands on a toolbar. See the article HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in.

To create an add-in that contains your macro methods follow these steps:

  • Launch Visual Studio 2012 or higher.
  • Click the "FILE", "New", "Project..." menu.
  • Go to the node "Templates", "Other Project Types", "Extensibility".
  • Select the "Visual Studio Add-in" template and give the add-in a name, for example, "MyMacrosAddin".
  • In the page 1 of 6, click the option "Create an Add-in using Visual Basic".
  • In the page 4 of 6, click the options "Yes, create a 'Tools' menu item..." and "I would like my Add-in to load when the host application starts". Note: we will not create a "Tools" menu item in the code sample below, but that option is needed for the add-in wizard to make the Connect class to implement the IDTCommandTarget interface (required for commands).
  • Replace the code of the Connect.vb file by the code below.

Notes:

  • By default, add-in commands have a name with the prefix "<AddInName>.Connect" ("MyMacrosAddin.Connect" in our example). If you want to get rid of the ".Connect" part, see the article HOWTO: Create command names without ".Connect" in Visual Studio add-ins.

  • Notice that the Connect class of the add-in implements two interfaces:

    • IDTExtensibility2: this interface provides methods to be called by Visual Studio when the add-in is loaded (OnConnection, OnStartupComplete), unloaded (OnBeginShutdown, OnDisconnection), etc.

    • IDTCommandTarget: this interface provides methods to be called by Visual Studio when it needs to know if a command should be enabled, and when a commands needs to be executed.  In both cases, the full name of the command (for example "MyMacrosAddin.Connect.<Command>") is passed as parameter.

  • The "application" parameter of the OnConnection method is converted to the EnvDTE80.DTE2 type and stored in a class variable named DTE, so that your macro methods don't need modification.

  • This sample stores macro names and methods (delegates) in a dictionary that must be initialized (the sample provides two macros named MyMacro1 and MyMacro2).

  • The add-in doesn't delete its commands when unloaded. Commands must be always created (the add-in ensures this each time is loaded), because otherwise the keyboard shortcut (binding) associated with the command would be lost. It's the uninstaller of the add-in who needs to delete its commands.

  • You will note while debugging the add-in that the commands need always to be created, because they don't exist. This is because if you go to the Project properties, "Debug" tab, "Start Options" tab, "Command line arguments" textbox, the /resetaddin <addin> flag is included, that causes the commands to be deleted before each debugging session. But this doesn't happen when not debugging.

  • An add-in can be loaded when Visual Studio is launched, or later by hand using the Add-in Manager ("Tools", "Add-in Manager" menu). Since commands are always created, if you invoke a command and the add-in was not loaded, it is loaded automatically.

The code of the Connect class is the following:

Language: VB.NET   Copy Code Copy Code (IE only)
Imports System
Imports System.Collections.Generic
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80

Public Class Connect
   Implements IDTExtensibility2
   Implements IDTCommandTarget

   Private DTE As EnvDTE80.DTE2
   Private AddIn As EnvDTE.AddIn

   Private Delegate Sub MacroMethod()

   Private MacroMethods As New Dictionary(Of String, MacroMethod)

   Public Sub OnConnection(ByVal application As Object, ByVal connectMode As ext_ConnectMode, _
      ByVal addInInst As Object, ByRef custom As Array) Implements IDTExtensibility2.OnConnection

      Me.DTE = CType(application, EnvDTE80.DTE2)
      Me.AddIn = CType(addInInst, EnvDTE.AddIn)

      Select Case connectMode

         Case ext_ConnectMode.ext_cm_AfterStartup

            ' The add-in has been loaded after Visual Studio was loaded. Since Visual Studio
            ' is fully initialized, we can initialize the add-in
            InitializeAddIn()

         Case ext_ConnectMode.ext_cm_Startup

            ' The add-in has been loaded with Visual Studio. Do nothing until Visual Studio 
            ' is fully initialized (the OnStartupComplete method will be called)

         Case ext_ConnectMode.ext_cm_UISetup

            ' Do nothing in this case

      End Select

   End Sub

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

      InitializeAddIn()

   End Sub

   Private Sub InitializeAddIn()

      DefineMacros()

      CreateMacroCommands()

   End Sub

   Private Sub DefineMacros()

      AddMacro("MyMacro1Command", AddressOf Me.MyMacro1)
      AddMacro("MyMacro2Command", AddressOf Me.MyMacro2)

   End Sub

   Private Sub AddMacro(macroName As String, macroMethod As MacroMethod)

      Me.MacroMethods.Add(Me.AddIn.ProgID & "." & macroName, macroMethod)

   End Sub

   Private Sub CreateMacroCommands()

      Dim command As Command
      Dim lastDotPosition As Integer
      Dim macroCommandShortName As String

      For Each macroCommandFullName As String In Me.MacroMethods.Keys

         ' Try to get if the command already exists
         command = Nothing
         Try
            command = Me.DTE.Commands.Item(macroCommandFullName)
         Catch
         End Try

         ' If the command doesn't exist, create it
         If command Is Nothing Then

            Try

               ' Get the short name of the command
               lastDotPosition = macroCommandFullName.LastIndexOf(".")
               macroCommandShortName = macroCommandFullName.Substring(lastDotPosition + 1)

               ' Create the command
               command = Me.DTE.Commands.AddNamedCommand(AddInInstance:=Me.AddIn, Name:=macroCommandShortName, _
                   ButtonText:=macroCommandShortName, Tooltip:=macroCommandShortName, MSOButton:=False)

            Catch ex As Exception

               System.Windows.Forms.MessageBox.Show(ex.ToString)

            End Try

         End If

      Next

   End Sub

   Public Sub OnDisconnection(ByVal disconnectMode As ext_DisconnectMode, ByRef custom As Array) _
      Implements IDTExtensibility2.OnDisconnection
   End Sub

   Public Sub OnAddInsUpdate(ByRef custom As Array) Implements IDTExtensibility2.OnAddInsUpdate
   End Sub

   Public Sub OnBeginShutdown(ByRef custom As Array) Implements IDTExtensibility2.OnBeginShutdown
   End Sub

   Public Sub QueryStatus(ByVal commandName As String, ByVal neededText As vsCommandStatusTextWanted, _
      ByRef status As vsCommandStatus, ByRef commandText As Object) Implements IDTCommandTarget.QueryStatus

      If neededText = vsCommandStatusTextWanted.vsCommandStatusTextWantedNone Then

         If Me.MacroMethods.ContainsKey(commandName) Then

            status = vsCommandStatus.vsCommandStatusEnabled Or vsCommandStatus.vsCommandStatusSupported

         Else
   
            status = vsCommandStatus.vsCommandStatusUnsupported

         End If

      End If

   End Sub

   Public Sub Exec(ByVal commandName As String, ByVal executeOption As vsCommandExecOption, ByRef varIn As Object, _
      ByRef varOut As Object, ByRef handled As Boolean) Implements IDTCommandTarget.Exec

      Dim macroMethod As MacroMethod

      handled = False

      If executeOption = vsCommandExecOption.vsCommandExecOptionDoDefault Then

         ' Get the delegate for this command
         If Me.MacroMethods.ContainsKey(commandName) Then

            macroMethod = Me.MacroMethods.Item(commandName)

            Try

               ' Execute the macro method
               macroMethod.Invoke()

               handled = True

            Catch ex As Exception

               System.Windows.Forms.MessageBox.Show(ex.ToString)

            End Try

         End If

      End If

   End Sub

   Public Sub MyMacro1()

      System.Windows.Forms.MessageBox.Show(DTE.Version)

   End Sub

   Public Sub MyMacro2()

      System.Windows.Forms.MessageBox.Show(DTE.Edition)

   End Sub

End Class

Related articles


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


Top