COM Add-ins: Part II Home
Experienced Programmer COM Add-ins | Visual Basic 6 | Office 2000
COM Add-ins: Part II

This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


VBtech

COM Add-ins: Part II

Coding, Debugging, Deployment, etc.

By Ken Getz

In Part I, I related a short history of the COM add-in, and demonstrated why the Office 2000 version is far superior to its Office 97 predecessor. I also introduced the COM add-in designer, and described why Visual Basic 6 makes a particularly good environment for developing COM add-ins.

In this installment, we'll delve into the coding requirements, including the necessary methods and event handlers. We'll also discuss debugging techniques and deployment issues, so by the end of the article you'll have all the information necessary to create your own COM add-ins.

Coding the Methods

Because all COM add-ins for Office 2000 must implement the IDTExtensibility2 interface, the COM add-in designer takes care of the details for you. In its class module, you supply code for any or all of the interface's five methods described in FIGURE 1. Depending on the needs of your add-in, you'll normally provide code for at least the OnConnection and OnDisconnection methods.

Method Description
OnConnection The host application calls this method in your add-in as the add-in is loaded. FIGURE 2 includes a list of the parameters the host application passes to your add-in.
OnDisconnection The host application calls this method as it's unloading. It passes to the method two parameters: RemoveMode, indicating why the add-in is being unloaded (one of ext_dm_HostShutdown or ext_dm_UserClosed), and the same Custom array passed to the OnConnection method (see FIGURE 2 for more information on this parameter).
OnStartupComplete The host application calls this method once it's completely loaded and ready to run (in any case, you're guaranteed this method is called after the OnConnection method). For example, you might want to use this method to load all the documents that were loaded in the last session. You could use the OnBeginShutDown method to save the list to the user's registry.
OnBeginShutDown The host application calls this method when it begins its shutdown (if the COM add-in is still loaded). You're guaranteed this method is called before the OnDisconnection method. You might use this method to save a list of all currently loaded documents to the registry, so you could load the list and the documents in the OnStartupComplete method the next time it's called.
OnAddinsUpdate The host application calls this method when the set COM add-ins loaded in the host application change. If a COM add-in is loaded or unloaded, the host will call this method. You might use this method to keep tabs on other add-ins, or track other add-ins on which your add-in has dependencies.

FIGURE 1: Methods required by the IDTExtensibility2 interface.

Argument Description
Application Reference to the host Application object. Generally, you'll want to cast this as a specific type of Application object within the OnConnection method.
ConnectMode One of the following Long values indicating how the add-in was loaded: ext_cm_AfterStartup or ext_cm_Startup.
AddInInst Office.COMAddIn object referring to the instance of the add-in that's currently running. You'll need to store this so later procedures can retrieve the ProgID of the running add-in; the host menu that calls your add-in will need this info. The provided template takes care of all this for you.
Custom An array of Variants that contains information provided by the host application. For Office applications, the array will only contain a single interesting value. The first element in the array (item 0) will contain information about how the host was started: from the user interface (1), by embedding a document in another document (2), or through Automation (3).

FIGURE 2: Parameters sent to the OnConnection method.

Normally, when you implement an interface in VB, you must supply code (at least a comment) for each of the members of the base interface. In this case, because the designer implements the interface for you, and you're simply handling events passed through from the designer into your code, all you need to do is supply code for the methods that are necessary to your add-in. In both example add-ins, you only need to supply code for the OnConnection and OnDisconnection methods. (Although these aren't actually event procedures, they look and act as if they are. Therefore, these implementation-specific methods will be referred to as event procedures here.)

Adding Code to Event Procedures

In the OnConnection event procedure, you'll usually want to accomplish specific goals. You'll need to:

  • Store the Application object passed into the method into some sort of global variable, so you can refer to it later.
  • Store the AppInstance object passed into the method. You'll need its ProgID property later on, if you're attaching your add-in to a CommandBar button.
  • Set up some sort of event hook. Whether you want your add-in to be loaded because the user selects a menu item, or in reaction to some event (perhaps a piece of mail arriving in Outlook), you'll need to hook up the event here, so your code can run later in reaction to the event.

The sample project, InsertName.vbp, includes code that takes care of all these things. First, it sets up a WithEvents Office.CommandBarButton variable (the WithEvents keyword is required so you can react to the Click event of this object from within your add-in):

Private WithEvents mcbb As Office.CommandBarButton

Then, in the OnConnection event procedure, InsertName.vbp hooks up all the necessary variables, and calls the code that creates the CommandBar button, as shown in FIGURE 3.

