Creating Add-Ins


Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 4.  Macros and Visual Studio Extensibility

Creating Add-Ins

As you might have gathered by now, a lot of information is available in the general and project-neutral extensibility models. What are all of these automation objects for? They are there to help language extenderspeople like youcreate Add-Ins among other things.

Because the object model is so vast, you will have to rely heavily on the help files to begin scratching the surface of extensibility; more than likely, several tool vendors will develop in-house expertise, and a developer from one of these shops will write an entire book on this subject.

In this section we will look at the necessary, basic considerations for creating VS .NET add-ins and will create some simple add-ins. Of course any add-in could potentially be a complex application by itselfconsider Visual SourceSafe.

Creating an Add-In Project

Macros extend the IDE by providing automated solutions that can be run in the IDE. The biggest difference between an add-in and a macro is that add-ins are compiled into a separate .DLL and macros are not. The tasks you automate with macros or add-ins can be identical. The reason you would choose an add-in over a macro is one of distribution. If you write an extension that you want to sell, and you want to prevent customers from pillaging your source code, you will create an add-in project; otherwise a macro project will suffice.

The essential ingredients necessary to create add-ins are a DLL or class library, a project, and a class that implements the IDTExtensibility2 interface. There are other interfaces you can implement that will make your add-in more useful, but IDTExtensibility2 is the primary interface that an add-in must implement.

Fortunately, there is an add-in wizard that automates the basic steps necessary to create an add-in. Granted, the add-in created by the wizard is a do-nothing add-in, but it will get you jump-started. Follow the numbered steps listed following to create an add-in project that will compile and run.

  1. Start a new project by choosing File, New Project, Other Projects Extensibility Projects, Visual Studio.Net Add-In. Click OK. Click Next at the Welcome screen.

  2. On Page 1 of 6, select Create an Add-In Using Visual Basic. Click Next.

  3. Page 2 of 6 uses the defaults, which define the Macros IDE and Visual Studio .NET as hosts for the add-in. Click Next.

  4. On Page 3 of 6 name the add-in MyAddin and enter a description; for example, My First Add-In. Click Next.

  5. On Page 4 of 6 we want to indicate that the add-in can be invoked from the Tools menu, the add-in should load when the host loads, and the add-in should be available to all users. (Check all check boxes except My Add-In Will Never Put Up a Modal UI.) Click Next.

  6. On Page 5 of 6, check the check box if you want to add About box information. (If you do, modify the About box information accordingly .) Click Next.

  7. On Page 6 of 6, review the Summary information, and if it is correct, click Finish.

The wizard will create an add-in projectin our example named MyAddInand a Windows Installer project named MyAddInSetup (using the AddInName Setup naming convention). The add-in project will have references, an assemblyinfo.vb module, and a module named connect.vb that contains the add-in class, Connect. Connect implements the IDTExtensibility2 interface and, because we indicated that we wanted integration with the IDE's Tools menu, the IDTCommandTarget interface.

The add-in will compile and run as is, but it performs no real task at the present time. If you run the add-in from the IDE, it will compile and package the setup file MyAddInSetup.msi. (Creating the setup file takes a while, so be patient.) When the add-in project runs, it starts a new instance of the IDE within our casethe add-in added as a menu operation in the Tools menu. Go ahead and try it.


The add-in menu item may stop appearing in the Tools menu after you test the add-in to debug it. If this happens, close all instances of the IDE and double-click ReCreateCommands.reg in your Add-In directory. This step reloads the registry settings and the menu should reappear when you restart VS .NET.

Our vanilla add-in implements the IDTExtensibility2 and IDTCommandTarget interfaces resulting in the code in Listing 4.9.

