Creating a Visual Basic Add-In (转贴、VBIDE编程的经典之作)

来源:互联网 发布:java b2b2c商城系统 编辑:程序博客网 时间:2024/05/16 07:20

转贴:下面是原文地址

http://podgoretsky.com/ftp/Docs/Basic/Using%20Visual%20Basic%205/ch43.htm 

Creating a Visual Basic Add-In

  • Introducing add-ins
VB5's Integrated Development Environment (IDE) is highly customizable, using software components written in Visual Basic itself.
  • Discovering the add-ins included with Visual Basic
You find many good examples of add-ins in the VB package, including the little known Template Manager that lets you reuse the controls, the menu, and the routines that you use on a regular basis.
  • Exploring the inner life of an add-in
Understanding what happens when the VB environment is launched and when it activates your add-ins is invaluable knowledge when trying to write bulletproof add-ins.
  • Analyzing the VB IDE object model
Your add-ins can do almost anything that a programmer does manually while working with the VB environment, provided that you spend some time learning the intricacies and subtleties of the VB IDE object model.

However sophisticated the VB5 Integrated Development Environment (IDE) may be, there is always something that is missing. Sometimes you want advanced word processor-like capabilities in your code editor (a spell checker, for instance); other times you need a more flexible way to align, center, and resize the controls on your forms.

It is understandable that Microsoft couldn't anticipate all of the wishes of every Visual Basic programmer on earth, so they made a smarter move: if you need a special capability in the VB environment, you only have to write an add-in and install it within the VB5 IDE.

Introducing Add-Ins

The concept of add-ins dates back to Visual Basic 3.0, when a few third-party software companies created and marketed a number of utility programs that looked as if they were part of the VB environment, while adding many features that the environment itself did not offer. The most successful product of this category is Sheridan's VB Assist, which is still one of the most popular accessories for VB programmers.

These add-ins usually offer a wide range of functions--VB Assist currently has more than fifty--ranging from simple code generators for message boxes and common dialogs, to complex stuff, such as a customized Property Window and a palette to resize and align controls on the active form at design time. Many add-ins include other useful utilities, such as screen-capture programs, icon extractors (a tool that extracts icons from DLLs, EXEs and other files), or clipboard enhancers (to keep multiple code fragments in the clipboard).

Writing a VB3 add-in was not simple at all. The big problem wasn't writing the code to implement the utility function itself; rather, it was integrating the add-in with the VB editor. In fact, Microsoft never made public the internal details of the VB3 environment, and add-in developers had to find out everything by themselves, using several unorthodox techniques and tools, including reverse engineering, debuggers, and spy programs. This approach worked 99 percent of the time, but certainly did not contribute to the overall robustness of the majority of add-ins on the market.

The situation dramatically changed when VB4 was released. In fact, this version of the Integrated Development Environment (IDE) exposes several internal structures to the outside in the form of objects that can be controlled by an external program, written in VB or most other programming languages. This innovation enabled developers to build more reliable add-ins and, in fact, the number of such tools increased significantly, especially in the shareware market. Generally speaking, add-ins were more robust and reliable and it seemed it was no longer necessary to resort to all sorts of unorthodox tricks to make them work.

Unfortunately, Visual Basic 4.0 exposed only a small number of objects to the outside; they only permitted to add a menu item to the Add-Ins menu, to handle the controls on the current form and to intercept a few user actions, such as saving or loading a new project. These features enabled Microsoft to enhance the VB IDE with Visual SourceSafe--a well-known and powerful control version program that was, and still is, included in the Enterprise Edition--but were not enough for the demanding add-in developers who were forced to continue to use non-standard techniques for their programs to work flawlessly in the VB IDE.

Visual Basic 5.0 has completely changed the picture. The VB5 IDE exposes nearly every internal structure to the outside, from the active project down to the individual forms, controls, and procedures; it also exposes all its windows, menus, and toolbars. You now may add new items to any menu--not just the Add-Ins menu--and to any toolbar. Finally, you can intercept most of the actions performed in the environment by the developer: when a new component is loaded, saved, or becomes the current one, when a control is selected, when a menu or toolbar command is invoked, and so on. In other words, you now have the capability to control almost every aspect of the environment and, therefore, you can write powerful add-ins that automate many complex or boring routine tasks.

Most VB programmers are interested only marginally in building add-ins, which they perceive as tools that should be purchased from specialized vendors. However, I strongly believe that add-ins are a great opportunity for the smart developer. In fact, even if third-party add-ins are very useful and likely to significantly reduce your development time, they cannot cover every possible need.

Add-ins are especially helpful to corporate programmers, who often have to adhere to a set of rules for writing source code--from consistent names for variables and controls, to coherent indentation of loops, If, and Select blocks. Thus, a wise team manager might decide to write down these standards and build an add-in that enforces them. Imagine an add-in that pops up whenever a developer creates a new control, asking for its name and rejecting default, meaningless strings such as Text1 or Command1.

However, add-ins are not for corporate programmers only. The time you spend writing and testing your personal add-in will save you time whenever you use it. Not convinced yet? Just think of this: Suppose you write a trivial code generator that saves you about five minutes a day. This means that you save about 20 hours in a year, not counting all the time you save by not having to debug the code generated by the add-in. Once you are familiar with add-ins, you might be able to write such simple utilities in just two or three hours; thus, you can save at least sixteen or seventeen hours!

Installing, Activating, and Using VB5 Add-Ins

You should be familiar with the concept of add-ins already as a number of such utilities were shown in action in previous chapters. This section summarizes what you've learned so far and illustrates a few details of their inner workings, which are not immediately apparent.

The Add-Ins in the VB5 Package

The following list is a quick review of all the add-ins that come with Visual Basic 5.0:

  • Application Wizard Produces the complete skeleton of an SDI or MDI application, including menus, toolbars, and data-access forms. This is likely to be the first add-in that you learn to use, as well as the most useful one.

  • Data Form Wizard Creates forms that read and save data to database tables, including forms that access data in a master-detail relationship (see Figure 43.1).

  • Class Builder Lets you both create new classes and hierarchies and add new properties, methods, and events to existing ones.

  • ActiveX Control Interface Wizard Enables you to save hours of hard, error-prone manual work when building ActiveX controls that are based on existing, simpler constituent controls.

  • Property Page Wizard Simplifies greatly the creation of property pages to associate with existing ActiveX controls.

  • ActiveX Document Migration Wizard Converts a regular application and its forms into an ActiveX Document that can be sent over the Internet and viewed from within a compatible browser, such as Internet Explorer.

See Chapters 24, "Creating ActiveX Controls," 25, "Extending ActiveX Controls," and 27, "Creating ActiveX Documents," for more information about ActiveX Controls, Property pages, and ActiveX Documents.

  • Wizard Manager Makes the creation of wizard-like applets a visual job.

  • API Viewer Offers a quick way to add declarations of external Windows API functions to your program by using a simple and intuitive interface. It is an executable program that also can be installed in the Add-Ins menu (see Figure 43.1).

See Chapter 46, "Accessing the External Functions: The Windows API," for more information about Windows API.

FIG. 43.1
Two of the many add-ins that come with Visual Basic 5.0.

  • Add-In Toolbar Appears as a toolbar that enables you to quickly launch other add-ins (an example is provided later in this chapter).

Users of the Visual Basic 5.0 Enterprise Edition have the following two additional add-ins to use:

  • Microsoft Data Tools Visually organizes remote SQL Server databases and related queries.

  • T-SQL Debugger A precious tool for those programmers who need to run Transact SQL stored procedures on a SQL Server database and want to trace their execution and debug them from a remote workstation.

The Template Manager Add-In

There is one more add-in of which you might not be aware--the Template Manager add-in. This add-in comes in the Tools/Unsupprt/Templmgr directory and is not automatically installed by the Visual Basic Setup procedure. It adds three new items to the Tools menu that enables you to easily reuse code snippets and individual routines, controls and their related code, and entire menus (see Figure 43.2).

FIG. 43.2
The Tools menu after you have correctly installed the Template Manager.

The installation procedure of the Template Manager is completely manual and requires that you carefully follow these steps (the instructions assume that your CD-ROM drive is D: and that Visual Basic has been installed in the directory c:/vb5):