Private Sub AddInInstance_OnConnection( _
   ByVal Application As Object,
ByVal ConnectMode As _
  AddInDesignerObjects.ext_ConnectMode, _
   ByVal AddInInst As Object, custom()As Variant)
      ' Save reference to host. 
   Set pobjHost = Application
   Set pobjCommandBarHost = Application
   Set pobjAddIn = AddInInst
   ' Set up module-level CommandBar item. 
   Set mcbb = AddToCommandBar()
End Sub

FIGURE 3: Creating the CommandBar button.

Why does the code store the passed-in Application object into two separate project-global variables (both defined by the project template in the basSharedCode module)? To be as generic as possible, the template includes code that allows you to work with the host's Application object (if you want to manipulate objects within the application, you'll need this reference), and code that allows you to manipulate CommandBars within the host. For most applications, the top-level Application object provides both these capabilities. In Outlook, however, the Application object doesn't supply any means of getting to the CommandBars collection, and there's no reason why any specific VBA-host application should either. (In Outlook, you can use the Application.ActiveExplorer as one way to work with CommandBar objects. Other applications may provide a different object altogether.) In an Outlook add-in, for example, you might modify the code in FIGURE 3 to store the two objects, as shown in FIGURE 4.

' Code from basSharedCode: For all apps but Outlook, the
' Application object hosts the CommandBars collection. In
' Outlook, the Explorer (or Inspector) object hosts the
' CommandBars. So, for most applications, pobjHost and 
' pobjCommandBarHost will refer to the same object, the 
' Application object. 
Public pobjCommandBarHost As Object
Public pobjHost As Object
Public pobjAddIn As Object
  ' Code from the application-specific designer module. 
Set pobjHost = Application
Set pobjCommandBarHost = Application.ActiveExplorer
Set pobjAddIn = AddInInst

FIGURE 4: Modifying the code for Outlook.

This is exactly what the Outlook add-in in the sample project does. As mentioned earlier, you need to store the AddInInst parameter's value so you can retrieve the ProgID property of the object later, if you connect a CommandBarButton object to load your add-in. If you choose some other mechanism to load your add-in (other than the CommandBar button used here), you need to replace the call to the AddToCommandBar method with one of your own design. (The AddToCommandBar method appears in the shared module in the template basSharedCode.)

