Part II of Extending Visual Basic with Add-ins

by Ken Lassesen

This is Part II of a two-part article about add-ins. The first part, published in the January/Feburary 1996 issue of Developer Network News, discussed add-ins for the Visual FoxPro Class Browser.

This portion is excerpted from the article "Building Add-Ins for Visual Basic 4.0." The complete article is in the Library in the Technical Articles bin.

Introduced in the Visual Basic programming system version 4.0, add-ins are special-purpose OLE servers that establish two-way communication between the add-in OLE server and the Visual Basic Integrated Development Environment (also an OLE server). Add-ins can do almost anything you can imagine, for example:

• Automating routine tasks such as creating standard menus and standard toolbars or generating Help file templates and ToolTips

• Automating programming standards

• Creating form and application wizards

An add-in has all the characteristics of a wizard, except that it has the ability to detect what the developer is doing and react appropriately. Imagine that you are creating a new form, and a dialog box automatically appears to ask whether you want to add the standard menu items (with code) to the new form or create a set of controls from a database table automatically.

Creating the framework

The greatest challenge in building an add-in is the analysis and design—the coding is simple.The implementation model uses different classes to handle user-instigated manipulations of the project, event-instigated manipulations of the project, and file control events.

   Figure 1. Suggested implementation model for add-ins

The add-in instance

When the Add-In Manager dialog box enables an add-in, the VBIDE object creates an instance of the add-in OLE server and then calls the methods or fires the events in the add-in. The important events are Initialize, ConnectAddIn, and Timer1_Timer.

When the VBIDE object creates an instance of the add-in, this instance may create many other instances as side effects. The class module should have the Instancing property set to Creatable MultiUse. The Instancing property has led me to suggest the following guidelines:

• Do not use global variables.

• Do not use module-level variables in nonclass modules.

• Do not use static variables or procedures in nonclass modules.

The project creates all forms and class modules by using a Dim . . . As New . . . statement and uses only these new instances.

I create the child instances in the Declarations section of each file, using Private to enforce a strict hierarchy and to allow multiple instances with the same instance name to coexist within different scopes. Child instances disappear with the parent. I create the other class instances and the frmStatus form instance with the following code:

   'In Declarations of AddIn.Cls
   Private ThisAddInMgr As New clsAddInMgr 'clsfrmONE
   Private ThisTipsHelp As New clsTipsHelp 'clsfrmTWO
   Private ThisForm As New frmStatus

I place the code to create a form instance in each child class module under the Declarations section of the class module or form:

   'In another class module or form, i.e. frmONE.Cls
   Private ThisForm As New frmAddInMgr 'frmONE

Each time the VBIDE creates an instance of the add-in, the add-in instance in turn creates separate private instances of any child class or form.

The ConnectAddIn event

The ConnectAddIn event passes a pointer to the instance of the VBIDE that created it. The add-in instance uses the ConnectAddIn event to add menu items to the VBIDE menu bar.

Each menu choice sends a click event without arguments to a class instance that has an AfterClick method.

The class instance receives a pointer to the instance of the VBIDE object and a pointer to the menu item that fires it. This class passes these items to the child form so that the form may manipulate the VBIDE object or the menu item controlling it.

   Set ThisInstance = VBInstance 'Keep a reference.
   Set ThisSubMenu = ThisInstance.AddInMenu.MenuItems.AddMenu("&My Addin")
   Set ThisMenuLine(2) = ThisSubMenu.MenuItems.Add("&Standard Code")
   hThisMenuLine(2) = ThisMenuLine(2).ConnectEvents(ThisAddInMgr)
   ThisAddInMgr.MenuLine = ThisMenuLine(2)
   ThisAddInMgr.VBIDE = ThisInstance

The child class instance saves these values and then passes them on to any child class instances or form instances.

   Public Property Let VBIDE(vNewValue)
   Set ThisInstance = vNewValue 'Keep a reference locally.
   ThisForm.VBIDE = vNewValue 'Pass reference to child.
   End Property