Listing 4.9 The add-in module created by the Visual Studio .NET Add-In Wizard (repaginated to fit this page)
[View full width]
  1:  Imports Microsoft.Office.Core  2:  Imports EnvDTE  3:  Imports System.Runtime.InteropServices  4:   5:  #Region " Read me for Add-in installation and setup information. "  6:  ' When run, the Add-in wizard prepared the registry for the Add-in.  7:  ' At a later time, if the Add-in becomes unavailable for reasons such as:  8:  '   1) You moved this project to a computer other than which it was originally graphics/ccc.gif created on.  9:  '   2) You chose 'Yes' when presented with a message asking if you wish to remove the graphics/ccc.gif Add-in.  10:  '   3) Registry corruption.  11:  ' you will need to re-register the Add-in by building the MyAddinSetup project  12:  ' by right clicking the project in the Solution Explorer, then choosing install.  13:  #End Region  14:   15:  <GuidAttribute("F61C62A7-A0F2-4660-87C7-67BCE5C0BF96"), _  16:  ProgIdAttribute("MyAddin.Connect")> _  17:  Public Class Connect  18:   19:  Implements IDTExtensibility2  20:  Implements IDTCommandTarget  21:   22:  Dim applicationObject As EnvDTE.DTE  23:  Dim addInInstance As EnvDTE.AddIn  24:   25:  Public Sub OnBeginShutdown(ByRef custom() As Object) _  26:  Implements IDTExtensibility2.OnBeginShutdown  27:   28:  End Sub  29:   30:  Public Sub OnAddInsUpdate(ByRef custom() As Object) _  31:  Implements IDTExtensibility2.OnAddInsUpdate  32:   33:  End Sub  34:   35:  Public Sub OnStartupComplete(ByRef custom() As Object) _  36:  Implements IDTExtensibility2.OnStartupComplete  37:   38:  End Sub  39:   40:  Public Sub OnDisconnection(_  41:  ByVal RemoveMode As ext_DisconnectMode, _  42:  ByRef custom() As Object) _  43:  Implements IDTExtensibility2.OnDisconnection  44:   45:  End Sub  46:   47:  Public Sub OnConnection(ByVal application As Object, _  48:  ByVal connectMode As ext_ConnectMode, _  49:  ByVal addInInst As Object, ByRef custom() As Object) _  50:  Implements IDTExtensibility2.OnConnection  51:   52:  applicationObject = CType(application, EnvDTE.DTE)  53:  addInInstance = CType(addInInst, EnvDTE.AddIn)  54:  If connectMode = ext_ConnectMode.ext_cm_UISetup Then  55:  Dim objAddIn As AddIn = CType(addInInst, AddIn)  56:  Dim CommandObj As Command  57:   58:  'IMPORTANT!  59:  'If your command no longer appears on the appropriate  60:  ' command bar, you add a new or modify an existing command,  61:  ' or if you would like to re-create the command, close  62:  ' all instances of Visual Studio .NET and double click  63:  ' the(file) 'ReCreateCommands.reg' in the folder  64:  ' holding the source code to your Add-in.  65:  'IMPORTANT!  66:  Try  67:  CommandObj = _  68:  applicationObject.Commands.AddNamedCommand(objAddIn, _  69:  "MyAddin", "MyAddin", "Executes the command for MyAddin", True, 59, graphics/ccc.gif Nothing, _  70:  1 + 2)  71:  '1+2 == vsCommandStatusSupported+vsCommandStatusEnabled  72:   73:  CommandObj.AddControl(_  74:  applicationObject.CommandBars.Item("Tools"))  75:  Catch e As System.Exception  76:  End Try  77:  End If  78:  End Sub  79:   80:  Public Sub Exec(ByVal cmdName As String, _  81:  ByVal executeOption As vsCommandExecOption, _  82:  ByRef varIn As Object, ByRef varOut As Object, _  83:  ByRef handled As Boolean) Implements IDTCommandTarget.Exec  84:   85:  handled = False  86:  If (executeOption = _  87:  vsCommandExecOption.vsCommandExecOptionDoDefault) Then  88:   89:  If cmdName = "MyAddin.Connect.MyAddin" Then  90:  handled = True  91:  Exit Sub  92:  End If  93:  End If  94:  End Sub  95:   96:  Public Sub QueryStatus(ByVal cmdName As String, _  97:  ByVal neededText As vsCommandStatusTextWanted, _  98:  ByRef statusOption As vsCommandStatus, _  99:  ByRef commandText As Object) _  100:  Implements IDTCommandTarget.QueryStatus  101:   102:  If neededText = _  103:  EnvDTE.vsCommandStatusTextWanted.vsCommandStatusTextWantedNone _  104:  Then  105:   106:  If cmdName = "MyAddin.Connect.MyAddin" Then  107:  statusOption = _  108:  CType(vsCommandStatus.vsCommandStatusEnabled + _  109:  vsCommandStatus.vsCommandStatusSupported, _  110:  vsCommandStatus)  111:  Else  112:  statusOption = vsCommandStatus.vsCommandStatusUnsupported  113:  End If  114:  End If  115:  End Sub  116:  End Class 