1. Copy the Template Manager DLL into the VB5 directory; from the MS-DOS prompt, execute the following command:
copy d:/Tools/Unsupprt/TmplMgr/Tempmgr.dll c:/vb5
2. Register the DLL by using the RegSvr32 utility in the /Tools/RegUtils directory:
d:/Tools/RegUtils/RegSvr32 c:/vb5/Tempmgr.dll
3. Create the directories for menu, control, and code templates and then copy the templates that come on the CD-ROM:
xcopy d:/Tools/Unsupprt/TmplMgr/Template/*.* c:/vb5 /s
4. Using Notepad or another text editor, open the VBADDIN.INI file in the /WINDOWS directory and add this line at the end of the [Add-Ins32] section:
TempMgr.Connect=1
5. Run Visual Basic: If everything is okay, you should find three more items in the Tools menu (refer to Figure 43.1).

Now you can easily add standard menus to your forms (see Figure 43.3), as well as controls and routines, and you can even create your own customized templates with the following directions:

  • For customized menu templates, create a blank form, add the menus that you want to include in the template, and then save the form with a descriptive name in the c:/vb5/Template/Menus directory.

  • For customized control templates, create a blank form, add one or more controls to it, and then save the form in the c:/vb5/Template/Controls directory.

  • For customized code snippet templates, create a blank BAS form, add the routine(s) you want to include in the template, and then save the file with a descriptive name in the c:/vb5/Template/Code directory.

FIG. 43.3
Three standard menus have been added to the underlying form by double-clicking the corresponding menu icon.

Activating an Add-In

As a developer, you probably already know how to activate an add-in, especially if you already used the add-ins that were provided with Visual Basic 4.0. However, a few minor details have changed, so it's better to tell the whole story from the beginning.

Note that this section discusses activating an add-in, not installing it. The installation process consists of copying the relevant files to the hard disk; an add-in should have been properly installed by using its own setup procedure--before being activated.

Using the Add-In Manager

The most common way to activate one all of the add-ins that comes in the VB5 package, or that you buy from third-party vendors, is through the Add-In Manager, a command found in the Add-Ins menu.

The Add-In Manager dialog box shows all of the VB5 add-ins currently installed in the system (see Figure 43.4). You activate and deactivate individual items simply by marking and unmarking the corresponding check box.

FIG. 43.4
The Add-In Manager dialog box, which lists all the add-ins that have been installed on the system.

In most cases, as soon as you activate an add-in, a new item is added to the Add-Ins menu; in other cases, you'll see a brand new submenu. However, there are add-ins that affect menus other than the Add-Ins menu (for instance, the Template Manager, discussed previously), or that add a custom toolbar. Finally, there also may be add-ins that do not have any user interface, which quietly work in the background until the developer does something that wakes them up.

 


Customizing a Toolbar for Easy Add-In Access
If you work intensively with add-ins, you may find it convenient to prepare a customized toolbar--or add a custom icon to an existing toolbar--that gives you quick access to the Add-In Manager dialog box (see Figure 43.5).

FIG. 43.5
Take advantage of VB5 customizability to build a personal toolbar with all your favorite commands, including a shortcut to the Add-In Manager dialog box.

To create a custom toolbar, follow this procedure:

1. Right-click any existing toolbar; then select the Customize command.

2. In the Toolbar tab of the Customize dialog box, click the New button and then create a new toolbar.

3. In the Command tab, select a menu in the leftmost list box, then drag-and-drop selected items onto the toolbar just created.

4. Right-click the new toolbar, then use the commands on the pop-up menu to customize each toolbar button with a caption, an icon, group separators, and so on.

Editing the VBADDIN.INI File

Behind the scenes, the Add-In Manager modifies the VBADDIN.INI file found in the /WINDOWS directory. This is a text file that includes a list of all the add-ins currently installed in the system. The format of the list is rather intuitive; here are the contents of a typical VBADDIN.INI file:

[Add-Ins32]VBSDIAddIn.Connect=0AppWizard.Wizard=1WizMan.Connect=1ClassBuilder.Wizard=1AddInToolbar.Connect=1ControlWiz.Wizard=1DataFormWizard.Wizard=1ActiveXDocumentWizard.Wizard=1PropertyPageWizard.Wizard=1APIDeclarationLoader.Connect=1PrjViewer.Connect=0TempMgr.Connect=1

As you can see, the format of all the lines, apart from the [Add-Ins32] header, is the same:

server-name.class-name = 0|1

The server name is unique to the add-in, and usually resembles the add-in's descriptive name. The class name is the name of the class that the add-in exposes to the VB5 IDE (more on this later in the chapter). Often, this class is called Connect or Wizard, but this is just a convention, not a strict rule. For instance, all the add-ins that you create using VB5's Add-In template expose a Connect class, unless you manually change this name before compiling.

The digit that follows the equals (=) symbol is 1 if the add-in is to be activated as soon as the VB5 IDE is loaded into memory, and 0 if the add-in is installed but is not automatically activated at VB5's startup. In practice, all add-ins that are marked with 1 appear in the Add-ins menu, while the others do not.

Each time you modify the settings inside the Add-In Manager dialog box, the VB5 IDE modifies this file, so that the next time you run the Visual Basic environment, you'll find exactly the same set of add-ins that you left in the previous session.

Of course, now that you know what happens behind the scenes, you can also edit the VBADDIN.INI file yourself by using any text editor, such as Notepad. In fact, you probably will be obliged to do so when you have to install an add-in that comes without a professional setup procedure, exactly as you did for the Template Manager add-in.

Note that you cannot add a line to the VBADDIN.INI file unless you know the exact add-in name and class name used by the add-in. This information usually is found in the documentation or a Readme.txt file that comes with the add-in. This is not a problem with the add-ins that you create on your own.

The Add-In Toolbar

The Add-In Toolbar is an add-in provided with Visual Basic 5.0; its function is to provide a way to activate any add-in quickly. You can activate the Add-In Toolbar exactly as you do with any other add-in: by using the Add-In Manager dialog box.

There are two methods to make the Add-in Toolbar show the icon of a given add-in:

  • You can manually add (or remove) an add-in to the Add-In Toolbar: just click the +/- button (the leftmost button on the toolbar), then choose the Browse command and select the DLL or EXE file corresponding to your add-in (see Figure 43.6).

FIG. 43.6
You can manually add any add-in to the Add-In Toolbar by clicking its +/- button, and then selecting the right DLL or EXE file.

  • The add-in automatically adds itself to the Add-In Toolbar during its setup routine; the majority of the add-ins that come with VB5 follow this approach. It is also said that the add-in registers itself with the Add-In Toolbar. For more information about this registration procedure, see the language manuals.

It is interesting that the list of the add-ins recognized by the Add-In Toolbar is distinct from the list that you see in the Add-In Manager dialog box. In fact, the Add-In Toolbar is capable of dealing with add-ins that are not registered in the VBADDIN.INI file, even if this opportunity is rarely exploited by commercial add-ins.

The Add-In Toolbar offers two important advantages to the add-in user. First, the add-in is always available and highly visible on a toolbar, without having to be activated through the Add-In Manager dialog box or invoked from the Add-Ins menus. Second, the add-in is launched only when the user actually needs its functions.

The latter point is very important because when Visual Basic starts, it activates all the add-ins that are marked with a 1 in the VBADDIN.INI file; this initialization step must be performed for every add-in in the list, and therefore may take several seconds.

Conversely, when an add-in is registered only in the Add-In Toolbar (and not in the Add-In Manager), Visual Basic won't initialize it during its startup, thus saving some precious time. Of course, you have to activate the Add-In Toolbar itself, which is one add-in, but this takes only a fraction of the time it would take to load all the add-ins you might have installed.

The first time you run a given add-in from the Add-In Toolbar, it goes through its initialization procedure; therefore, you are not really saving any time--you are simply procrastinating the initialization step until you really need the add-in's services. In other words, you are able to quickly start Visual Basic without having to initialize a bunch of add-ins that you'll probably never use in the current session.

The Nature of Add-Ins

At this point, you should have a clear picture of what an add-in is and how it is activated, at least from the users' standpoint. Since this chapter discusses developing an add-in, you must understand the real nature of this kind of application. The following concepts may sound a bit too theoretical at first, but it is strongly suggested that you do not jump over this section: if you have the patience to follow me in this digression, you'll find no difficulty in learning all the practical, nitty-gritty details of add-in programming.

The Add-In as an OLE Client

As mentioned at the beginning of this chapter, the VB5 IDE exposes an extensive object hierarchy through which an external program can access all its inner structures and components. It is evident that any add-in that needs to manipulate such real entities (windows, controls, code procedures, and so forth) actually has to work with these objects. There is an OLE client-server relationship between the VB IDE and the add-in, where the IDE is the server that exposes the objects, and the add-in is the client that handles them.

The most important item of Visual Basic's IDE object model is the VBE object, which represents the environment that has activated the add-in. The complete VB/IDE object model is a complex tree--and I won't discuss it until later--but it is important to note that all the other entities in the hierarchy are dependent objects that are children (or grandchildren, or great-grandchildren) of the VBE object.

This hierarchical relationship means that the add-in must have a reference to the root VBE object to completely explore the object tree. As will be seen in a moment, this reference is passed to the add-in during its initialization process, when Visual Basic starts and launches all of the add-ins whose entry in VBADDIN.INI is set to 1, or when the add-in is selected later in the Add-In Manager dialog box. The add-in must save this reference somewhere for future use.

Adding the Necessary References Because the add-in has to know how to deal with the VBE objects, and all the other objects exposed by the Visual Basic environment, when you start coding it, you must add the correct type libraries in the References dialog box. Add-ins require that the following two distinct type libraries be available; the third one is optional (see Figure 43.7):

  • The Microsoft Visual Basic 5.0 Extensibility type library, embedded in the VB5EXT.OLB file This library includes all the VB IDE objects (windows, components, and so forth) except those exposed by the library that is explained next.

  • The Microsoft Office 8.0 Object library, contained in the MSO97.DLL file This library includes a small number of user interface elements, namely menus and toolbar buttons.

  • The VB Add-In Toolbar type library, included in the AITOOL.DLL file This reference is necessary only if you want to register your add-in programmatically with the Add-In Toolbar

FIG. 43.7
You need to add two types of library references when writing your add-ins, or three if you also want to register your program with the Add-In Toolbar.

The Add-In as an OLE Server

While it is easy to understand that the add-in works as an OLE client, it is less obvious that--at the same time--the add-in also works as an OLE server, and the VB IDE works as an OLE client.

To understand why this is necessary, think of what happens when the Visual Basic IDE activates an add-in: it looks in the VBADDIN.INI file for the corresponding pair server-name.class-name and then asks the OLE subsystem to create an object of that class. This action brings up the add-in, which is now ready to respond to Visual Basic's requests.

The add-in therefore must expose a Public class to the VB IDE. In other words, at this stage of the process, the add-in is the OLE server, and the Visual Basic environment is the client. During the regular use roles are reversed, as seen in the previous section: the add-in handles the objects exposed by the IDE and, therefore, is the client. However, when the user requests to deactivate the add-in from the Add-In Manager dialog box, or the IDE is shutting down, the environment calls the add-in to inform it that it should remove all the menu or toolbar items it has created. Once again, the VB IDE acts as the client and the add-in acts as the server.

To summarize, it makes little sense to specify whether an add-in is an OLE client or server; the truth is that the VB IDE and the add-in communicate through OLE in a bidirectional fashion. This means that both of them have to expose a Public object that the other can see and use.

In-Process versus Out-Process Add-Ins Visual Basic is capable of creating both in-process and out-process OLE servers. The following bullets briefly summarize the differences between the two:

  • In-process servers DLLs that are loaded in the same address space as the program that is using them (the client program).

  • Out-process servers Regular EXE programs that also expose a number of objects and, therefore, are programmable from the outside. Out-process servers are distinct processes and don't share the address space with their client applications.

It turns out that in-process DLLs generally are faster because the communication with their clients occurs in the same address space. Conversely, while EXEs are slowed down by the relatively slow cross-process communication, they offer a few advantages. First, they can be used as stand-alone applications; second, they can exploit the multitasking nature of Windows 95 and NT and can execute tasks in the background. Besides, a stand-alone OLE server can expose its objects to multiple client applications, while a DLL can serve only one client (however, multiple clients can load multiple instances of the DLL, even though these instances might take more memory and cannot communicate easily among each other).

The Visual Basic environment deals with both kinds of add-ins. However, the only out-process add-in included in the Professional Edition is the API Viewer. All the other add-ins in the box are DLLs, probably because they communicate with the IDE at a higher speed (this is not an issue with the API Viewer because it doesn't send too much data to the VB environment).

If you expect that your add-in will need to make heavy use the objects in the IDE, you should compile it as an in-process OLE server. However, you can decide which kind of server to create just before compiling the program. In other words, you initially can work with out-process components (that are easier to debug), and then switch to in-process servers if you discover later that the out-process components are too slow, often without changing one single line of code.

The IDTExtensibility Interface

There is one more detail to discuss. While the VB IDE can easily create an instance of the class exposed by the add-in (whose name is found in the VBADDIN.INI file), this action is not sufficient to establish a robust communication between the two programs. This communication is possible only if the VB IDE calls a specific method in the class exposed by the add-in. But, which method should the IDE call? And, above all, how can the IDE be sure that the add-in class actually exposes that method?

This is where the IDTExtensibility interface comes into action. IDTExtensibily is not a class used to create an object; rather, it is an abstract class used to define an interface, the set of properties and methods exposed by a class. If class A exposes a given set of properties and methods, and class B exposes the same set of members, class B is said to implement the A interface. This simple concept is very important both when writing robust object-oriented applications, and when working with advanced OLE concepts. Find out next what this has to do with add-ins.

Add-ins are not required to include a class with a given name--in fact, the name of the class exposed to the VB IDE is completely arbitrary--as long as that class implements the IDTExtensibility interface. This particular interface consists of the following four methods: OnConnection, OnDisconnection, OnStartupComplete, and OnAddinsUpdate.

Because the VB IDE can assume that the Public class exposed by the add-in implements this particular interface, it can invoke one of those methods without the risk of raising a runtime error. The environment invokes one of the methods when it has something to inform the add-in about its current status, as follows:

  • The OnConnection method is called by the VB IDE when the add-in is being connected to the environment; this can occur when VB is starting, when the user selects the add-in in the Add-In Manager dialog box, or when the user clicks the corresponding icon in the Add-In Toolbar.

  • The OnDisconnection method is invoked when the environment is about to disconnect the add-in; this occurs when the user deselects the add-in from the Add-In Manager dialog box, or when VB shuts down.

  • The Visual Basic IDE calls the OnStartupComplete method when it has completed its initialization. This method is called after OnConnection only for those add-ins that are loaded at VB startup, and is never called for add-ins that are activated at a later time through the Add-In Manager or the Add-In Toolbar.

  • The Visual Basic IDE calls the OnAddinsUpdate method whenever the VBADDIN.INI file has been modified.

The OnConnection and OnStartupComplete methods are similar in that they both offer the add-in an opportunity to perform all the initialization chores, such as adding one or more menu items or toolbar buttons to the VB environment user interface. The difference between the two is subtle: when the OnStartupComplete method is invoked, the add-in has an opportunity to learn which add-ins have been loaded at startup, which is not possible within the OnConnection method.

 


NOTE: When the OnConnection method is fired, the VB IDE is still loading and might be in an unstable state: that's why it's suggested that you not act on VB IDE objects before the OnStartupComplete method is executed.

The OnDisconnection method is where the add-in destroys the menu items and toolbar buttons it created and, in general, restores the previous state of the environment.

The fourth method, OnAddinsUpdate, is of limited use. It fires when an item is added or removed from the list of active add-ins. You probably will need to write code for this method only if you are writing an add-in that manages other add-ins (as in the case of the Add-In Toolbar add-in).

The Life Cycle of an Add-In

It's time to see some code in action.Even if all add-ins differ from each other in their functionality and user interface, there clearly are some recurring patterns. This section introduces the routines that you're likely to find in a typical add-in.

Registration

To be recognized by the Visual Basic environment as an add-in, an application must meet the following two conditions:

  • The add-in must be an OLE server and correctly registered as such in the system Registry.
  • The complete name of its main Public class--the class that works as the connection with the VB IDE--must be listed in the VBADDIN.INI file.

Registering an Out-Process Add-In If the add-in is an out-process OLE server--in other words, it is an executable stand-alone program--it can be run from the Start menu or from the Windows Explorer without being registered in the Registry or the VBADDIN.INI file. This enables the add-in to perform the registration by itself, without the need of any other support utility. All OLE servers created in Visual Basic self-register themselves in the system Registry the first time they execute; thus, if you are building an executable add-in, you don't need to worry about the registration in the Registry, which will be handled by the VB runtime. However, it is up to you to register the add-in in the VBADDIN.INI file. This can be done using the code in Listing 43.1.

Listing 43.1 The Code that Registers the Add-In into VBADDIN.INI

Declare Function GetPrivateProfileString Lib "kernel32" _     Alias "GetPrivateProfileStringA" _     (ByVal AppName As String, ByVal KeyName As String, _     ByVal keydefault As String, _     ByVal result As String, ByVal resultSize As Long, _     ByVal filename As String) As LongDeclare Function WritePrivateProfileString& Lib "Kernel32" _     Alias "WritePrivateProfileStringA" _     (ByVal AppName$, ByVal KeyName$, ByVal keydefault$, _     ByVal FileName$)` the name of the addin (modify as required)Public Const AddInName = "PrjViewer"Sub Main()     Call RegisterAddInEnd SubSub RegisterAddIn()`------------------------------------------------------------` Add a reference in the VBADDIN.INI file`------------------------------------------------------------Dim result As String` skip the block if the add-in has been invoked by the OLE ` sub-system (in this case we are sure it is already` installed as an add-in)If App.StartMode = vbSModeStandalone Then        ` try to read the entry in VBADDIN.INI     result = Space$(256)     GetPrivateProfileString "Add-Ins32", AddInName & ".Connect", _          "***", result, _          Len(result), "vbaddin.ini"     If Left$(result, 3) = "***" Then          ` the entry is not there, so we must record it          WritePrivateProfileString("Add-Ins32", _               AddInName & ".Connect", _               "0", "vbaddin.ini")     End IfEnd IfEnd Sub

The logic behind this routine is simple: when the program is launched, its Main procedure is executed, which in turn calls the RegisterAddIn routine. This routine uses the App.StartMode property to discern if the program is being activated by the user (vbSModeStandalone) or by the OLE subsystem (vbSModeAutomation); in the latter case, it obviously means that the program is already running as an add-in; therefore, the registration procedure can be conveniently skipped.

The rest of the procedure searches the VBADDIN.INI file for the server-name.class-name string, where server-name is the name of the add-in project, as appears in the Project Properties dialog box, and class-name is the name of the class that is instantiated by Visual Basic (the name of this class is often "Connect," "Wizard," or something similar). You can change the name of the project in the Project Properties dialog box (see Figure 43.8). If the search fails, the following line is appended to the file:

PrjViewer.Connect=0

FIG. 43.8
The name of the add-in listed in the VBADDIN.INI file is in the form server-name.class-name; you can modify the server name from the Project Properties dialog box.

On the other hand, if the file already contains a reference to the add-in, it is left undisturbed. This approach does not modify the current activation status of the add-in (for example, 1 if the add-in has to be loaded at Visual Basic's startup, otherwise 0).

While it might be possible to manually open the VBADDIN.INI file and perform the search and creation of the line of text, the RegisterAddIn procedure follows a different, simpler approach. It uses a couple of Windows API functions that are very useful when dealing with INI files.

The GetPrivateProfileString reads an INI file for a given key and returns that associated value, or the provided default value if the key is not found. In this case, the routine searches for the PrjViewer.Connect key and returns *** if it is not found; if this happens, it manually appends the key by using another API function, WritePrivateProfileString.

See "Calling Basic API and DLLs," Chapter 46

If your add-in often works as a standalone program, you may decide to save a call to the RegisterAddIn--which is a relatively slow process because it involves reading, and possibly writing, a file--and execute it only when the user specifies a particular switch on the command line:

Sub Main     If Command$ = "/REGISTER" Then          Call RegisterAddIn     End IfEnd Sub

Registering an In-Process Add-In Unfortunately, in-process add-ins do not have a chance to run as stand-alone programs and therefore cannot register themselves either in the system Registry or in the VBADDIN.INI file. If an in-process add-in is running, it means that Visual Basic has already found a way to invoke it; therefore, registration is no longer needed. Hence, it is evident that an in-process add-in can't register itself and, instead, has to rely on some external setup procedure to do it. The following instructions provide a "manual" procedure that you should follow to get your add-in up and running.

First, you need to register the add-in in the system Registry. The simplest way to accomplish this task is to use the RegSvr32.exe utility that comes on the Visual Basic CD-ROM, in the /Tools/RegUtils directory; just run these commands from the command prompt:

copy myaddin.dll c:/vb5c:/vb5/Tools/RegUtils/RegSvr32.exe c:/vb5/myaddin.dll

Of course, you need to modify the paths to match the directory names on your system.

At this point, you have to edit only the VBADDIN.INI file to inform Visual Basic that a new add-in is available. You can do this in a number of ways--using Notepad, for example--but the simplest one is, by far, executing the RegisterAddIn procedure from the Immediate window of the VB environment.

Connection

When Visual Basic needs to activate an add-in, it looks in the VBADDIN.INI file and creates an instance of a class with that name; then it invokes the OnConnection method of the IDTExensibility interface of that instance to let the add-in know that it is now active. Here is the syntax for this method:

`--- class ConnectImplements IDTExtensibilityPrivate Sub IDTExtensibility_OnConnection(ByVal VBInst As Object, _     ByVal ConnectMode As vbext_ConnectMode, _     ByVal AddInInst As VBIDE.AddIn, custom() As Variant)End Sub

Note that you really don't have to type the method definition yourself. As soon as you specify that the Connect class exposes the IDTExtensibility secondary interface, you'll find the four methods that belong to this interface right in the Visual Basic Code window (see Figure 45.9).

FIG. 43.9
If the Connect class exposes the IDTExtensibility interface, you have its four methods ready in the upper-right combo box.

Let's see the meaning of each argument passed to the OnConnection method. The VBInst object is a reference to the VBE object that represents the root of the VB IDE that is invoking the add-in. Please note that there could be more than just one instance of VB running on the machine, and the add-in has to know which one has called it. For this reason, it has to save this reference in a Public property of the class itself, as follows:

`--- class ConnectImplements IDTExtensibility` the instance of the VB IDEPublic VBInstance As VBIDE.VBEPrivate Sub IDTExtensibility_OnConnection(ByVal VBInst As Object, _     ByVal ConnectMode As vbext_ConnectMode, _     ByVal AddInInst As VBIDE.AddIn, custom() As Variant)     `save the vb instance     Set VBInstance = VBInstEnd Sub

Why can't you use a global variable in a BAS module? Again, never forget that the add-in is an OLE server and therefore can be invoked by more than one VB environment (however, this is true only for out-process servers: in-process servers always belong to only one instance of the VB environment). Hence, if the VBInst value is saved in a global, shared variable, then when the second instance of VB activates the add-in, it overwrites the value stored there by the first instance. This is not a problem if you store the reference to the VBE object in a public property of the Connect class--in this case, each instance of the class will refer to its local value.

The ConnectMode argument informs the add-in when and how it is being activated. It can be one of the following symbolic constants:

  • vbext_cm_Startup The add-in is being activated as part of the Visual Basic's startup process (this means that the corresponding entry in VBADDIN.INI was 1).

  • vbext_cm_AfterStartup The user has activated the add-in from the Add-In Manager dialog box (this means that the corresponding entry in VBADDIN.INI was 0).

  • vbext_cm_External The add-in is being activated by another program, most likely the Add-In Toolbar (this means that the corresponding entry in VBADDIN.INI was 0 or missing completely).

AddInInst is a reference to the Addin object held in the VBE hierarchy and corresponds to the add-in being activated. The meaning of this argument becomes apparent when exploring the VB IDE object tree; however, for now, suffice it to say that this argument is rarely used. Likewise, the custom() array is of no use in most add-ins.

If the add-in has been connected at VB5 startup, the OnStartupComplete method is fired some time after the OnConnection method, when the IDE has completed its loading and all add-ins are ready to be used. Basically, there are two reasons for writing code within this method. One reason is that you want to find out which other add-ins are currently activated in the environment. The second reason is that you need to show a form or some other message to the VB programmer, as in the following code:

Private Sub IDTExtensibility_OnStartupComplete(custom() As Variant)     ` this add-in interacts with the user through a form     frmAddin.ShowEnd Sub

It is important to understand that you cannot show a form from within the OnConnection method because the IDE is still loading and is not stable. On the other hand, as stated previously, the OnStartupComplete method is never called for those add-ins that are loaded manually through the Add-In Manager or the Add-In Toolbar. This is an interesting problem because you must differentiate between two cases: if the add-in is loaded when the IDE is launched, you must show its form from within the OnStartupComplete method, whereas, if the add-in is loaded manually, the form must be shown from within the OnConnection method because the OnStartupComplete method will never be called. Listing 43.2 shows a complete implementation of this concept.

Listing 43.2 A Typical OnConnection Method

`--- class ConnectImplements IDTExtensibility` the instance of the VB IDEPublic VBInstance As VBIDE.VBEPrivate Sub IDTExtensibility_OnConnection(ByVal VBInst As Object, _     ByVal ConnectMode As vbext_ConnectMode, _     ByVal AddInInst As VBIDE.AddIn, custom() As Variant)     `save the vb instance     Set VBInstance = VBInst          ` show the form if the add-in is being loaded manually     If ConnectMode <> vbext_cm_Startup Then          ShowForm     End IfEnd SubPrivate Sub IDTExtensibility_OnStartupComplete(custom() As Variant)     ShowFormEnd SubPrivate Sub ShowForm()     frmAddin.Show     ` add here all the other initialization code ...End Sub

Note that a separate ShowForm routine was created so that more code can easily be added to be executed when the add-in finally becomes visible. The same approach should be followed if your add-in has to interact with other add-ins, which names are not available during the OnConnection method if the ConnectMode argument is equal to vbext_cm_Startup.

Adding User Interface Elements

If the add-in immediately shows a form and such form represents the only way for the VB programmer to interact with the add-in, you may skip this section. However, most add-ins do not use this approach; instead, they add some user interface element to the VB IDE--such as a menu item or a toolbar button--so that programmers can invoke them anytime they actually need its services.

Adding an interface element to the VB IDE is rather simple, but complete comprehension of this process requires an in-depth knowledge of the IDE object hierarchy, which is dis- cussed later in this chapter. For now, suffice it to say that the VB environment exposes the CommandBars collection, where each CommandBar object is either a menu or a toolbar. The first element in this collection--the VBInstance.CommandBars(1) object--is the main IDE menu, and each member of the collection can be referenced by using a name, as in VBInstance.CommandBars("Tools").

Each CommandBar object contains a Controls collection, which is a collection of CommandBarControls objects that lets you reference all the items in a menu, or the buttons of a toolbar. Thus, VBInstance.CommandBars("Tools").Controls(1) is the first item in the Tools menu, which can also be referred to as VBInstance.CommandBars("Tools").Controls ("Add Procedure...").

 


CAUTION: When referencing a menu or toolbar item, it is preferable that you use its caption, rather than its numerical index in the menu or the toolbar, because users can customize the VB environment and move all interface items according to their tastes.

 

Also, the string used as a key in the CommandBars or Controls collection must match exactly what appears on the menu, including the trailing ellipsis, if any. However, if the caption includes a hot key, you optionally may omit the ampersand (&) character. For instance, the following two references are equivalent:

 

VBInstance.CommandBars("Tools")

VBInstance.CommandBars("&Tools")


 

Because it is a collection, you also can add new items to the Controls collection by using the Add method. In other words, you effectively can add new menu items, as well as set their captions and other properties, as in the following example:

Dim newMenuItem As Office.CommandBarControlSet newMenuItem = VBInstance.CommandBars("Add-Ins").Controls.Add(1)newMenuItem.Caption = "My Great Add-in"

You have added a new menu item, but you don't know how to receive notification when the user selects it. In other words, your add-in has made itself available in the Add-Ins menu, but has no means to activate itself when the user needs to use it.

To receive notification when the user selects the new menu item, you must declare an object of yet another type, CommandBarEvents, and use it to receive an event when the menu item is selected. The source code in Listing 43.3 shows how to perform those tasks.

Listing 43.3 How to Receive an Event when the User Clicks a Menu Item

` this is a form-level variableDim WithEvents MenuHandler As CommandBarEventsPrivate Sub Form_Load()     Set MenuHandler = VBInstance.Events.CommandBarEvents(newMenuItem)End SubPrivate Sub MenuHandler_Click(ByVal CommandBarControl As Object, _     handled As Boolean, CancelDefault As Boolean)          ShowFormEnd Sub

The CommandBarEvents object exposes only one event, Click, which obviously fires when the user selects the corresponding menu item or the toolbar button (depending on the type of the CommandBar object). Within the Click event, you can do whatever you want with the VBIDE by using the many objects that will be introduced later in this chapter.

Disconnection

An add-in can be shut down in several ways (through the Add-Ins Manager or automatically when VB closes, for instance), but this usually is of no concern to the add-in programmer, in the sense that the sequence of the operations to be performed most often is identical in all cases.

Generally speaking, in the OnDiconnection method, the add-in should release all the resources it took previously, destroy all the user interface elements it created, and so on. Here is a typical OnDisconnection method, which completes the previous example:

Private Sub IDTExtensibility_OnDisconnection(ByVal RemoveMode _     As vbext_DisconnectMode, custom() As Variant)          `delete the menu item         ewMenuItem.Delete          ` unload the form          Unload frmAddIn          Set frmAddIn = NothingEnd Sub

When the OnDisconnection method is fired, you should assume that your add-in is not executing anymore and, in fact, you shouldn't try to keep it alive (by keeping a form visible, for example). You should be aware that, after this method is executed, the VBInstance reference is not valid anymore and you absolutely should not reference it or any of its dependent objects.

A Simple Example

You can finally put everything together and prepare your first complete example of a working add-in. Because the complete VB IDE object model hasn't been introduced yet, you are still unable to write complex (and very useful) add-ins at this point; nevertheless, you can put to good use what you have read thus far.

A Simple Property Code Generator

Even the simplest example should be somewhat useful; therefore, this example prepares an add-in that many of you will appreciate: a simple code generator that improves upon the "Add Procedure" standard menu command. As you may recall, this command lets you quickly create subs, functions, and Get/Let property procedure pairs, but has a number of shortcomings: It creates code for Variant properties only, and it knows nothing about Property Set procedures or Friend keywords. Above all, it isn't any help when you have to create properties that wrap around member variables.

See "Working with Procedures," Chapter 17

A member variable is a variable that is private to a class and whose value is exposed to the outside world using a couple of wrapper property procedures. Suppose you have a LastName public property--you can implement it in two different ways. The simplest way to implement it is to use a Public variable, as in:

Public LastName As String

In the second method, which is preferred by most VB developers, you wrap the real value of the property between a couple of Property procedures, as follows:

Private m_LastName As StringPublic Property Get LastName() As String     LastName = m_LastNameEnd PropertyPublic Property Let LastName (newValue As String)     m_LastName = newValueEnd Property

Why are property procedures to be preferred to member variables? For one thing, these property procedures let you have greater control over which value is assigned to the property, which helps to create more robust and bug-free applications. Here is a simple example:

Public Property Let LastName (newValue As String)     ` refuse to assign null strings     If newValue <> "" Then          m_LastName = newValue     End IfEnd Property

On the other hand, writing all this code just for a property is a lot of work, and in this respect, the Add Procedure command is almost completely useless. However, you can build an add-in that does exactly what you need it to do. This add-in will, in fact, let you set all your desired options and will copy the generated VB code to the system clipboard, ready to be pasted in the appropriate position in the project that is under development.

The PropertyBuilder Project

You can create an add-in in several ways, one of which is loading the Add-In template project from the dialog box that appears when you invoke the File-New Project command. If this command doesn't lead you to the New Project dialog box shown in Figure 43.10, you should set the appropriate option button in the Environment tab of the Tools-Option dialog box.

FIG. 43.10
The quick way to create an add-in: simply invoke the File-New Project menu command and select the correct template.

However, your current PropertyBuilder project does not use the provided add-in template, which is too sophisticated for this simple example. Besides, it is more interesting to see what occurs "behind the scenes" than simply letting a template do all the work. To create your add-in from scratch, just follow these simple steps:

1. Execute the File-New Project command and create an ActiveX EXE project.

2. Change the name of the class module to Connect.

3. Add a regular form and name it frmAddin.

4. Add a regular BAS module and name it AddIn.bas.

5. Invoke the Project-Properties command and modify the project's attributes as shown in Figure 43.11.

FIG. 43.11
All the project attributes you need to create an add-in.

6. Go to the Component tab of the same dialog box and set the StartMode option to ActiveX Component.

7. Press F2 to show the Object Browser, right-click the Connect class in the leftmost pane, and select the Properties menu command. In the Member Options dialog box, set the description of the Connect class equal to the text that you want to appear in the Add-In Manager (see Figure 43.12).

FIG. 43.12
The description associated to the Connect class is the string that users will see in the Add-In Manager.

The project name, as set in Step 5, is very important because it will be added to the class of the Connect class to form the complete name of the class, as it will appear in the VBADDIN.INI file. In this case, the resulting name is PropertyBuilder.Connect.

This last step is somewhat undocumented in the language manuals. In fact, many programmers mistakenly believe the description that appears in the Add-In Manager comes from the Description attribute of the add-in project, while it actually comes from the Description attribute of the Connect class. This might sound counterintuitive, but it makes perfect sense: after all, the same add-in project could theoretically install several add-ins (even though I never saw such a program), thus the add-in's description should be an attribute of the individual Connect class, not of the project.

The Connect Class

See Listing 43.4 for the code in the Connect class that reacts to VB IDE notifications.

Listing 43.4 CONNECT.CLS--The Source Code of the Connect Class

Option ExplicitImplements IDTExtensibility` the VBIDE instance connected to this addinPrivate VBInstance As VBIDE.VBE` the new menu item added to the Add-Ins menuPrivate newMenuItem As Office.CommandBarControl` the Event object used to get a notification when` the user clicks on newMenuItemPrivate WithEvents MenuHandler As CommandBarEvents` this is the form corresponding to this instance of the classDim frmAddin As New frmAddinPrivate Sub IDTExtensibility_OnConnection(ByVal VBInst As Object, _     ByVal ConnectMode As vbext_ConnectMode, ByVal AddInInst As VBIDE.AddIn, _     custom() As Variant)     On Error GoTo OnConnection_Error        `save the VB instance     Set VBInstance = VBInst        If ConnectMode <> vbext_cm_Startup Then          ` if no AfterStartup method will be called, this is the right          ` place to add a menu item to the Add-Ins menu          CreateMenuItem     End If     Exit Sub   OnConnection_Error:     MsgBox "Unable to correctly connect the add-in", vbCriticalEnd SubPrivate Sub IDTExtensibility_OnStartupComplete(custom() As Variant)     ` if the add-in is being loaded at VB startup, this is     ` the right place to add a menu item to the Add-Ins menu     CreateMenuItemEnd SubSub CreateMenuItem()     Dim addInMenu As Object     On Error Resume Next        ` search the Add-Ins menu, exit if not found     ` (very unlikely)     Set addInMenu = VBInstance.CommandBars("Add-Ins")     If (Err <> 0) Or (addInMenu Is Nothing) Then Exit Sub        ` add a new item to the Add-In menu, after existing ones     Set newMenuItem = addInMenu.Controls.Add(1)     ` set its caption to a suitable string, with a hotkey    ewMenuItem.Caption = "&Property Builder..."        ` create a menu handler object that will receive     ` a click event when the user selects the menu command     Set MenuHandler = VBInstance.Events.CommandBarEvents(newMenuItem)End SubPrivate Sub MenuHandler_Click(ByVal CommandBarControl As Object, _     handled As Boolean, CancelDefault As Boolean)          frmAddin.ShowEnd SubPrivate Sub IDTExtensibility_OnDisconnection(ByVal RemoveMode _     As vbext_DisconnectMode, _     custom() As Variant)          On Error Resume Next             `delete the menu item         ewMenuItem.Delete          ` clear the menu handler object          Set MenuHandler = Nothing          ` unload the form, if necessary          Unload frmAddin          Set frmAddin = NothingEnd SubPrivate Sub IDTExtensibility_OnAddInsUpdate(custom() As Variant)     ` a placeholder remarkEnd Sub

The OnConnection method of the IDTExtensibility interface must save the reference to the calling VBIDE object in the VBInstance variable and decide whether it is the right time to add a menu item to the Add-Ins menu.

The OnStartupComplete method is much simpler. It will be invoked by VB only for those add-ins that are loaded when the environment is launched, and in this context its only purpose is to create the menu item, which was not possible within the OnConnection method:

See what happens in the CreateMenu procedure. Basically, it does three things. First, it checks that the Add-Ins menu actually is there (in the unlikely case that the add-in is being installed by something different from the VB environment), then it adds a new element to the Controls collection of the Add-In menu. Finally, it creates a menu-handler object that then will be used to receive an event when the user clicks the brand new "Property Builder" menu item:

Now that you have a menu handler object, you can write code for its Click event, but there is a subtle detail to which you must pay attention. While the project does include a frmAddIn form, here you actually are accessing a local variable with the same name, and declared as:

Dim frmAddin As New frmAddin

In other words, each instance of the Connect class will create a new frmAddIn form and will show a different form. Again, this permits you to create a single add-in that is capable of serving several instances of the VB environment.

The OnDisconnect method's purpose is to undo whatever was done during the connection stage.

Lastly, you have to create an empty OnAddInsUpdate method. This is necessary because you used the Implements keyword to create the IDTExtensibility secondary interface, and this program won't even compile until this method is implemented in the class. The simplest way to complete the implementation of the IDTExtensibility secondary interface is creating an OnAddInsUpdate method with a remark in it.

The frmAddIn Form

When the user invokes the new "Property Builder" command in the Add-Ins menu, a MenuHandler_Click event will be raised, which in turn will pop up a form that lets the user enter the details of the Property procedure that he or she is about to create (see Figure 43.13).

FIG. 43.13
The frmAddIn form at design time. The cboDataType combo box already contains the names of all native VB's data types.

Most of the code in this form serves to implement a sensible interaction with the user (see Listing 43.5):

Listing 43.5 The Code in frmAddin.frm that Reacts to User's Action

` this is the standard prefix used to build Member variable namesConst DEFAULT_PREFIX As String = "m_"Private Sub Form_Load()     ` restore defaults     txtVariable = DEFAULT_PREFIX     cboDataType.Text = "String"End SubPrivate Sub txtName_Change()     ` build the name of the member variable     txtVariable.Text = DEFAULT_PREFIX & txtName.TextEnd SubPrivate Sub cboDataType_Click()     ` delegate to the Change event     Call cboDataType_ChangeEnd SubPrivate Sub cboDataType_Change()     ` enable or disable the "Property Set" checkbox according     ` to which data type has been selected     Select Case LCase$(cboDataType.Text)          Case "integer", "long", "single", "double", _               "currency", "string", _               "date", "byte", "boolean"                    chkObject.Enabled = False          Case "object"                    chkObject.Enabled = True                    chkObject.Value = vbChecked          Case Else                    chkObject.Enabled = True     End SelectEnd SubPrivate Sub chkObject_Click()     chkSet.Enabled = chkObject.ValueEnd SubPrivate Sub cmdCopy_Click()    Clipboard.setText GeneratedCode    Unload MeEnd SubPrivate Sub cmdCancel_Click()    Unload MeEnd Sub

Both the Copy and the Cancel command buttons cause the form to be closed, but the former also places the generated code into the system clipboard.

Next comes the smart part of the program that generates the code, based on the contents of the fields on the form. The code in Listing 43.6 should be rather simple to follow, without any further comments.

Listing 43.6 The Routine that Actually Builds the Code of Property Procedures

Private Function GeneratedCode() As String     Dim codeText As String     Dim scopeText As String     Dim setText As String        ` retrieve the scope     If optScope(0).Value Then          scopeText = "Private "     ElseIf optScope(1).Value Then          scopeText = "Public "     Else          scopeText = "Friend "     End If        ` should we use "set" when assigning?     If chkObject.Enabled And chkObject.Value = vbChecked Then          setText = "Set "     End If        ` create the declaration of the member variable     codeText = "Private " & txtVariable & " As _     " & cboDataType & vbCrLf & vbCrLf        ` add the Property Get code, if requested     If chkGet.Value Then          codeText = codeText & scopeText & "Property Get " _          & txtName & "() As " _               & cboDataType.Text & vbCrLf _               & vbTab & setText & txtName & " = " _               & txtVariable & vbCrLf _               & "End Property" & vbCrLf & vbCrLf     End If        ` add the Property Let code, if requested     If chkLet.Value Then          codeText = codeText & scopeText & "Property Let " _               & txtName _               & "(newValue As " & cboDataType.Text & ")" _               & vbCrLf _               & vbTab & txtVariable & " = newValue" _               & vbCrLf _               & "End Property" & vbCrLf & vbCrLf     End If        ` add the Property Let code, if requested     If setText <> "" And chkSet.Value Then          codeText = codeText & scopeText & "Property Set " _               & txtName _               & "(newValue As " & cboDataType.Text & ")" _               & vbCrLf _               & vbTab & setText & txtVariable & " = newValue" _               & vbCrLf _               & "End Property" & vbCrLf & vbCrLf     End If        GeneratedCode = codeTextEnd Function

The Addin.Bas Module

The only module left to illustrate is Addin.Bas, that contains the Sub Main procedure that is fired when the add-in starts its execution.

Apart from the Main procedure, it only includes a routine that adds the proper string to the VBADDIN.INI file:

Declare Function WritePrivateProfileString& Lib "Kernel32" Alias _     "WritePrivateProfileStringA" (ByVal AppName$, ByVal KeyName$, _     ByVal keydefault$, ByVal FileName$)Sub Main     frmAddin.ShowEnd SubSub RegisterAddin()     WritePrivateProfileString "Add-Ins32", "PropertyBuilder.Connect", "0", "vbaddin.ini"End Sub

You may also use the more sophisticated RegisterAddIn routine, illustrated in Listing 43.1, if you want to.

Testing the Add-In

Your add-in is finally completed, and you only have to install it and check that it behaves as expected. This is the simplest part of the whole job; just follow these steps:

1. Run the RegisterAddIn procedure from within the Immediate window.

2. Press Ctrl+F5 to run the program with the Full Compile option.

3. Start another instance of VB5, open the Add-In Manager dialog box, and check that your add-in is there and ready to be activated (see Figure 43.14). Select the corresponding check box and close the dialog box.

FIG. 43.14
The Property Code Builder add-in is finally available in the VB environment.

4. Open the Add-Ins menu: a new "Property Builder" command should have been added to the bottom of the menu.

To test the add-in, run it from the Add-Ins menu and try to create several kinds of properties, including read-only properties and object properties (see Figure 43.15).

FIG. 43.15
It takes less than ten seconds to create a property; the generated code is visible in the Code window behind the add-in dialog box.

Of course, you are by no means limited to this first, simple version of the add-in, and you are encouraged to improve it with many other features. For instance, you could add the support for one or more arguments, or for automatic insertion of a remark banner reporting who wrote this property, and when it was written. Another interesting addition might be the capability to define multiple properties and then paste them all into the VB project in just one operation.

To produce a stand-alone add-in, you must compile it into an EXE file. Note that this particular add-in may also work as a stand-alone program because it really doesn't interact with the VB IDE object model. In that case, you must run it from the Start menu or a desktop icon instead of as an item in the Add-Ins menu.

The VB IDE Object Model

Now that you know how to create add-ins, you probably are wondering what you can do with them. The answer is: Everything that you can do with your mouse and keyboard from within the VB environment, including opening and closing windows, creating new projects and files, finding routines and adding new ones, manipulating controls on a form, and so on. Well, in truth, there are a few things that are still beyond the reach of add-ins, but they are mostly minor issues (adding and removing breakpoints and bookmarks or trapping user actions within a code window, for example).

To leverage the power of VB5 add-ins, you have to spend some time on the VB IDE Object Model, which is rather complex. This section introduces you to the intricacies of the VB IDE Object Model, but there simply is not enough space available to explain every single detail. In many cases, you'll have to gather more information from the Object Browser and online help, and write some test programs to see if things work as expected. However, all the main objects of the hierarchy and their most useful properties and methods will be explained, and you can use the many code samples in this section as guidelines for your own complete add-ins.

One of the difficulties you'll find while exploring this object model is that the model itself often is recursive, so you have to know where to stop when you dive into its many branches. One example is the CommandBarsControl objects--they expose a Controls collection (menu items or toolbar buttons) that, in turn, can be other CommandBarControls (sub-menus, in this case).

The VBE Object

The VBE is the object at the top of the object model, and represents the VB environment itself. This object is passed to the add-in as an argument of the OnConnection method, and the add-in is supposed to store it somewhere for further reference (see Figure 43.16).

FIG. 43.16
The VB IDE object hierarchy; subsequent figures show the various object levels in more detail.

The following list describes the main level objects, collections, and properties:

  • Addins collection Includes all the add-ins active in the environment.

  • CodePanes collection All the code windows that are currently open.

  • CommandBars collection All the top level menus and toolbars in the environment, including toolbars that are not currently visible.

  • VBProjects collection All the projects that are loaded in the environment.

  • Windows collection All of the windows that belong to the environment, excluding the main window, but including all the windows that are currently hidden.

  • Events object This doesn't represent a real, visible object, and is only used to return objects that handle events generated by the user within the environment (for example, when the user loads a new project or adds a new control).

  • ActiveCodePane property The code window that is currently active.

  • ActiveVBProject property The project that is currently active.

  • ActiveWindow property The window that is currently active.

  • MainWindow property The main window of the VB IDE, in other words, the window that hosts the main menu and acts as a container for all the others (if the IDE is in MDI mode).

  • SelectedVBComponent property The VBE object's SelectedVBComponent property, used to reference the active component.

  • IDTExtensibility interface An abstract class used to define an interface, the set of properties and methods exposed by a class.)

The VBE object also exposes a number of properties that you can use to retrieve additional information regarding the current state of the environment. For instance, the DisplayModel property lets you set or retrieve the current display model, MDI or SDI; the FullName property returns the full path name of the Visual Basic application. Finally, the Quit method lets you programmatically exit the environment.

The VBProjects Collection and the VBProject Object

The VBProjects collection holds all the VB projects that are currently loaded in the environment, so you can enumerate them by using a simple For Each loop. Alternatively, you can exploit the VBE.ActiveVBProject object that points to the active project. In all cases, you end up with a reference to a VBProject object (see Figure 43.17).

FIG. 43.17
The VBProjects collection and its dependent objects.

Here is a short code snippet that shows how to load into a list box the name and the path of all loaded projects, and how to highlight the project that is currently active:

Sub LoadProjects(Target As Listbox)     Dim vbp As VBIDE.VBProject     Target.Clear     For Each vbp In VBInstance.VBProjects          Target.AddItem vbp.Name & " - " & vbp.FileName          If vbp Is VBInstance.ActiveVBProject Then               Target.ListIndex = Target.ListCount - 1          End If    extEnd Sub

The VBProjects collection also includes a number of interesting properties and methods. The StartupProject property lets you set or get a reference to the project that runs when you press F5; the FileName property returns the fully qualified path of the VBG file that gathers all the projects in a group. The Add method lets you add a blank project of the desired type (standard EXE, ActiveX DLL or EXE, ActiveX Control or Document, and so forth), and you can load an existing project with AddFromFile, or by using a template with AddFromTemplate. Because you can use the latter two methods to add a VBG file, they return a VBNewProjects collection of VBProject objects that you can enumerate to learn which projects have been added, as follows:

` load a project or project group and list which projects have been addedDim vbg As VBIDE.VBNewProjectsDim vbp As VBIDE.VBProjectSet vbg = VBInstance.VBProjects.AddFromFile "c:/vb5/prova.vbg"For Each vbp In vbg     Debug.Print vbp.Name & " - " & vbp.FileNameEnd Sub

The VBProject object is rather powerful and complex. It exposes a large number of properties that let you query or modify what the programmer has typed into the Options dialog box. A number of properties are rather self-explanatory, such as FileName, Description, HelpFile, HelpContextID, Type (Standard EXE, ActiveX Control or Document, ActiveX DLL or EXE), StartMode (stand-alone or ActiveX Component), and BuildFileName (the name of the EXE or DLL file that will be produced by the compilation process).

There are also a few useful methods, such as MakeCompiledFile and SaveAs. The AddToolboxProgID command lets you add an OCX to the Toolbox, and the ReadProperty/WriteProperty pair enables you to read and modify selected portions of the VBP file, including the version of the project and all the compiler optimization settings.

The VBProject object also exposes the References and the VBComponents collections; the latter is undoubtedly one of the more interesting elements in the hierarchy, and is explained in more detail in the next section.

The VBComponents Collection and the VBComponent Object

The VBComponents collection gives you access to all the individual components of the projects (see Figure 43.18), including forms, code and class modules, ActiveX Control and Document designers, and so on. You can use its StartupObject to learn which VBComponent object is executed when F5 is pressed (usually, the code module that contains Sub Main, or the startup form, but it could also be "(none)" for ActiveX components).

FIG. 43.18
The VBComponents collection and its dependent objects.

This collection also exposes three methods for adding new items: Add creates a blank component of a given type (code or class module, form, and so forth); AddFromTemplate and AddFromFile let you create a component from a template or load an existing component, respectively. Regardless of how you load a component, you can unload it by using the Remove method.

The VB environment does not offer a quick way to add multiple components in one single operation and it requires that you issue several Project-Add File commands. Listing 43.7 shows a useful routine that you can encapsulate in your add-ins to automatically add all the files in a given directory:

Listing 43.7 Adding Multiple Components

Sub AddMultipleComponents(ByVal path As String, _     Optional extension As String)     Dim filename As String     Dim vbc As VBIDE.VBComponent     ` add a trailing backslash, if needed     If Right$(path, 1) <> "/" Then path = path & "/"        If extension <> "" Then          ` if an extension was given, add all components          ` with that extension          filename = Dir$(path & "*." & extension)          Do While filename <> ""               Set vbc = VBInstance.ActiveVBProject._                    VBComponents.AddFile(path & filename)               ` this is necessary to work around a VBIDE bug               vbc.IsDirty = True               vbc.IsDirty = False               ` read the next file in the directory               filename = Dir$          Loop     Else          ` otherwise, recursively call this routine with          ` all standard extensions          AddMultipleComponents path, "frm"          AddMultipleComponents path, "bas"          AddMultipleComponents path, "cls"          AddMultipleComponents path, "ctl"          AddMultipleComponents path, "dob"          AddMultipleComponents path, "pag"     End IfEnd Sub

The AddMultipleComponents routine shows a couple of interesting points. First, it is a simple but effective example of how to use recursion: if the routine is called with just one argument, it recalls itself once for each of the existing extensions. Second, it works around a bug in the environment: under some circumstances, the VB IDE ignores the components added through the AddFile method of the VBComponents. To prove it, comment out the two lines containing a reference to the IsDirty property and run the procedure. If you issue a File-New Project command, VB will ask if you want to save modified files, but won't show all the components that have been added in this way.

The VBComponent object is also rich of properties and methods. The meaning of a few of them is rather evident: Name, Type, Description, HelpFile, HelpContextID, and IsDirty. The FileNames and FileCount properties, however, require a more detailed explanation: some components--namely, Forms, UserControls, and UserDocuments--are associated to designer modules and are stored into two different files, one for the code and regular properties, the other to hold binary properties. For instance, forms are stored in FRM and FRX files and, similarly, UserControl modules require CTL and CTX files. In this case, the FileCount property returns 2 and you can query the names of the files with FileNames(1) and FileNames(2). When the component is associated to a designer, you also can query the HasOpenDesigner Boolean property, which returns False if the form is currently closed.

The Properties collection stores all the properties related to a component. Such properties correspond to what you see when you press the F4 key when the component has the focus. Code modules have only one item in this collection (Name), class modules have two (Name and Instancing), and all components that correspond to a designer may have dozens. Here is a simple routine that shows all the properties for the component that is currently selected:

Dim prop As VBIDE.PropertyOn Error Resume NextFor Each prop In VBInstance.SelectedVBComponent.Properties     Print prop.Name & " = " & prop.ValueNext

This code references the active component by using the SelectedVBComponent property of the VBE object. Note that you must add error trapping because there might be one or more properties that return an object or that have several values, and in both cases, the Print method will fail. You can use this collection to modify an existing property, as in:

VBInstance.SelectedVBComponent.Properties("Width").Value = 1000

There are two more items that are worth examining: the CodeModule property returns a reference to the code module associated to the component; the Designer property returns a reference to the VBForm object that represents the designer on which you place controls. Both types of objects will be explained in a moment.

VBComponent objects also expose a few methods. InsertFile has the same effect as the Edit, Insert File menu command (it merges the contents of a file at the current position of the cursor), Activate makes the component active, Reload discards all changes from the last Save operation, and SaveAs writes the component to disk using a different file name.

The VBForm and VBControl Objects

Don't confuse the VBForm object with the usual form object: in the add-in jargon, the visible form object corresponds to the VBComponent object, while the VBForm is the designer on which you place controls, and has no correspondence to any visible entity in the environment. In fact, you can enumerate a form's properties by using the Properties collection of the corresponding VBComponent object, as seen above, not of the VBForm object (which doesn't expose any Properties collection). To retrieve the VBForm object related to a given VBComponent, you just query its Designer property.

The VBForm object indeed has a very limited use in that it only exposes the Paste and SelectAll methods and the CanPaste boolean property. Its main function is as the container for three important collections: VBControls, SelectedVBControls, and ContainedVBControls (see Figure 43.19).

FIG. 43.19
The VBForm and VBControl objects.

All of them contain VBControl objects, which correspond to the controls that a VB programmer picks from the toolbox and drops onto the form. The only difference among these three collections is in the controls they contain: the VBControls collection gathers all the controls on the form; the SelectedVBControls collection returns only the controls that are currently selected; the ContainedVBControls collection returns only the controls that are on the form's surface (as opposed to controls contained in other controls, such as a frame or a picture box).

You can create new controls on the form by simply adding a new VBControl object to the VBControls or ContainedVBControls collection, using their Add method. Here's how you can add a textbox to the currently selected form (or UserControl or UserDocument):

Dim frm As VBIDE.VBFormDim ctr As VBIDE.VBControlOn Error Resume NextSet frm = VBInstance.SelectedVBComponent.DesignerSet ctr = frm.VBControls.Add("VB.TextBox")

After you have a reference to a VBControl object--retrieved from one of the collections or just created by yourself, as in the previous code example--you can modify its properties by using its Properties collection:

` set its namectr.Properties("Name") = "txtLastName"` move to the upper-left cornerctr.Properties("Left") = 0ctr.Properties("Top") = 0

You can do a lot of other interesting things by using these objects and collections. For instance, whenever you add a textbox to a form, VB initializes its Text property to something meaningless, such as "Text1", whereas in most cases, you will prefer to have it blank. What's worse is that, while you can usually select more controls on the form and use the Properties window to change all the properties they have in common, this is not possible with the Text property, and you have to blank this property for each individual control. Here is a better solution:

Dim ctr As VBIDE.VBControlFor Each ctr in VBInstance.SelectedVBComponent.Designer.VBControls     If ctr.ProgId = "VB.TextBox" Then          ctr.Properties("Text") = ""     End IfNext

This code clears the Text property for all the textbox controls on the active form. Since you probably want to do the same as well with other similar controls, such as combo boxes or MaskedEdit controls, there is an alternative solution that you might like more:

Dim ctr As VBIDE.VBControlOn Error Resume NextFor Each ctr in VBInstance.SelectedVBComponent.Designer.VBControls     ctr.Properties("Text") = ""Next

This code attempts to clear the Text property of each control on the active form, and relies on the On Error statement to avoid fatal errors if a given control does not support that property.

Finally, note that each VBControl object exposes a ContainedVBControls collection, which returns all the child VBControl objects, or Nothing if the control is not a container or doesn't contain any child control. Because it is perfectly legal for a container control to host other controls that work as containers--for example, a picture box that contains a frame control that contains an array of option buttons--this is another case of recursion in the object model. If you plan to write an add-in that explores all the controls on a form, you should take this issue into account.

The CodeModule Object

This probably is one of the most interesting objects in the VBE hierarchy, wherein it appears as a child of the VBComponent object. Each VBComponent object exposes a CodeModule property that returns a reference to the object that represents the source code behind a component.

Thanks to the many properties of the CodeModule object, it is possible to read and modify individual lines of code, single routines, or blocks of code of any size. The CountOfLines property returns the total number of lines in the module, while CountOfDeclarationLines returns the number of lines in the declaration section. If you know the name of a procedure, you can retrieve its position and length by using the ProcStartLine and ProcCountLines properties respectively. Note, however, that the value returned by the ProcStartLine property keeps remarks and blank lines into account: if you want to learn the position of the first actual line of code, you must use the ProcBodyLine property (this detail is not documented in the language manuals).

After you know in which lines you are interested, you may read them by using the Lines property, delete them by using the DeleteLines method, use the ReplaceLine method to replace the code, or insert new statements by using the InsertLines method. Finally, you can use the AddFromFile and AddFromString methods to merge the contents of a file or a string into the module.

To fully exploit the potential of the CodeModule object, you have to introduce one more element, the Members collection. This collection holds Member objects, which correspond to the individual items in the code. You can iterate on the Members collection to retrieve the name of each variable, event, and procedure in the module. The routine shown in Listing 43.8 fills a listbox with information on all the members of a given component:

Listing 43.8 Adding the List of Component Members to a Listbox

Sub MemberListToListbox(ctrl As ListBox, projectName _     As String, componentName As String)     `------------------------------------------------     ` Add the list of component members to a listbox     `------------------------------------------------     Dim cmp As VBIDE.VBComponent     Dim mbr As VBIDE.Member     Dim text As String        On Error Resume Next        ` get component reference - exit if error     Set cmp = VBInstance.VBProjects(projectName).VBComponents_          (componentName)     If Err Then Exit Sub            ` iterate on all members     For Each mbr In cmp.CodeModule.Members          text = mbr.Name          ` add member scope          Select Case mbr.Scope               Case vbext_Friend                    text = text & vbTab & "Friend"               Case vbext_Private                    text = text & vbTab & "Private"               Case vbext_Public                    text = text & vbTab & "Public"          End Select          ` add member type          Select Case mbr.Type               Case vbext_mt_Const                    text = text & vbTab & "Const"               Case vbext_mt_Event                    text = text & vbTab & "Event"               Case vbext_mt_Method                    ` this could be a real method or an ingoing event                    ` the following code is not bullet-proof                    If InStr(mbr.Name, "_") _                         And mbr.Scope = vbext_Private Then                         text = text & vbTab & "Event proc"                    Else                         text = text & vbTab & "Method"                    End If               Case vbext_mt_Property                    text = text & vbTab & "Property"               Case vbext_mt_Variable                    text = text & vbTab & "Variable"          End Select               If mbr.Static Then text = text & " Static"          ` add to the listbox          ctrl.AddItem text    NextEnd Sub

This procedure makes good use of the Name, Type, Scope, and Static properties of the Member object, but there are other properties that you might find interesting, such as Description, Hidden, and HelpContextID. In general, all those values that appear in the Procedure Attributes dialog box can be read or modified by using these properties. For instance, you may write an add-in that warns you if any Public property or method in a UserControl component is not associated to a description or a HelpContextID value (see Listing 43.9). Because Public properties and methods are those that are visible to the developer who uses your ActiveX control, you have to complete the documentation for the control:

Listing 43.9 An Add-In that Warns You if Public Properties Are Not Associated

Sub CheckAttributes(list As ListBox, projectName As String, _     componentName As String)     `----------------------------------------------------------     ` Check the attributes of all the members in a component     ` Fill a listbox with the names of all members whose     ` description or HelpContextID is missing     `----------------------------------------------------------     Dim cmp As VBIDE.VBComponent     Dim mbr As VBIDE.Member     On Error Resume Next        ` get component reference - exit if error     Set cmp = VBInstance.VBProjects(projectName).VBComponents(componentName)     If Err Then Exit Sub            ` iterate on all members     list.Clear     For Each mbr In cmp.CodeModule.Members          If mbr.Scope = vbext_Public Then               If mbr.Description = "" Or mbr.HelpContextID = 0 Then                    List.AddItem mbr.Name               End If          End If    NextEnd Sub

The CodePanes Collection and the CodePane Object

The CodePanes collection, as shown in Figure 43.20, is directly exposed by the root VBE object and represents all of the code windows that are open at a given time. Each CodePane object exposes several properties, such as TopLine (the first visible line in the window), CountOfVisibleLines (the number of lines showed in the window), and CodePaneView (which lets you alternate between procedure or full-module view). You can learn which lines are currently highlighted by using GetSelection, or move the selection with SetSelection. Here is a simple usage of the CodePanes collection:

` reset full module view for each code module` and scroll it to the first line of the moduleDim cpa As VBIDE.CodePaneFor Each cpa In VBInstance.CodePanes     cpa.CodePaneView = vbext_cv_FullModuleView     cpa.TopLine = 1Next

It is important to remember that CodePane objects do not give you access to the actual code, not immediately at least. However, each CodePane object exposes the underlying CodeModule object, which you use to read and modify the code shown in the window.

FIG. 43.20
The CodePanes collection and its dependent objects.

For instance, if you want to retrieve the code that is currently selected, you first must get a reference to the active code pane by using the ActiveCodePane property of the VBE object, then apply its GetSelection method, and finally, use the Lines method of the related CodeModule object to return the actual code. Listing 43.10 shows the complete routine.

Listing 43.10 A Procedure that Retrieves the Code Currently Highlighted

Function GetSelectedText() As String     Dim startLine As Long, startCol As Long     Dim endLine As Long, endCol As Long     Dim codeText As String     Dim cpa As VBIDE.CodePane     Dim cmo As VBIDE.CodeModule        On Error Resume Next        ` get a reference to the active code window and the            ` underlying module     ` exit if no one is available     Set cpa = VBInstance.ActiveCodePane     Set cmo = cpa.CodeModule     If Err Then Exit Sub        ` get the current selection coordinates     cpa.GetSelection startLine, startCol, endLine, endCol     ` exit if no text is highlighted     If startLine = endLine And startCol = endCol Then Exit Sub        ` get the code text     If startLine = endLine Then          ` only one line is partially or fully highlighted          codeText = Mid$(cmo.Lines(startLine, 1), startCol, _          endCol - startCol)     Else          ` the selection spans multiple lines of code          ` first, get the selection of the first line          codeText = Mid$(cmo.Lines(startLine, 1), startCol) & vbCrLf          ` then get the lines in the middle, that are fully highlighted          If startLine + 1 < endLine Then               codeText = codeText & cmo.Lines(startLine + 1, _               endLine - startLine - 1)          End If          ` finally, get the highlighted portion of the last line          codeText = codeText & Left$(cmo.Lines(endLine, 1), endCol - 1)     End If        GetSelectedText = codeTextEnd Sub

As you can see, getting the selected text is not immediate: the Lines property of the underlying CodeModule can retrieve entire lines only, while a selection can start or end at any column. For this reason, you must deal with different cases, depending on whether the selection spans multiple lines.

Modifying the text that appears in a code pane is a bit more difficult because you must resort to the Replace method of the related CodeModule object, and this method only works on single lines. The routine shown in Listing 43.11 converts the highlighted code to uppercase.

Listing 43.11 A Routine that Converts the Highlighted Text to Upper- or Lowercase

Sub ConvertSelectedText(Optional conversion As Long = vbUpperCase)     Dim startLine As Long, startCol As Long     Dim endLine As Long, endCol As Long     Dim codeText As String     Dim cpa As VBIDE.CodePane     Dim cmo As VBIDE.CodeModule     Dim i As Long        On Error Resume Next        ` get a reference to the active code window and the      ` underlying module     ` exit if no one is available     Set cpa = VBInstance.ActiveCodePane     Set cmo = cpa.CodeModule     If Err Then Exit Sub        ` get the current selection coordinates     cpa.GetSelection startLine, startCol, endLine, endCol     ` exit if no text is highlighted     If startLine = endLine And startCol = endCol Then Exit Sub        ` get the code text     If startLine = endLine Then          ` only one line is partially or fully highlighted          codeText = cmo.Lines(startLine, 1)          Mid$(codeText, startCol, endCol - startCol) = _               StrConv(Mid$(codeText, startCol, _               endCol - startCol), conversion)          cmo.ReplaceLine startLine, codeText     Else          ` the selection spans multiple lines of code          ` first, convert the highlighted text on the first line          codeText = cmo.Lines(startLine, 1)          Mid$(codeText, startCol, Len(codeText) + 1 - startCol) = _               StrConv(Mid$(codeText, startCol, Len(codeText) _               + 1 - startCol), conversion)          cmo.ReplaceLine startLine, codeText                 ` then convert the lines in the middle, that are           ` fully highlighted          For i = startLine + 1 To endLine - 1               codeText = cmo.Lines(i, 1)               codeText = StrConv(codeText, conversion)               cmo.ReplaceLine i, codeText         ext                 ` finally, convert the highlighted portion of the last line          codeText = cmo.Lines(endLine, 1)          Mid$(codeText, 1, endCol - 1) = StrConv(Mid$(codeText, 1, _          endCol - 1), conversion)          cmo.ReplaceLine endLine, codeText     End If        ` after replacing code we must restore the old selection     ` this seems to be a side-effect of the ReplaceLine method     cpa.SetSelection startLine, startCol, endLine, endColEnd Sub

Note that you also can use the ConvertSelectedText routine for converting the highlighted code to lowercase or proper case (for example, "This Is A Sentence In Proper Case"), by simply passing a suitable value for the optional parameter, as in

ConvertSelectedText vbLowerCase

or

ConvertSelectedText vbProperCase

As an exercise, it is left to you to create an add-in that adds three menu commands to the Edit menu to make these conversion routines available to the VB environment.

The Windows Collection and the Window Object

The VBE object exposes the Windows collection (see Figure 43.21), which includes all the windows of the environment, except the main window. All the child windows used in the IDE appear in this collection, even if they currently are not visible. After you have the reference to a Window object, you can query its Type property (toolbox, color palette, immediate, and so forth--each window has a distinctive type); move or resize it by using the standard Left, Top, Width and Height properties; hide or show it by means of the Visible attribute; and maximize or minimize it by using the WindowState property.

FIG. 43.21
The Windows collection.

The Windows collection is not the only set of objects that gives you access to the windows used in the environment. Each CodePane object exposes a Window property, which returns a reference to the corresponding Window object, and the VBE object also exposes the ActiveWindow property, which returns a reference to the window that currently has the input focus.

What can you do with the Windows collection? Just try out this handy routine, which closes all the forms and code windows in the environment, except the one with which you are currently working. Call this routine from within an add-in, and you'll have a quick way to reduce the clutter on your screen:

Sub CloseUnusedWindows()     Dim win As VBIDE.Window     For Each win In VBInstance.Windows          If win Is VBInstance.ActiveWindow Then               ` it's the active window, do nothing          ElseIf win.Type = vbext_wt_CodeWindow Or win.Type _                 = vbext_wt_Designer Then               ` close it if it is a code pane or a designer window               win.Close          End If    extEnd Sub

The Events Object

Don't let the "s" fool you--this is an object, not a collection. By itself, this object is useless and serves only to expose six properties that return a reference to other objects: CommandBarEvents, FileControlEvents, ReferencesEvents, SelectedVBControlsEvents, VBComponentsEvents, and VBControlsEvents (see Figure 43.22). All these objects let your add-in receive an event from the VB environment when something interesting happens.

FIG. 43.22
The Events objects and its dependent objects in the VB IDE hierarchy.

The CommandBarEvents object was shown in action in the earlier section of this chapter, "Adding User Interface Elements." All add-ins that add a menu item to the Add-Ins menu have to create an object of this type to get a notification when the user clicks it. However, you can intercept actions related to any other menu or toolbar item, not just those that you added to the environment. Listing 43.12 shows how easy intercepting the File, Print command is.

Listing 43.12 How to Intercept the File, Print Menu Command

` form level variablePrivate WithEvents PrintMenuHandler As CommandBarEventsPrivate Sub Form_Load()     Dim cbc As Office.CommandBarControl     Set cbc = VBInstance.CommandBars("File").Controls("Print...")     Set PrintMenuHandler = VBInstance.Events.CommandBarEvents(cbc)End SubPrivate Sub PrintMenuHandler_Click(ByVal CommandBarControl As Object, _     Handled As Boolean, CancelDefault As Boolean)          ` user is trying to print something          ` remind to connect the printer          If MsgBox("Have you connected the printer?", vbYesNo) = vbNo Then               ` if the user replies "No", don't proceed with command               CancelDefault = True          End IfEnd Sub

Admittedly, this example really is not useful, but it shows a number of interesting points. After you get a reference to an existing menu item, using the CommandBars property (cbc in this example), you pass it to the CommandBarEvents property to get a reference to a CommandBarEvent object (PrintMenuHandler in the example). Since you want to use this object to trap events, it must be declared as a form-level variable by using the WithEvents clause. You can then write an event procedure that fires when the user selects the corresponding menu command (File-Print, in this case), and you can even cancel the command by setting the CancelDefault parameter to True.

After you are familiar with this pattern, you'll find no difficulty in understanding how all the other properties of the Events object work.

The VBControlsEvents property enables you to receive notification when a new control is added on a given form (or any form in the environment), or an existing control is deleted or renamed. Once again, you have to declare an object that will act as a receiver for the IDE events and initialize it properly in the Form_Load event (or where you find it more appropriate). See Listing 43.13 to learn you can trap the ItemAdded, ItemRemoved and ItemRenamed events.

Listing 43.13 Intercepting the Events Associated to Controls

` form level variablePrivate WithEvents CtrlHandler As VBControlsEventsPrivate Sub Form_Load()     ` the syntax (Nothing, Nothing) means that we are asking to     ` receive events from any project and any component     Set CtrlHandler = VBInstance.Events.VBControlsEvent(Nothing, Nothing)End SubPrivate Sub CtrlHandler_ItemAdded(ctrl As VBControl)     ` a new control has been addedEnd SubPrivate Sub CtrlHandler_ItemRemoved(ctrl As VBControl)     ` a control has been deletedEnd SubPrivate Sub CtrlHandler_ItemRenamed(ctrl As VBControl, oldName _     As String, oldIndex As Long)     ` a control has been renamed or its index has been modifiedEnd Sub

You might use the ItemAdded and ItemRenamed events to ensure that the programmers assign a no-nonsense name to the control, instead of sticking to those dumb "Text1" and "List1" default strings. The ItemRemoved event might be useful to delete all the code related to the control, which often is left forgotten in the source code, even though it will never be executed.

You can take advantage of the SelectedVBControlsEvents property to learn when a control is selected (or deselected) on the current form. For instance, if your add-in offers a toolbar that aligns controls, you might enable it when there are at least two selected controls, and disable (or hide) it when there are zero or one selected controls.

The remaining event properties are less likely to be useful, unless you are writing a source code-maintenance utility. The VBComponentsEvents property lets you trap events related to components (for example, when a new module is added or an existing form is removed from the project). The ReferencesEvents property is useful only to get a notification when a reference is added or removed from the current project. Finally, the FileControlEvents property exposes a huge number of events that are related to files. You can be notified when a file is added to the project, when it is renamed or written back to disk, and so forth.

The References Collection and the Reference Object

Each VBProject object exposes a References collection, which holds one Reference object for each selected item in the Reference dialog box. Each individual Reference object exposes useful properties, such as Name, Guid, Type, Description, FullPath, BuiltIn (which returns True for those references that cannot be removed, for example, VB and VBA object libraries), and Major and Minor (which return version information). Another interesting property is IsBroken, which is True if the reference does not correspond to a valid entry in the system registry and, therefore, can be used for diagnostic purposes, as in the routine shown in Listing 43.14.

Listing 43.14 You Can Programmatically Check that All References Are Okay

Dim ref As VBIDE.ReferenceFor Each ref In VBInstance.ActiveVBProject.References     Print "Name: " & ref.Name     Print "Type: " & IIf(ref.Type = vbext_rk_Project, _          "Project", "Type Library") _          & IIf(ref.BuiltIn, " (Built-in)", "")     Print "Path: " & ref.FullPath     Print "Version: " & ref.Major & "." & ref.Minor     Print "Guid: " & ref.Guid     Print "Description: " & ref.Description     If ref.IsBroken Then Print "WARNING: the reference appears to be invalid"     PrintNext

You can also programmatically add new references to the project, using the AddFromFile and AddFromGuid methods of the References collection, and discard them by using the Remove method.

The Addins Collection and the Addin Object

As implied by its name, the Addins collection holds one reference for each add-in installed in the environment, either active or not. Each Addin object represents one entry in VBADDIN.INI and exposes properties such as Guid, ProgID, and Description. The most important property is Connect, which is True if the add-in is active and is otherwise False. To activate and deactivate other add-ins, you simply modify this setting and execute the Update method of the Addins collection. However, unless you are writing an alternative add-in manager, you are not going to use the Addin objects very often.

Hints and Suggestions

At this point, you should be aware of the many things that you can accomplish by using add-ins. The VB IDE object model is very complex and powerful at the same time, and it is impossible to cover all of its intricacies in a single chapter. However, the information contained in this chapter should be sufficient for you to start exploring it on your own. When in doubt, you may resort to official documentation and online help.

The next section provides you with some ideas to be implemented in your own add-ins:

  • Code formatter Modifies your source code by providing the correct indentation for If and Select Case blocks and loops, by inserting blank lines where desired, by breaking longer lines, and so on.

  • Print routine Prints selected portions of the source code (for instance, only the procedures modified after a specified date); uses different styles for such things as keywords, variables, and statements; adds informative headers and footers; and other related printing styles.

  • Cross-reference utility Shows where a variable is initialized, modified, and referenced; where a procedure or method is invoked; and so forth.

  • Code optimizer Spots which variables are declared but not used, and which routines are never executed (the so-called dead code). A decent optimizer would also point to inefficient Variant variables and arguments, or to invariant expressions within loops, which could be optimized simply by evaluating the expression outside the loop and assigning it to a temporary variable.

  • Palette for control alignment While VB5 offers many commands to align and resize the controls on a form, you might find that something is still missing. For example, while it is possible to center a control on its parent form, you cannot center a control within its container control.

  • Repository for code routines A database where you store your routines, and retrieve them by using intelligent queries (for example, "show me all the routines that deal with graphics").

  • aming Style enforcer A utility that helps to enforce a set of rules for control and variable naming (for instance, all command buttons should be prefixed by "cmd", all text box controls should be prefixed by "txt", and so forth).

  • Interface guidelines enforcer An add-in that checks that a number of rules are met while building the user interface of your application. For instance, it can check that there are no duplicate hot keys in menus and controls, that all controls are properly aligned, that no form is greater than 640 x 480 pixels (if you want your apps to run in VGA mode), and so on.

  • Wizards Automate several phases in the process of software development, such as the many wizards included in the VB package.

  • Common dialog code generators A common task for add-ins is the generation of code for building message boxes, Open and Save dialogs boxes, and all the other types of Windows common dialogs boxes.

  • Common styles for controls Build control styles, a well defined set of properties and related values that you can then apply to any control or group of controls that you place on a form.
  • Code spell-checkers It is rather easy to build an add-in that extracts all the quoted strings in an application and passes them to an external spell checker (such as the one included in Microsoft Word).

  • Tools for code localization Similar to the spell-checker, but instead helps translate a program to a different language, while checking that hot keys are consistent and that the new translated strings are short enough to appear on-screen (this is especially useful for messages that appear in label controls).

  • Homemade source control utilities While it is strongly suggested that you use full-fledged tools, such as Microsoft Visual SourceSafe, for any nontrivial requirement in this area, you can do many interesting things with your own add-ins, such as automatic compression and backup of all the source files as soon as they are written to disk, or immediate notification to the team administrator--through e-mail, for instance--that a programmer has opened or saved a shared file.

From Here...

This chapter explained what add-ins are and how you can implement them. It also showed a number of advanced routines that you may reuse in your own add-ins, and gave you many hints that you may want to develop on your own.

Much of the code illustrated in this chapter based on many advanced concepts that may be discussed in more depth elsewhere in this book:

  • Chapter 17, "Managing Your Projects," covers functions and procedures, and explains argument passing.

  • Chapter 18, "Introduction to Classes," contains details on the implementation of properties and methods, that are essential to understand all the issues related to add-ins.

  • Chapter 44, "Building a Wizard," is a good companion to this chapter because many add-ins are written in the form of wizards--find out how to couple the two concepts.