The ability to write add-ins is not one of Visual Basic 5.0's new features, but it's one of the most improved. VB4's IDE included some extensibility, but its approach to add-ins was limited. For example, VB4 did not give you full access to its menu bar, toolbars, or windows. Such shortcomings made the VB4 extensibility model look more like an afterthought than a carefully planned project, and I suspect that it was thrown into the product primarily to support the Visual SourceSafe add-in.

Well, the difficult days of writing add-ins are over. VB5's IDE exposes a complete, flexible, and powerful object model that lets you manipulate VB's environment much more precisely. VB5 gives you complete control of your projects, components, modules, procedures, and even individual lines of code. You can customize all the menus and toolbars of the environment; trap nearly every user action, such as adding a new component or renaming an old one; open, scroll, move, and close all the code windows; and so on. This article shows how to tap this power to create your own add-ins.

All this power comes at a price, however: the new extensibility object model is much more complex than VB4's, and you must spend some time acquainting yourself with how it works. Setting up even the simplest add-in is not trivial.

I have prepared a graphical model of the object tree that demonstrates the complexity of the new extensibility object model; you can view this model on the VBA Objects site at http://objects.windx.com. Take a careful look at the object tree before you dive into the practical details of add-in creation. I'll walk you through most of the items in the extensibility object model, but you must understand the model in its entirety to tap the power of the VB IDE to create your own add-ins, as well as take full advantage of the precision by which you can manipulate the VB environment. Most of the objects shown in the tree belong to the VB5EXT.OLB type library, which the VB5 installation procedure stores in the same directory as the main VB5.EXE file. This is the Microsoft Visual Basic 5.0 Extensibility library, which you must add to your references if you want to write an add-in.

EXAMINE THE OBJECT TREE
The IDTExtensibility class is included in the type library, but the VB5 IDE does not expose any object of this type. Instead, the class defines the interface that add-ins must implement to integrate with the VB environment. The add-in must include a public class, but the class's name is not important, as long as it includes this statement:

Implements IDTExtensibility

The IDTExtensibility class includes four methods that the VB5 IDE calls to notify the add-in of important events: OnAddinsUpdate, OnConnection, OnDisconnection, and OnStartupComplete. The most important method, OnConnection, is called when the user starts the add-in from the Add-In Manager dialog box:

Sub _
	IDTExtensibility_OnConnection(ByVal _	VBInst As Object, _
	ByVal Mode As vbext_ConnectMode, _ 
	ByVal AddInInst As _
	VBIDE.AddIn, custom() As Variant)