In the OnDisconnection event procedure, your code needs to determine why your add-in has been disconnected. (If the application is shutting down, there's no need to remove your CommandBar item. If the user has unselected your add-in from the list of available add-ins, however, your menu item has to go.) In the sample project, all the add-ins call the same code, as shown in FIGURE 5.

Private Sub AddInInstance_OnDisconnection(ByVal _
  RemoveMode As AddInDesignerObjects.ext_DisconnectMode, _
  custom()As Variant)
     If RemoveMode = ext_dm_UserClosed Then
     Call DeleteFromCommandBar()
   End If
End Sub

FIGURE 5: OnDisconnection event procedure.

In this case, if the add-in is being disconnected because the user chose to remove the add-in, the code calls the shared DeleteFromCommandBar procedure (see basSharedCode in the sample project). The sample project is available for download; see end of article for details.

The AddToCommandBar method (in basSharedCode in the template project) provides simple brute force code to add a CommandBarButton object to an Office command bar. The interesting part comes after adding the control: in order for the menu item to load the COM add-in for you, it must know the ProgID for the add-in itself. If the OnAction property of a CommandBarButton object includes "!<ProgID>", where ProgID is the ProgID for a specific add-in, clicking the menu item will cause the host application to load the add-in (if you've selected the Load on demand startup option) and call the Click event procedure for the menu item as well. In addition, Microsoft Word won't take any action unless you've also set the Tag property of the menu item. To make the code generic for all hosts, it sets the Tag property no matter which host is active. The DeleteFromCommandBar procedure simply removes the CommandBar button from its parent command bar. Listing One includes both of these procedures, provided as starting points for your own add-ins in the included project template.

What do you do when the user clicks the add-in's menu item within the host? That's up to your specific needs. The InsertName sample add-in loads a form, and waits for the form to be dismissed. If the form wasn't cancelled (that is, the user selected the OK button), the code for each separate host reacts to the data entered into the form, provided as a property that returns an array of strings, appropriately for the host application. The Word add-in creates paragraphs containing the formatted information. The Excel add-in inserts the data into a vertical group of cells starting at the current location. The Outlook add-in creates a new contact, containing the data entered into the form. In each case, the add-in's code uses objects provided by the host application's Application object to do its work. FIGURE 6 shows the code from the Word add-in.

Private Sub mcbb_Click( _
   ByVal cbb As Office.CommandBarButton, _
  CancelDefault As Boolean)
     Dim wrdApp As Word.Application
   Dim strOut As String
   Dim strInfo()As String
   frmInsertAddress.Show vbModal
   If Not frmInsertAddress.Cancelled Then
     ' Cast global objHost as Word.Application object. 
     Set wrdApp = pobjHost
    astrInfo = frmInsertAddress.AddressInfo
    strOut = astrInfo(dfFirstName) &" " & _
      astrInfo(dfLastName) & vbCrLf &_
      astrInfo(dfAddress) & vbCrLf & _
      astrInfo(dfCity) & " " & _
      astrInfo(dfState) & " "& _
      astrInfo(dfZip) & vbCrLf
     With wrdApp.Selection
      .Range.InsertAfter strOut
      .MoveEnd wdParagraph, 4
      .Collapse wdCollapseEnd
     End With
   End If
   Set frmInsertAddress = Nothing
End Sub

FIGURE 6: Each of the sample add-ins use code like this to work with the supplied data.

Determining the Host

Sometimes, you'll need to alter behavior of a common component in an add-in project based on the current host application. Because your add-in always receives a reference to the host application (and the template stores this information in a project-global variable of type Object), your code can use the If TypeOf construct to determine the exact object type. For example, the InsertName sample application's form, frmInsertAddress, displays an e-mail textbox if the host application is Outlook, and hides the control otherwise. You could use code in the form to make the decision, like this:

blnOutlook = False
If TypeOf pobjHost Is Outlook.Application Then
  blnOutlook = True
End If

All of the Office applications' Application objects also supply a Name property, so you could modify the code to use that property:

blnOutlook = (pobjHost.Name = "Outlook") 

You can't count on all applications that host COM add-ins to provide this property, but all the Office applications do. If you check, you'll find that all the Office 2000 applications fill their Name properties with "Microsoft Excel," "Microsoft Word," and so on, except Outlook, which returns simply "Outlook." (Apparently, all the Office applications besides Outlook have Microsoft as part of their registered trademarks. So much for product consistency.)

Debugging COM Add-ins

Unless you're extremely lucky, you'll need to spend some time single-stepping through your add-in's code to ensure that it works correctly. Fortunately, debugging ActiveX components with VB is straightforward. You can simply run the project from within the VB environment, set a breakpoint at the desired spot, and run the add-in from within the host environment. When you hit the breakpoint, set the focus back to the VB IDE, and debug away. However, there are a few things you should consider:

  • The first time you run the add-in this way, VB displays a dialog box asking how you want to start the add-in. Accept the default option, Wait for components to be created.
  • If your add-in displays a form, debugging can get complicated. If you set your breakpoint before the add-in attempts to display the form, you should be able to switch to VB and continue debugging. If you set a breakpoint after the form displays, or if you don't set a breakpoint at all, it's likely you'll never see the form, and VB will appear to hang. To work around this, switch back to the VB IDE, and press [Ctrl]k to drop into Break mode. You'll find yourself at a breakpoint on the line of code that displays the form. You should then be able to continue debugging normally.
  • As when running a COM add-in in any host, you'll need to make sure you completely restart the host application before attempting to debug the application. Otherwise, your host app will either not see the add-in, or will use the last version you had compiled.

Deploying Your Add-ins

How do the various host applications know what add-ins are available, and how to use them? Each product looks in the registry at HKEY_CURRENT_USER\Software\Microsoft\Office\<AppName>\Addins, as shown in FIGURE 7, for information about available add-ins. Of course, VB places these registry settings into your own registry when you compile the add-in on your development machine. You'll need some way to get the add-in onto users' machines, registered, and the correct values into the registry.


FIGURE 7: After installing an add-in, the registry includes information about the various properties you set in the add-in designer.

Just as with any other ActiveX component, COM add-ins include the DLLRegisterServer entry point (and the corresponding DLLUnregisterServer entry point for removing the registry entries). This procedure includes all the information it needs so that a call to REGSVR32 (for DLLs), or running the component from the command line (for EXEs), will set everything up correctly. Therefore, you can use any tool that creates distribution media, as long as it understands how to install ActiveX components. The Package and Deployment Wizard that comes with VB should work fine.

Conclusion

Rather than learning add-in technologies for each individual product for which you need to create add-ins, use COM add-ins to create cross-product tools for any or all of the Microsoft Office 2000 applications. Creating the add-ins requires VB, or Office 2000 Developer. VB however, provides you with additional help, including a template filled in with much of the code you'll need. You simply create a standard ActiveX DLL or EXE, implement the required interface, and set up the necessary registry keys, and you've got an add-in that can work in multiple applications.

Ken Getz (mailto: KenG@mcwtech.com) is a Senior Consultant with MCW Technologies, a Microsoft Certified Solution Provider focusing on Visual Basic and the Office and BackOffice suites. He is also co-author of Visual Basic Language Developer's Handbook (with Mike Gilbert) [SYBEX, 2000], and Access 2000 Developer's Handbook (with Gilbert and Paul Litwin) [SYBEX, 1999]. He's recently completed a series of VB6 training tapes, and a series of Access 97 tapes (with co-author Paul Litwin) for Application Developers Training Company (http://www.appdev.com/video).

IDE Add-ins

You might also find it interesting to dig into writing add-ins for the VBA IDE, and I've provided a simple one along with the examples for this article. The provided WebBrowser add-in allows you to create a dockable window in the IDE. If you're interested, you'll need to investigate the VBA IDE extensibility object model, and the use of the CreateToolWindow method that allows you to create dockable windows. The issues involved in creating add-ins for the VBA IDE are similar to those you encounter when writing add-ins for the Office products, except for these points:

  • You should use the AddIn project template project from within VB.
  • In your add-in's designer, select Visual Basic for Applications IDE as the host application.
  • Remove the reference to the Microsoft Visual Basic 6.0 Extensibility type library, and add a reference to the Microsoft Visual Basic for Applications Extensibility 5.3 type library. (Yes, although VB and VBA share most of the same objects in their extensibility models, they use different type libraries.)

Once you've set things up correctly, you're ready to write an add-in, such as the WebBrowser add-in available for the VBA IDE. To load your add-in once you've compiled it, load the VBA IDE, then select Add-Ins | Add-In Manager. You'll see your new add-in there, and selecting the Loaded/Unloaded checkbox loads the add-in for this session. (The WebBrowser add-in loads automatically once you've done this. If you close the window, use the Add-Ins | Web Browser menu item to display it again.)

- Ken Getz

Begin Listing One - AddToCommandBar, DeleteFromCommandBar

Public Function AddToCommandBar( _
   Optional intPosition As Integer = 0) _
   As Office.CommandBarButton
     Dim cbb As Office.CommandBarButton
   Dim cbr As Office.CommandBar
     On Error Resume Next
     ' See if you can find correct menu. Note: most apps don't
  ' require the .Item, but Outlook does. 
   Set cbr = _
    pobjCommandBarHost.CommandBars.Item(conMenuName) 
   If cbr Is Nothing Then
     ' Not available so you fail. 
     Exit Function
   End If
   ' Try to retrieve reference to existing 
  ' CommandBar control. 
   Set cbb = cbr.FindControl(Tag:=conMenuTag) 
   If cbb Is Nothing Then
     ' CommandBar button isn't there. Add it to CommandBar. 
     If intPosition = 0 Then
       ' Add to the end. 
       Set cbb = cbr.Controls.Add(Type:=msoControlButton) 
     Else
       Set cbb = cbr.Controls.Add(Type:=msoControlButton, _
                                  Before:=intPosition) 
     End If
     ' Set the caption. 
    cbb.Caption = conMenuText
     ' This is only necessary for Word add-ins, but the code
    ' uses it for all applications just to be consistent. 
    cbb.Tag = conMenuTag
     ' This is only necessary for "on-demand" add-ins, 
     ' but it can't hurt. 
    cbb.OnAction = "!<" & pobjAddIn.ProgId & ">" 
   End If
   Set AddToCommandBar = cbb
End Function
  Public Sub DeleteFromCommandBar()
   ' Remove the control you added from the CommandBar. All
  ' apps but Word seem to manage this by calling the Delete
  ' method of the WithEvents CommandBarControl object. Word
  ' requires you to find the control again, and delete it. 
   ' Therefore, all the apps use this same technique. 
   On Error Resume Next
  pobjCommandBarHost.CommandBars(conMenuName)._
    Controls(conMenuText).Delete
  Err.Clear
End Sub

End Listing One

© 2009 Microsoft Corporation. All rights reserved.   Terms of Use | Trademarks | Privacy Statement
Page view tracker
Rate the Lightweight library
x
Lightweight builds on ScriptFree (loband) by adding features you've requested: a SearchBox and default code language selection.
Do you like the SearchBox?
Do you like the tabbed code blocks?
How useful is this topic?
Tell us more.
Thanks
x
You're helping to improve MSDN Online.
Feedback
Switch View
Classic
Lightweight Beta
ScriptFree
Switch View