Keep in mind that GUIDs will always be unique. Thus, the value of GuidAttribute on line 15 on your computer will vary.

As is immediately apparent from the listing, a few of these procedures are a little murky and all of the statements are long, but when you evaluate each one they are pretty straightforward. All of the code implements the two interfaces previously mentioned. The IDTExtensibility2 methods are implemented as empty methods, and the IDTCommandTarget methods have the necessary code needed to place a menu item in the Tools menu and respond when it is clicked. The next two subsections discuss the IDTCommandTarget and IDTExtensibility2 interfaces.

Implementing the IDTExtensibility2 Interface

IDTExtensibility2 is the basic add-in interface. There are five methods that you must implementalthough they can be empty methodsto define an add-in. These are described in Table 4.1.

Table 4.1. IDTExtensibility2 Interface Methods That Have To Be Implemented To Create an Add-In
Interface Method Description
OnAddInsUpdate Called when the Add-In Manager list changes
OnBeginShutDown Called when the IDE is shut down before OnDisconnection
OnConnection Called when an add-in is loaded
OnDisconnection Called when an add-in is unloaded
OnStartupComplete Called when the IDE has completed startup

From the descriptions, it is apparent that these methods are provided to allow you an opportunity to initialize and release resources at opportunistic points during your add-in's lifecycle. If you need some processing to occur before your add-in is run, implement OnConnection or OnStartupComplete. If you need processing to occur before your add-in or the IDE is shut down, implement OnDisconnection or OnBeginShutDown. Otherwise, leave the implementation of these interface methods blank, as demonstrated in Listing 4.9.

OnConnection is implemented to create the (named) Command object on lines 67 through 70, allowing the command to be invoked from the Command window, and adds the command to the Tools menuin the example, on lines 73 and 74.

Implementing the IDTCommandTarget Interface

The IDTCommandTarget interface implements the menu item for the add-in. The Exec method is called when the menu item is clicked and QueryStatus returns the status of the command specified in the arguments, indicating whether the command is available.

The code implementing the IDTCommandTarget is very specific, suggesting that it is easiest if you let the wizard generate the add-in initially. You could, however, use the wizard-generated code as a template for creating add-ins manually.

Registering Add-Ins

To register your VB .NET add-in, open a command prompt window and run the Regasm.exe application with the / codebase switch, passing it the name of your add-in DLL.

Regasm.exe is installed in the \Winnt\Microsoft.Net\Framework\ directory by default. Because we indicated that the add-in should be available to all users in step 5 in the section "Creating an Add-In Project," the add-in keys are added to the registry in HKEY_LOCAL_MACHINE\ SOFTWARE\ Microsoft\ VisualStudio\ 7.0\ AddIns\ MyAddIn. Connect.

The help topic ms-help://MS.VSCC/MS.MSDNVS/vsintro7/html/vxconAdd-InRegistration.htm titled "Add-In Registration" provides additional details on the keys that are added to the registry and appropriate values for those keys.