The VBInst object passed as the first argument is the instance of the Visual Basic Environment (VBE) object that is at the top of the VB IDE object tree (view the VBE object tree at http://objects.windx. com ). The add-in must save a reference to the particular instance of the VB environment that has just installed into the VBInstance class property. Note that you can't use a global variable because the same add-in may serve multiple VB instances. You need this object to access all the items in the hierarchy, and I use it in all the code examples that follow.

Just below the VBE object are six collections and a few other objects that enable you to leverage all the features of the environment.

The Windows collection holds all the windows used by the environment, except the main MDI window. You cannot add or remove items from this collection, but you can use their properties and methods to move, resize, hide, minimize, maximize, and close all windows in the IDE.

VB5 lets you dock and group its windows together, and a window is linked to other windows that share the same frame. If the window is not docked, its LinkedWindowFrame property returns Nothing. Otherwise, you can enumerate the frame's LinkedWindows collection to access all the companion windows in the same frame. The members of this collection are other Window objects, so you are actually exploring a recursive tree.

Note that you cannot dock code and designer windows, the Object Browser, or the Search and Replace dialogs. Use the Type property to learn which kind of window you're dealing with. For example, you can write a short routine that minimizes all the designer windows in the environment:

Dim w As VBIDE.Window
For Each w In VBInstance.Windows
	If w.Type = vbext_wt_Designer Then
		w.WindowState = _
			vbext_ws_Minimize
	End If
Next

The Addins collection represents all the add-ins that are registered in the environment. You can access each Addin object, query its Guid and ProgId properties, and activate or deactivate each object using the Connect property. However, the Addins collection is useful only for debugging or creating an alternative add-in manager; I won't cover its details in this article.

MANIPULATE WINDOWS' CONTENTS
The CodePanes collection includes all the code windows open in a given environment. You can use each CodePane object to scroll a window's contents, select text and copy it to the Clipboard, alternate between full module and procedure view, and learn how many lines are visible. However, you cannot directly modify the code using a CodePane object. Instead, you must use the CodeModule object exposed by the property with the same name.

Every CodePane object exposes a Window object, which represents the physical window seen by the user. For example, this short code snippet fills a list box with the caption of every CodePane object, then scrolls its window to the first line of code:

Dim cp As VBIDE.CodePane
For Each cp In VBInstance.CodePanes
	List1.AddItem cp.Window.Caption
	cp.TopLine = 1
Next

The CommandBars collection belongs to an external library, the Microsoft Office 8.0 Object Library (file MSO97.DLL). A CommandBar object unifies the concept of menus and command buttons, and can include other CommandBar objects, much as a menu can include one or more submenus. Each CommandBar object exposes a Control property that returns a CommandBar- Controls collection composed of CommandBar objects-just another example of recursion in the object model. The first CommandBar object in the IDE is the menu bar, so you can address any menu of the environment (see Listing 1).

LISTING 1

Dim cb As Office.CommandBar
Dim cbc1 As Office.CommandBarControl
Dim cbc2 As Office.CommandBarControl
Dim cbc3 As Office.CommandBarControl

List1.Clear
' iterate on all the command bars in the IDE
For Each cb In VBInstance.CommandBars
	List1.AddItem cb.Name

	' every command bar has a collection of controls
	For Each cbc1 In cb.Controls
		List1.AddItem vbTab & cbc.Caption

		' CommandBarPopup is a type of CommandBarControls
		If TypeOf cbc1 Is Office.CommandBarPopup Then
			' iterate on all the items of menus
			For Each cbc2 In cbc1.Controls
				List1.AddItem vbTab & vbTab & cbc2.Caption

				' if this is a submenu itself, iterate
				' on its items too
				If TypeOf cbc2 Is Office.CommandBarPopup _
					Then
						For Each cbc3 In cbc2.Controls
							List1.AddItem vbTab & vbTab & _
								vbTab & cbc3.Caption
						Next
				End If
			Next
		End If
	Next
Next

For example, your add-in can learn the current state of the program under development:

Dim mbc As Office.CommandBarControl
Set mbc = VBInstance.CommandBars(1)._
	Controls("Run").Controls(1)
If mbc.Enabled = False Then
	Print "Executing"
ElseIf mbc.Caption = "&Start" Then
	Print "Design Mode"
ElseIf mbc.Caption = "&Continue" Then
	Print "Break Mode"
End If

A brief explanation is necessary: the first control on the Run menu is the "Start" item, which is disabled if the program is in run mode. The control's caption changes to "Continue" in break mode.

Even more interesting, you can "click" on any menu item or toolbar button using the Execute method of the CommandBarControl object. For example, you can stop the running application with this statement:

VBInstance.CommandBars(1).Controls_
	("Run").Controls("Break").Execute

and resume execution with:

VBInstance.CommandBars(1).Controls_
	("Run").Controls("Continue").Execute

Accessing buttons on toolbars isn't difficult either. For example, you can show the Open Project dialog box using this command:

VBInstance.CommandBars("Standard")._
	Controls("Open Project...").Execute

Note that you should probably reference a menu or toolbar item using its name, rather than its numerical index. This protects you when a user customizes the menu or the toolbar. You also need to match the caption perfectly, including any trailing ellipses. You can omit the "&" character, however.

ACCESS FORM AND CODE ITEMS
You can find the most interesting part of the VB IDE object model if you travel down the the VBProjects path (view the VBProjects object tree at http://objects.windx.com). Each item of this collection is a project currently loaded in the environment; you can also directly reference the active project through the VBInstance.ActiveProject property.


Don't Miss These Events. These are the events that the Visual Basic IDE can raise in your add-in when the user performs a relevant action.

Each VBProject object exposes properties and methods that let you save the corresponding project, set its help file or description, and more. The most interesting use of this object is to enumerate its components through the VBComponents collection that gathers all the form, code, class modules, and so on.

VBComponents' objects can store their contents in one single file, as in the case of standard and class modules, or in two distinct files. For example, forms use both FRM and FRX files. You can detect the number of files used with the FileCount property, and learn their path using the FileNames property. You can query whether the component has been modified (property IsDirty), and you can even save it to disk (method SaveAs). If the component corresponds to a visible object, you can read and modify its settings using the Properties collection:

' set the BackColor of the
' current selected component
VBInstance.SelectedVBComponent._
	Properties("BackColor") = vbGray

You can access the code in each component with the CodeModule object, whose properties and methods allow you to read any single procedure and declaration in the module, find a given string, replace a block of lines, add new lines, and so on.

If you need to enumerate all the items in a code module-variable, methods, procedures, and constants-you must go one level deeper in the object tree. The Members collection serves exactly this purpose. Not only does it return the name and type of each item, but it also lets you read and modify the item's attributes, such as its description, HelpContextID, ProcedureID, and all the information that you enter in the Procedure Attributes dialog box (see Listing 2).

A number of component types, including UserControls and UserDocuments, also have a visible interface. You can access the corresponding designer using the Designer property, which returns a VBForm object. You can do a lot of interesting things with this object, such as iterating through the controls it contains:

' list all the controls on the selected 
' form (or UserControl or UserDocument)
Dim frm As VBIDE.VBForm
Dim ctr As VBIDE.VBControl
Set frm = VBInstance._
	SelectedVBComponent.Designer
For Each ctr In frm.VBControls
	List1.AddItem ctr.ClassName & _
		" " & ctr.Properties("Name")
Next

Each VBForm object exposes three collections: VBControls, which includes all the controls on the form; ContainedVBControls, which includes the controls placed on the form's surface; and SelectedVBControls, the controls that are currently selected. Changing a property in all the selected controls is relatively easy:

' same variables as the previous 
' example
For Each ctr In frm.SelectedVBControls
	ctr.Properties("Tag") = ""
Next

If a control can act as a container-as is the case with picture boxes and frames-it exposes a ContainedVBControls collection. This collection holds one or more VBControls objects, which in turn might be containers themselves, and so on. It is another case of recursion in the object tree.

You can find an example of what you can do with these collections on the free, Registered Level of The Development Exchange (see the Code Online box at the end of the article for details). You'll also find the complete source code of an add-in that helps you quickly create a grid of controls (see Figure 1).

TAP THE POWER OF EVENTS
You should know about one more item under VBE in the VB IDE object model: the Events object. Don't let the fact that the name is plural fool you-this is a class, not a collection. The only purpose of the class is to expose six properties that also look like collections, but are not.

Using these properties-CommandBarEvents, FileControlsEvents, ReferencesEvents, VBControlsEvents, SelectedVBControlsEvents, and VBComponentsEvents-you can obtain a reference to another object that is capable of raising events in your add-in when something happens in the VB environment (see Figure 2).

For example, use the CommandBarEvents property if you want to be alerted when the user clicks on a toolbar button or invokes a menu command:

' form level variable
Private WithEvents MenuHandler As _
	CommandBarEvents
Private Sub Form_Load()
	Dim cbc As Office.CommandBarControl
	Set cbc = VBInstance.CommandBars _
		("Window").Controls("Split")
	Set MenuHandler = VBInstance.Events._
		CommandBarEvents(cbc)
End Sub

Because you declare MenuHandler using WithEvents, your add-in is able to trap all the events it raises. For more information on the WithEvents keyword, see my article, "Add Class Inheritance to Apps" [VBPJ June 1997]. CommandBarEvents objects expose only the Click event. Simply put your code in the chain of actions that occur when the user selects the corresponding command:

Private Sub MenuHandler_Click_
	(ByVal CommandBarControl As Object, _
	Handled As Boolean, _
	CancelDefault As Boolean)
		' don't let the user split the
		' window
		CancelDefault = True
End Sub

The VBControlsEvents property returns a reference that you can use to trap all the events related to controls. Depending on the arguments you pass to the property, you can monitor a specific form, all forms in a given project, or any form in all the projects that are currently loaded in the environment:

' form-level variable
Private WithEvents CtrlHandler As _
	VBControlsEvents
Private Sub Form_Load()
' use (Nothing, Nothing) to monitor
' all forms in all projects
Set CtrlHandler = VBInstance.Events._
	VBControlsEvents(Nothing, Nothing)
End Sub

After you obtain a reference to a VBControlsEvents object, you can make good use of its ItemAdded, ItemRemoved, and ItemRenamed events. For example, suppose you are in charge of a programming team, and you want to remind your coworkers to assign meaningful names to controls on forms. Sure, you can rename controls when you revise the code, but this is a nuisance and an error-prone process. A much better solution is to prompt for a name when team members create the controls:

Sub CtrlHandler_ItemAdded(ByVal _
	VBControl As VBIDE.VBControl)
		VBControl.Properties("Name") = _
			InputBox("Please, type a " & _
			"meaningful name *N*O*W* !")
End Sub

The SelectedVBControlsEvents property is similar to VBControlsEvents: it lets you monitor when the user selects or deselects controls on a given form. The object it returns exposes only two events, ItemAdded and ItemRemoved. Use these events to set the enabled/disabled state of your own toolbar and menu items.

You can also do interesting things with the FileControlsEvents property of Events, such as monitor when a file is saved, renamed, added, or removed from a project. Although FileControlsEvents objects are most useful in full-fledged source management and version-control utilities, you can still exploit them in other creative ways. For example, you can use FileControlsEvents objects to automatically compress and back up all the files in the project as soon as they are saved to disk.

The VBComponentsEvents property returns an object that lets you keep track of all the activity related to components: when they are saved, loaded, renamed, and so on. You can also receive an event when a component is selected, which is useful if you want to disable buttons and menu items on your add-in.

The sixth and last property of the Events object, ReferencesEvents, returns an object that raises an event whenever a reference is added or removed from a project.

START FROM THE TEMPLATE
After exploring the VB IDE object model, you'll find that many of the details about the inner workings of add-ins are less obscure. However, setting up a working add-in is still nontrivial. Fortunately, VB5 includes a project template that saves you a lot of time while building an add-in. Load it by issuing the New Project command under File, and selecting the Addin template.

When the add-in is registered, it writes a line in the Add-ins32 section of VBADDIN.INI:

ProjectName.ClassName = 0

"ProjectName" is the name of the project as typed in the Project Properties dialog box, and "ClassName" is the name of a public class that the add-in exposes and records in the Registry the first time it runs (as all VB-written OLE servers do). If you create add-ins using the provided template, ClassName is "Connect," and ProjectName is "MyAddin." Of course, you should always change ProjectName to something more descriptive. The actual name of the class doesn't really matter, as long as it implements the IDTExtensibility interface.

The zero that follows the equals symbol means that the add-in will be listed in the Add-Ins dialog, but won't be automatically loaded when the VB environment is started. When you tick the check box in the add-in list, the entry in the VBADDIN.INI file becomes:

ProjectName.ClassName = 1

The project then loads automatically in the IDE the next time you start the Visual Basic environment.

The registration process in the VBADDIN.INI file is performed by the AddToIni procedure in the BAS module that comes with the template. You can invoke this command from the Immediate window.

However, if you plan to distribute the add-in to other developers, you must devise a way to perform this registration automatically. The simplest way is to include a Sub Main procedure:

Sub Main()
	If App.StartMode = 0 Then
		AddToINI
	End If
End Sub

This code registers the add-in the first time it runs as a standalone application. Unfortunately, this solution only works if the add-in is compiled as an out-of-process EXE. If you are delivering it as an in-process DLL, you should resort to another approach, such as explaining to the user how to manually edit the VBADDIN.INI file using a text editor, or preparing a customized setup procedure that does it automatically.

When the add-in has been properly registered, it appears in the Add-Ins dialog box, along with a short description that you can modify only by acting on the Description property of the Connect class. Note that this step is not documented in the manuals. You need the Object Browser to modify this description.

When the user actually runs the add-in, the VB5 IDE calls the IDTExtensibility_OnConnection method in the Connect class, passing a reference to itself. Within this method the add-in initializes itself and typically adds one or more user interface items to the IDE, such as menu items, new toolbars, or additional buttons on existing toolbars. Now that you know how to deal with the CommandBarEvents object, understanding how the code in the template works should not be a problem.

Note that you should never store objects and values in global variables because your add-in might be invoked from many instances of the IDE. Instead, you should add public properties to the Connect class, and store all your values there. You should use this approach because there is a distinct Connect object for each instance of the environment that uses the add-in.

The other important event in the life of an add-in occurs when the VB IDE calls the IDTExtensibility_OnDisconnection method. In this procedure, the add-in must destroy all the objects it has created, such as menu items, toolbar buttons, and event handlers. After a call to this method, any previous reference to the VBE object-or any of its dependent objects-becomes invalid and should not be used again by the add-in.

Compile the add-in into an out-of-process EXE or an in-process DLL when you complete the debug step. Generally, you should compile to EXEs only if your add-in also works as a standalone utility; otherwise, use DLLs because they interact much faster with the IDE when no cross-process communication is involved.

You can download a word file with a description of all the items in the object model, their methods, and their properties if you are a Premier Club member of The Development Exchange (see the Code Online box for details). The Word file contains the same information shown by the Object Browser, but in an easier-to-read format. You'll also find a complete Project Viewer add-in, which shows you how to view the code of any procedure in any project currently loaded in the environment. It comes with commented source code and many routines you can recycle in your own add-ins.