The Timer1_Timer Event

One problem with doing OLE Automation is time-outs. I prefer to do asynchronous automation using a timer. With asynchronous automation, the OLE Automation call sets an argument’s value, enables a timer, and then returns to the client. The timer executes the method after the OLE Automation call finishes and prevents a time-out. For example, when an instance creates a form instance, Form_Load would fire, but if this event takes a long time to finish, a time-out may occur.

I use a DelayedForm_Load procedure and a timer set to an arbitrary 3/10 of a second to ensure that the instance executes DelayedForm_Load procedure once.


   Private Sub Timer1_Timer()
   'This allows return to occur fast!
   Static fFormLoad As Long 'A flag to prevent multiple execution to occur fast!
   If Not fFormLoad Then
   fFormLoad = True 'Set flag and disable timer.
   Timer1.Enabled = False 'BEFORE calling DelayedForm_Load.
   DelayedForm_Load 'Otherwise the timer may fire AGAIN during it.
   End If
   'Other uses of timer may be added here.

The way I created the instances above allows easy manipulation of the VBIDE object from an add-in form, for example, to extract information from the active VBIDE project or to add or modify a file. The form uses the pointers ThisMenuLine or ThisInstance to do these manipulations in my model.

To illustrate the extraction of information from ThisMenuLine and ThisInstance, I changed the caption of each form belonging to my add-in so that the user can identify the project attached to the form instance. The code is a one-liner:

   Me.Caption = ThisMenuLine.Caption + " : " + ThisInstance.ActiveProject.FileName

Although this does get the job done, I use a nickname for clarity:

   Private ThisProject As VBIDE.ProjectTemplate
      ....
   Set ThisProject = ThisInstance.Active
      ...
   Me.Caption = ThisMenuLine.Caption + " : " + ThisProject.FileName

Adding a file

Although I could blindly copy files from other projects, I would prefer to see a menu of files, click the item I want, and have these files added to my project. I create a file containing the code and then add it. The natural depository for these files is a database using a memo or equivalent field.

I place the code I obtained from the database in a string and call the procedure below. The procedure creates the full path from the filename so that this new file is in the same directory as the project file, checks for the existence of this file, and then adds it to the project.

   Public Sub Project_AddFile(ByVal FileCode$, ByVal FileName$, Project
   As VBIDE.ActiveProject)
   Dim FullPath$
   FullPath$ = ExtractPath(Project.FileName) + FileName$
   If Len(Dir$(FullPath$)) > 0 Then
   MsgBox "File [" + FileName$ + " Already exists. Please delete and try
   again.", vbCritical, "ERROR"
   Exit Sub
   End If
   fno% = FreeFile
   Open FullPath$ For Output As #fno%
   Print #fno%, FileCode$
   Close fno%
   FileType$ = Project.AddFile(FullPath$)
   MsgBox "The " & FileType$ + " file [" + FullPath$ + "] has been added",
   vbInformation, "Add File"
   End Sub

This style of add-in allows reuse of standard forms (log-on, System Information), modules (INI functions, registry functions), or class modules. What if I just want to toss in a bunch of procedures into an existing file? (For an answer to this question, see the full article at the For Developers Only online address given above.—Editor)

The implementation of an add-in is simple once you understand the design and implementation issues. The benefit is less time coding with better and more consistent code. It’s time to stop reinventing the wheel!

Ken Lassesen is known to his colleagues in the Microsoft Developer Network Technology Group as the Lord of Lutefisk and Grand Vizier of Visual Basic.

An add-in has all the characteristics of a wizard, but can also detect what the developer is doing and react appropriately.

The greatest challenge in building an add-in is the analysis and design—the coding is simple.

It’s time to stop reinventing the wheel!

One problem with doing OLE Automation is time-outs.