Here is an example demonstrating the proper registration of an add-in named MyAddIn.dll. (The example assumes that the current directory contains the add-in .DLL.)

 C:\ WINNT\ Microsoft.NET\ Framework\ v1.0.3215\ Regasm.exe myaddin.dll /codebase 

(The specific location of Regasm.exe may vary by computer and version of the .NET Framework.)


The best way to ensure that add-ins are properly registered and installed is to run the Add-In setup project when you are finished testing and debugging your add-in.

Regasm.exe assigns a .NET assembly a ClassID and adds a registry entry. The reason for all of this has to do with the Add-Ins Manager. The Add-Ins Manager is an Automation server. Yes, that's rightold COM technology still lingers in a few places.

The /codebase switch puts the full path to the Add-In into the registry.

Implementing the IDTToolsOptionsPage Interface

Implement the IDTToolsOptionsPage interface when you want to define a custom Tools Options Page for your add-in. You will have to implement the following interface methods to create the Options page: GetProperties, OnAfterCreated, OnCancel, OnHelp, and OnOK.

GetProperties needs to return a DTE.Properties collection containing all of the properties on the custom page. OnAfterCreated is called after the page is created. OnCancel, OnHelp, and OnOK are called in response to the user clicking those buttons , respectively.

Adding Behaviors to the Add-In

Now that we have created an empty add-in, we need to add some code to define a behavior. Our add-in behavior is invoked when the user clicks the Tools, MyAddIn menu item or when the user types MyAddin.Connect.MyAddIn in the Command window. Either of these methods yields control to the Exec method we implemented, hence our new behavior needs to be initiated in the Exec method.

Building on the behavior we contrived in an earlier section, we can insert code to run the copyright-insertion behavior from the Add-In menu. That is, our add-in will insert a copyright tag.

To modify the MyAddIn add-in to use the InsertCopyrightTag macro, we will need to ensure that the macro project containing that macro is loaded. Alternatively, we can copy the macro code into the MyAddIn project and avoid a dependency on the macro. Listing 4.10 demonstrates the former approach; it is assumed that the macro project is loaded and we will execute the macro to actually perform the add-in behavior for us.

Listing 4.10 Excerpt from Listing 4.9 showing the modifications to the add-in created by the wizard.
  80:  Public Sub Exec(ByVal cmdName As String, _  81:  ByVal executeOption As vsCommandExecOption, _  82:  ByRef varIn As Object, ByRef varOut As Object, _  83:  ByRef handled As Boolean) Implements IDTCommandTarget.Exec  84:   85:  handled = False  86:  If (executeOption = _  87:  vsCommandExecOption.vsCommandExecOptionDoDefault) Then  88:   89:  If cmdName = "MyAddIn.Connect.MyAddIn" Then  90:   91:  DoExecute()  92:   93:  handled = True  94:  Exit Sub  95:  End If  96:  End If  97:  End Sub ...  120:  Private Sub DoExecute()  121:  ' Need to ensure the Copyright macro project is defined.  122:  applicationObject.ExecuteCommand(_  123:  "Macros.MyMacros.Copyright.InsertCopyrightTag")  124:  End Sub 

Listing 4.10 is excerpted from Listing 4.9. Replace the Exec implementation and add the DoExecute method to complete the add-in MyAddIn. Line 91 invokes the DoExecute behavior when the Exec method is called. DoExecute is implemented in terms of the InsertCopyrightTag defined earlier in this chapter. Line 122 of DoExecute uses the EnvDTE.DTE.ExecuteCommand method to run the macro. ExecuteCommand works as if you had typed the statement in the Command window.

Because macros are implemented with VB .NET, you could literally cut and paste the macro code into the add-in as an alternative to invoking the macro.


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
Year: 2001
Pages: 222 © 2008-2017.
If you may any questions please contact us: