SEPTEMBER, 1996
By Mark Alan Pruitt
One of the aspects of Visual Basic 4.0 that intrigued me right from the start was its extensibility via the Add-In model. I looked at the examples, read some articles and got very confused. (Go ahead, all of you who figured out these things in a snap, laugh and point all you want!) What I wanted to find was a simple, step-by-step procedure that would walk me through creating an Add-In and explain why I was doing what I was doing. I was pretty sure that once I made one work, and understood why it was working, I could leverage that into whatever other Add-In tickled my fancy. And so far thats been correct.
What I hope to do here is provide you with that step-by-step tour through the creation of a basic Add-In framework, hoping to save you some of the grumbling, head-scratching and un-maidenly language that I went through. Along the way Ill sprinkle in some plain old good advice on coding. This framework can be used as a template for building any later Add-Ins you might dream up, but the project were going to build here will only show a form with one button on it (to get rid of the form when were tired of looking at it).
Please note that much of the code here came from two sources:
The Microsoft Knowledge Base article Q141932, How to Create a Basic Add-In Using Visual Basic 4.0. | |
An article in MSDN by Kenneth Lassesen, Building Add-Ins for Visual Basic 4.0. |
I would strongly suggest that you look up both of these articles and study them, especially Kens, for the additional Add-Ins wisdom they contain. Also read the various online help topics on the subject that come with VB.
First thing to do, I suppose, is to fire up Visual Basic. This will open a new project with the default Form1. You can create Add-Ins that never use a form, so at another time you might want to delete Form1 from the project, but for the purposes of this demo leave Form1 alone. Well get back to it later.
Now that youve got the IDE up on screen we want to set a reference to it so that the objects it exposes will be available to our Add-In.
Go to the Tools menu and click on References. Scroll down the list until you find the entry that says Microsoft Visual Basic 4.0 Development Environment. If theres no X in the checkbox next to it click on the checkbox. Now that the X is there, click the OK button.
Go to the Insert menu and click on Class Module. Youll get a the default code window for a Class Module. Now bring up the properties window. A class module has three properties; Instancing, Name and Public. By default they are set to 0 - Not Creatable, Class1 and False respectively. Well need to change these, so lets just start at the top.
Change the Instancing property to 2 - Creatable MultiUse. It is a requirement of Add-Ins that they be creatable. You can set it to1 - Creatable SingleUse, but that would only allow a single instance of VB to use your Add-In. With 2 - Creatable MultiUse you can have multiple instances of your Add-In running concurrently if you find it necessary.
Change the Name property to BasicAddin. With your later Add-Ins you can name this whatever you want of course, but follow these tips when you do:
Keep the name short and descriptive. This name will be used in VB.INI to refer to your Add-In and will be the class name that VB uses to access the three methods well be adding in later. | |
The naming convention here is not to put a three letter identifier in front of the name (like mod is tacked to the front of a code module name, or frm to the front of a form module name). |
Change the Public property to True. This is another requirement for an Add-In. It needs to be Public so that the VBIDE can access its methods.
Well be coming back later and adding code to the class module.
Go back to the Insert menu, this time click on Module. It has only one property, Name, which is set by default to Module1. Bring up the properties window and change this to modExample. Again, with your later Add-Ins you can name this whatever you choose, just remember that this will be the main code module for your Add-In. I make it a point to always name my main code module the same as my overall project name. So if I was making a project called MyApp the main code module in it would be called modMyApp (and the executable would be MyApp.exe). This is certainly not a hard and fast rule, but it does help to keep things clearer.
Lets take a couple of minutes here to finish up with Form1. Theres not a lot to do with it in this example but for your later ones you can get as fancy as you think you need to be.
If Form1 is not on your screen so that you can click on it to select it, go to the Project window, highlight Form1 and click on the View Form button.
Use your toolbox to put a command button on the form. You can leave it named to its default of Command1. Double-click the command button to bring up its code window. You should be in the procedure named Private Sub Command1_Click().
Type in:
Thats it for Form1. You can close it and its code window. |
Now well add the required code to modExample. Highlight it in the Project window and click on the View Code button. You should be in the General, Declarations section of the module and the window should just say Option Explicit.
If it doesnt say Option Explicit you should go to the Tools Menu and click on Options. This brings up a tabbed dialog. You should be on the Environment tab. In the lower right corner click the checkbox next to Require Variable Declaration so that its checked.
None of that last is required for Add-Ins. But it will save you hours of debugging by not allowing you to use a variable name that you havent explicitly declared with a DIM statement. Theres nothing more aggravating than setting the array variable strUserName() at some point in the code and then have a loop thats trying to fill the array called strUserNames(). Dont laugh. It happened to me and I spent a lot of time trying to figger out why the dad-blamed array didnt fill up while the code read through my database. If Id been using Option Explicit the first time I tried to run it VB would have stopped, highlighted strUserNames() and told me that it didnt recognize this array. Make it a liferule that you have all your projects set to Option Explicit. Just leaving the checkbox checked will do it. But I digress.
The major reason for modExamples existence is to determine whether the Add-In has been registered in the VB.INI file and, if not, write the necessary entry to VB.INI.
VB.INI is where VB keeps track of (amongst other things) the Add-Ins to the VBIDE. To read from and write to it were going to use some API calls, two for 32-bit and two more for 16-bit. Well put the declares for them inside a conditional compile statement, so that if youre targeting your Add-In for the 16-bit platform those declares will get compiled in and the 32-bit ones will get used if youre targeting the 32-bit platform.
Note that due to the nature of HTML the long declare statements are going to appear on several lines. In your code you need to enter each declare statement in a single line. Im going to put a paragraph break between each declare for readabilitys sake, but this isnt necessary in your code.
Type this (or copy and paste) into your code window:
#If Win16 Then Declare Function WritePrivateProfileString Lib "KERNEL" (ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal FileName As String) As Integer Declare Function GetPrivateProfileString Lib "KERNEL" (ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal ReturnString As String, ByVal NumBytes As Integer, ByVal FileName As String) As Integer #ElseIf Win32 Then Declare Function WritePrivateProfileString Lib "KERNEL32" Alias "WritePrivateProfileStringA" (ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal FileName As String) As Long Declare Function GetPrivateProfileString Lib "KERNEL32" Alias "GetPrivateProfileStringA" (ByVal AppName As String, ByVal KeyName As String, ByVal keydefault As String, ByVal ReturnString As String, ByVal NumBytes As Long, ByVal FileName As String) As Long #End If |
That takes care of all the declares.
Next well add a Sub Main() and put the required code there. Type in
Sub Main
and hit the Enter key. This creates a sub in modExample called Main and puts it in the code window. Type (or copy and paste) this code into the window (comments and all) between the Sub Main() and End Sub lines:
Dim Ret As Variant Dim RetStr As String Const BufSize = 255 #If Win16 Then Const Section = "Add-Ins16" #ElseIf Win32 Then Const Section = "Add-Ins32" #End If 'Check to see if the entry is already in the Vb.ini file. Add if not RetStr = Space(BufSize) Ret = GetPrivateProfileString(Section, "Example.BasicAddin", "NotFound", RetStr, BufSize, "VB.INI") RetStr = Left(RetStr, Ret) If RetStr = "NotFound" Then WritePrivateProfileString Section, "Example.BasicAddin", "0", "VB.INI" End If
Im going to break down the above code, more or less line by line.
The first three lines Dim some necessary variables. Ret is the number that the API call is going to return to you. It will either be an Integeror a Long, depending on if you compile for the 16- or 32-bit platform. Since it has to be able to accept either value, it is Dimd as a Variant. It holds the length of the string that the API call returns.
RetStr is the variable to hold the APIs returned string. It will be used to determine whether the entry for your Add-In is already in VB.INI.
The constant BufSize is just to set the size of the buffer to pass the string back into. The API is written in C++ and C++ likes to have an explicitly declared buffer to pass strings back to (dont worry overmuch why this is so just humor C++ and do it!).
You see we have another conditional compile statement next, deciding between the 16- and 32-bit API calls. It just sets the constant Section to either Add-Ins16 or Add-Ins32. These are the names of the two sections in VB.INI that are used to track settings for Add-Ins.
The next line sets RetStr to a string of 255 spaces, ready to receive the return from the API call. The next line makes the actual API call. It is one of only three places in Sub Main() as its written here where youll need to make any changes for future Add-Ins. For that reason were going to pay a bit more attention to it. To repeat, the call looks like this:
Ret = GetPrivateProfileString(Section, "Example.BasicAddin", "NotFound", RetStr, BufSize, "VB.INI")
Were going to pay more attention to the parameters (all that stuff in the parentheses) than to the rest of the call. There are six params being passed.
The first, Section, just tells VB what section of the VB.INI file to look in. Youll remember we set this with the conditional compile statement a couple lines ago. The section of the VB.INI file that handles 32-bit Add-Ins looks like this:
[Add-Ins32] VBCP.VBCPClass=1 DataFormDesigner.DFDClass=1 MsgBoxMaker.MBMClass=0 Example.BasicAddin=0
Youll notice that Example.BasicAddin is in there this is the next param we pass. Well go back in a few minutes and explain why the Example part is there. For now, youll note that the top line of the little piece Ive included here is [Add-Ins32]. Its no coincidence that this is what our conditional compile statement sets the Section constant to if its aimed at the 32-bit environment. The square brackets around Add-Ins32 tells VB (well Windows for that matter) that this is a section header and all of the things between it and the next thing surrounded by square brackets (or the end of the file) are part of that section.
Example.BasicAddin is a keystring. The format for sections of INI files (and the registry, pretty much) is:
[Section Header] keystring=keyvalue
Its the keyvalues that you want to write, read, etc. in order to use either INIs or the registry. (As a quick aside why the heck MS decided to make VB4.0 write to an INI file here instead of putting that all in the registry is simply beyond me. Maybe its because theres also a 16-bit version and 16-bit versions of Windows are pretty much clueless about registries. In any event, when at all possible for your programs for Win95 or WinNT you should use the registry instead of a private INI file.)
Example.Addin (or rather, the param space that its in) is the only thing in the API call that you need to change from the code Ive presented you when you make another Add-In. The very next Add-In I wrote after figuring out the Example.Addin was my Message Box Maker. Youll note that right above Example.Addin is MsgBoxMaker.MBMClass. Both entries are a combination of the Project Name and the Class Module Name for a given Add-In.
So now lets go back to your running instance of VB (and its about time, eh?). This should probably be Step Six but we still have a bit of work to do in Step Five so well compromise here and call it:
Click on Tools. Choose Options.
This will bring up a tabbed dialog with four tabs, Environment, Project, Editor and Advanced. We saw this a little bit ago when you set the Option Explicit on the Environment tab. Click on the Project tab. In the dropdown list called Startup Form, choose Sub Main. This will let Windows know that the first thing it needs to do when calling your Add-In (or any other VB program for that matter) is to run the code in Sub Main. A VB project can only have one Sub Main, no matter how many code modules, class modules, form modules, etc. that it possesses.
Next, enter a name in the Project Name textbox. As I pointed out a moment ago, its the combination of this and your Class Module Name that go into the VB.INI to tell the VBIDE just what the heck its supposed to run when you click on the entry in the Add-Ins menu. Were calling this project Example, so thats what you should type in there.
Now look at the frame called StartMode. It has two radio buttons, StandAlone and OLE Server. Click on the OLE Server one, because thats what were writing here, an OLE server. (Gee just like the big boys!)
Note that all of this (entering the Project Name, setting the Startup Form to Sub Main and setting the StartMode to OLE Server) are required for Add-Ins. Just do it this way every time.
Now click on the Advanced tab. Find the frame named Error Trapping and click on the radio button next to Break in Class Module. This, too, is required for Add-Ins.
Go ahead and click on the OK button. Were done here. Now its about time to save. Save this project (in whatever folder blows your hair back). Make sure that when asked for a project name (the filename.vbp file) you tell it Example.
When we last left our intrepid coder, he or she was being led through the code in Sub Main and had just finished talking about the second param in the call to the GetPrivateProfileString API "Example.Addin" when they had been led off on a long digression.
The next parameter passed is "NotFound". This is simply the default string that GetPrivateProfileString will pass back to RetStr if the requested section is not found in the VB.INI file.
Now RetStr and BufSize are passed to the API call. Remember, C++ likes to have these things set out neat for it, so just humor it and dont change these params.
Lastly you pass it "VB.INI" which, oddly enough, tells the Windows API that VB.INI is the file it should be looking at for all the rest of the stuff. Dont change this either.
The next line:
RetStr = Left(RetStr, Ret)
This simply takes the RetStr that the GetPrivateProfileString API call has set, and trims off all of the spaces to the right of the actual number of returned characters (set by the GetPrivateProfileString API call in Ret).
At last we get to the juicy parts! The If/Then branch
If RetStr = "NotFound" Then WritePrivateProfileString Section, "Example.BasicAddin", "0", "VB.INI" End If
says: "If you cant find an entry in VB.INI under Section (either Add-Ins16 or Add-Ins32) whos keyname is Example.BasicAddin then you need to write the info to the VB.INI file".
The WritePrivateProfileString API call contains the other two things that you can change for future Add-Ins.
Leave Section alone.
Example.BasicAddin (just like in the call to GetPrivateProfileString) should be changed to the ProjectName.ClassModuleName of your particular project.
The next param, "0", can either be a 0 or a 1. Its the last of the three things you should change in Sub Main (as it is written here) for your other Add-In projects.
If you set this to "0", it will just add a reference to your Add-In to the Add-In Manager (found on the Add-Ins menu, of course). This means that in order for the Add-In to appear on the Add-Ins menu the user will have to fire up the Add-Ins Manager and check the checkbox next to Example.BasicAddin. Once the checkbox has been checked, the Add-In will show up on the Add-Ins menu until the box is unchecked. Essentially, setting it to "0" says: "Im available but wont be loaded until you ask".
If you set this to "1" it says: "Every time you fire up VB Im going to load this Add-In".
Lastly, you pass the WritePrivateProfileString the name of the file it should be writing to. In this case its "VB.INI" and you should leave this part of the call alone as well.
At last were through with modExample. Close it down (dont forget to save here!) and lets move on to
Here well put in the last code needed to make this Add-In go. Go to the Projects window, highlight BasicAddin.cls and click on the View Code button.
You should be in the General, Declarations section of BasicAddin. Type in these three lines:
Private ThisInstance As VBIDE.Application Private AddInMenuLine As VBIDE.MenuLine Private hMenuLine As Long
The first one just declares a variable that will hold the ID of the instance of Visual Basic your Add-In is being called from.
The second one sets up a variable to pass stuff along to
the Add-Ins menu through. The last one keeps track of the added menu lines via an index number. All are declared Private because they are only available to methods in your Class Module. |
Now type in:
Public Sub AfterClick
and hit the Enter key. This creates Public Sub AfterClick(). The code that goes in here is what will happen when the user clicks on the entry in the Add-Ins menu for your Add-In.
For our example simply type
Form1.Show
which will load and show our simple little form with the one button on it.
It is a Public Sub so that the VBIDE can see it, call it and act on it.
Go back to the General, Declarations section. Type in
Public Sub ConnectAddIn
and hit the Enter key.
This creates Public Sub ConnectAddIn(). Put your cursor between the parentheses and type in:
VBInstance As Object
This is what the VBIDE will pass to your ConnectAddIn method, IDing the instance of the VBIDE that is currently running.
Between the Public Sub ConnectAddIn(VBInstance As Object) and the End Sub line type in:
Set ThisInstance = VBInstance Set AddInMenuLine = ThisInstance.AddInMenu.MenuItems.Add("Basic Add-In") hMenuLine = AddInMenuLine.ConnectEvents(Me)
The first line tells VB that you want to refer to the currently running instance of Visual Basic.
The next line tells it to put the words Basic Add-In on the Add-Ins menu.
The last line just gives a "handler number" to the menu line so that VB knows which code to fire up (the proper Sub AfterClick code) when you click on that menu line.
Go ahead and save your project again.
Now, run the project. You wont see anything yet (except the Debug window). Thats alright, its what its supposed to do. Minimize this copy of Visual Basic (or your screen will get really crowded).
Launch another copy of Visual Basic. Click on the Add-Ins menu and then the Add-In Manager menu line.
You should have an entry in there that says Basic Add-In with an empty checkbox to the left of it. Click the checkbox so the X appears and then choose the OK button.
Now, click on the Add-Ins menu again. Down towards the bottom should be the Basic Add-In entry. Click that.
Voila! If youve followed all of the steps carefully youll see Form1 (with its one command button) appear on your screen. When you get tired of all that enraptured staring at it you can click the command button and it will go away.
OK. I admit, while its pretty exciting to see something youve written appear right there on the VB IDE menu, the Basic Add-In itself is not very thrilling. But thats just the framework. Once you know how to make your Add-In callable from VB, you can do most anything you want with it.
For instance you only have the one form in this one and pretty much no code for it. But you can do the project like youd do any other VB project, adding in code modules, other forms, etc. as needed.
Study the VBIDE object. There are a lot of other functions that can be done with it. You can add and remove files from your VB project. You can add, remove and alter the position and other properties of controls on a given form.
If youre completely at a loss as to what to do with this next article well look further into the VBIDE and create an Add-In that creates Add-Ins! Or, at least, the automates the creation of the framework for an Add-In project and then turns you loose from there.
Please feel free to contact me with questions, comments, etc. at: [email protected]
Until then, Happy Coding!
Para ver el original, dirigete a VB Online.