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. The greatest challenge in building an add-in is
the analysis and designthe 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
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
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.
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)
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
arguments 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
ThisAddInMgr As New clsAddInMgr 'clsfrmONE
Private
ThisTipsHelp As New clsTipsHelp 'clsfrmTWO
Private
ThisForm As New frmStatus
Private
ThisForm As New frmAddInMgr 'frmONE
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
Set
ThisInstance = vNewValue 'Keep a reference locally.
ThisForm.VBIDE
= vNewValue 'Pass reference to child.
End Property
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. Its 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 designthe coding is simple.
Its time to stop reinventing the wheel!
One problem with doing OLE Automation is time-outs.