by Peter G. Aitken ©1997
Originally published in Visual Developer magazine
ActiveX and the Single Programmer
If you haven't heard about ActiveX then you probably died a while back and you should let us know so we can cancel your subscription. Most of the 99.99% of you who have heard of it probably don' t know exactly what it is, how it can help you in your programming tasks, and what sizes it comes in. You shouldn't feel bad because not only is ActiveX an extremely complex topic, it has also been a moving target as Microsoft has not always used the term to mean the same thing! Whatever it means (and I will eventually tell you!) ActiveX is undoubtedly an important, perhaps essential, technology for Visual Basic programmers in particular and Windows programmers in general.
In my last column, I spoke a little bit about ActiveX what the term means, and how its meaning has evolved from tools that were specifically Internet-related to the much more general category of software components that are based on the Common Object Model, or COM, technology. This is an important point to remember. When you think "ActiveX" you should not automatically think "Internet" at the same time, any more than you would automatically associate the Java language with the Internet. True, both ActiveX and Java are important for Internet-related programming, and in both cases the impetus behind their development came in part from Internet-related needs. Even so, the importance and usefulness of both ActiveX and Java technology go far beyond the 'net and reach into almost every aspect of programming and applications development.
How does this relate to Visual Basic programmers? If you dont know the answer to that question, then I can only assume that you have not yet taken a good look at Visual Basic 5. If Microsoft holds reasonably close to their announced release schedule for the new Visual Basic, then it will have been available for a month or two by the time you read this. You'll need Visual Basic 5 in order to do anything serious with ActiveX; be sure to get either the Professional or Enterprise edition. I covered some of Visual Basic 5's new features last issue, and I am not going to rehash them here since you probably already know about most of them. There are plenty of new goodies, that's for sure. Perhaps the most significant new feature is the comprehensive support for creation of ActiveX objects.
The ActiveX Dessert Tray
Creating an ActiveX object in Visual Basic is something like ordering dessert in a fancy restaurant and the waiter brings you a cart loaded with 8 varieties of cheesecake. What are the differences, and which one should you choose? Likewise, when you start a new Visual Basic project you have five different kinds of ActiveX projects to choose from. But first, some basics.
ActiveX is a technology for creating reusable software components. In any of its 5 guises, ActiveX is used to define classes, which can be though of as blueprints. Just like you cannot ride on a blueprint for a bicycle, you can't do anything directly with an ActiveX class. When some software process, the client, needs to make use of the capabilities of an ActiveX class, it creates an object, or instance, of the class. An ActiveX object is related to an ActiveX class in the same way a bicycle is related to its blueprints. It's the ActiveX object that actually exists as executable code in memory and/or visual elements on-screen. The client process then makes use of the object's visual elements, properties, and methods to perform the required tasks.
An ActiveX object can run either in-process or out-of-process with respect to its client. In-process execution provides faster access to the object from the client, but the object and the client are utilizing the same execution thread. Out-of-process execution provides the object with a separate and independent execution thread from the client, and the client can request that the object perform some task and then go about its own tasks while the object works independently. Communication in this case is cross-process and correspondingly slower.
Now let's look at the 5 types of ActiveX projects that Visual Basic supports:
Of these five ways of "doing" ActiveX, the most attention has gone to ActiveX controls, probably because of their connection with the Web. If you need some specific functionality on your Web site, whether it be database access, graphic display, or text handling, you can put the required elements in an ActiveX control and then embed the control in your HTML document. When a visitor views your page in a ActiveX control-enabled browser, the control (if not already present on the local system) is automatically downloaded and installed. Their simplicity of use, powerful capabilities, and security features such as digital signing mean that ActiveX controls are almost sure to become an important part of Web standards, and are something that you need to know about. In this and the next column I will show you how to create an ActiveX control in Visual Basic, and how to use it in programs and Web pages.
Creating an ActiveX Control
The process of creating an ActiveX control in Visual Basic is relatively simple. For the most part, the procedure is the same as creating a standard Visual Basic executable. You start with a form, place controls on it (including other ActiveX controls), and write code to perform various tasks. The differences between programming an ActiveX control and programming a regular Visual Basic executable, and some potential confusions, arise when dealing with events, when creating property pages, and when testing the control. I will deal with all of these as needed.
There are two basic methods for creating an ActiveX control, which can be used alone or in combination. You can create a completely custom visual appearance by using Visual Basic's graphics drawing statements, such as Line and PSet, in the control's Paint event procedure. You can also create a new control by combining existing controls, such as Command Button and Text Box. A new ActiveX control can use an existing ActiveX controls as constituents, also. So far, creating an ActiveX control's visual interface sounds pretty much the same as creating a regular Visual Basic form, and in fact it is, so you should find this part of the process quite familiar.
There is actually a third method for creating an ActiveX control that consists of taking an existing ActiveX control and modifying it. This is not trivial, however, because the original control already contains code to paint its visual interface. It is possible, although tricky, to get a control to provide notification of its Paint events so you can add additional graphics statements. This is a topic I will not cover further, at least not now.
The foundation of any ActiveX control you create in Visual Basic is the UserControl object. In a way, a UserControl object is like a regular Visual Basic form because it provides the container onto which you place any other controls, which are called constituent controls. The UserControl has its own set of default properties, methods, and events that you work with in defining the ActiveX control's functionality.
Enough talk let's try it out. The control that I will create will be relatively simple. It is a "real" control in that you could actually use it in your own projects, but it does not do more than scratch the surface of the functionality you could place in an ActiveX control. And just what can an ActiveX control do? Just about anything, that's what. Unlike Java applets, ActiveX controls are not limited in capabilities for security reasons (security is handled differently, as we will see later).
The demonstration control's name, FancyCmdButton, describes it well. It will be a replacement for the regular Command Button control, permitting the user to click it to trigger an event to which the container program can respond. It will have a different appearance, however. Rather that seeming to be depressed when clicked, it will change color. This sounds simplistic, and in fact is quite simple compared with most ActiveX controls, but it is ideal to illustrate the major parts of creating and testing an ActiveX control.
Let's get to work. In Visual Basic, start a new ActiveX Control project. Visual Basic starts the new project and adds a UserControl designer to it. The gray rectangle in the design window represents the new control. It is similar to a standard Visual Basic form in some respects, but it does not have a border or title bar (although a border can be added).
Next we need to set some of the project properties. Select Project1 Properties from the Project menu to open the Project Properties dialog box. Display the General tab, and make the following entries:
Next, change the Name property of the control itself. Be sure that the control is selected in the Designer window, as indicated by handles around its periphery. In the Properties window find the Name property and change it to FancyCmdButton. This name will now appear in the Designers title bar. Don't forget to save the project. Select Save Project from the File menu and save both the control and the project under the default names that Visual Basic suggests: FANCYCMDBUTTON.CTL and AXCTRLDEMO.VBP.
The basic framework of the ActiveX control is in place, but it does not yet do anything (sounds like some people I know!). Rather than drawing the control's appearance with graphics statements, we will make use of standard Visual Basic controls. The first is a Shape control, which should be placed in the upper left corner of the UserControl. Set the Shape control's properties as shown here:
BorderStyle 0 – Transparent
FillColor &H0000FF00& (or any light shade of green)
FillStyle 0 – Solid
Shape 4 – Rounded Rectangle
Next, add a Label control on top of the Shape control. Set its properties as follows:
BackStyle 0 Transparent
Alignment 2 – Center
Now, reduce the size of the UserControl. The exact size is not crucial, but you want it fairly small. At this stage your Visual Basic screen will look like Figure 1.
Figure 1: The FancyCmdButton ActiveX control after adding the Shape and Label controls.
The FancyCmdButton control now has all of the constituent controls that it needs. But thats not enough, of course. What will it do? How will it appear? The control needs code that will bring it to life.
Our first concern is the control's appearance. When it is displayed within a container, we want the Shape control to fill the entire area of the ActiveX control, and we want the Label to be the same width as the Shape control and to be centered vertically. The code to perform these actions should be placed within the UserControls Resize event procedure. Display the code editing window in the usual manner, then select UserControl in the object list at the top of the window, and select Resize in the Procedure list. Add the following code to this event procedure:
Private Sub UserControl_Resize()
' Change the position and size of the Shape control
' to fill the FancyCmdButton control's entire area.
shpButton.Move 0, 0, ScaleWidth, ScaleHeight
' Make the Label control the same width as the Shape
' control and center it vertically.
lblButton.Move 0, (ScaleHeight - lblButton.Height) / 2, ScaleWidth
Testing the Control
While the control is not complete, it has reached the stage where it is ready to be tested. But how do we do that? An ActiveX control cannot run by itself it needs a container object to hold it. What's needed, obviously, is a separate Visual Basic project with a form on which you can place an instance of the ActiveX control. Rather than having to start a separate copy of Visual Basic, you can use one of Visual Basic 5s new features. By defining a project group you can have two or more independent projects open at once. In this case, the ActiveX control will be one project, and the second project will be a standard Visual Basic executable to test the ActiveX control.
To create a project group, select Add Project to Group from the File menu, then select Standard EXE as the project type. Youll now have two Designers open, one for the ActiveX control project and one for the Standard EXE project that you just created. Both projects will be listed in the Project window. Next, save the project group by selecting Save Project Group from the File menu. Use the file names given here (the extensions are added automatically by Visual Basic):
Project Group TestAXCtl.vbg
Running the ActiveX Control
You may be puzzled by the heading of this section didnt I just claim that an ActiveX control cannot run by itself? Yes indeed, but now I am using "run" in a different sense. An ActiveX control does not run in the same sense as a standard Visual Basic executable. When an ActiveX control "runs" all it does is make itself available for insertion into other projects. And this is exactly what we want to run the ActiveX control so it becomes available to insert onto the test projects form, while the test project remains in design mode.
Guess what? This is exactly what Visual Basics project groups are intended for. Heres how its done: Close the ActiveX controls Designer by clicking the close button in its title bar. This puts the ActiveX control into run mode. The only visible sign of your ActiveX control now is the UserControl icon in the Toolbox, indicating that the control is available for use in other project. Make the test projects form active and double-click the UserControl icon to place an instance of it on the form. This is shown in Figure 2.
Figure 2: The test project form after placing an instance of the UserControl on it.
If you look in the Properties window, youll see that the new control that you just placed on the form has been given a default name, FancyCmdButton1, and that it has its own set of properties. Change the Name property to FCB1 (for the sake of brevity). For now, the other properties should be left at their default values.
To take the ActiveX control out of run mode and back into design mode, double click its name in the Project window. Youll see the ActiveX control Designer open up again, and you'll also note that the instance of the UserControl on the test project form becomes hatched, indicating that the control is no longer active. This illustrates the usefulness of Visual Basic's project groups you can quickly switch the UserControl between design mode and run mode, making it easy to test and modify.
ActiveX Control Events
One difference between ActiveX controls and standard executable programs is that an ActiveX control has a defined behavior not only at execution (when it is executing within its container) but also at design time. To illustrate this, open the UserControls Resize event procedure and add the following line to the code that is already there:
Debug.Print "Resize event"
Close the ActiveX Designer to run the ActiveX control. On the test project form, change the size of the FancyCmdButton control. The messages displayed in the Immediate window reveal that each time you resize the control, its Resize event procedure fires, even though the test project is still in design mode. If you add a second instance of the FancyCmdButton control to the form youll see that the Resize event fires then, too. Delete the second control, if you placed one, before continuing.
Now lets take a look at some of the other events that occur during the life of an ActiveX control. When working with these events it is essential that you understand the ephemeral nature of an ActiveX control. You might think that once you place an ActiveX control on a form, thats it the control is created and continues to exist, as a component of the form, from then on. Things are not so simple. Oh no, not nearly!
When you place an ActiveX control on a Visual Basic form during program design, an instance of the control is created in memory. When you run the program, that instance of the control is destroyed and a new run-time instance is created; it is this instance that will be in operation as the program executes. Then, when you terminate the program to return to design mode, the runtime instance of the control is destroyed and yet another instance is created and displayed on the form in the Visual Basic Designer. You can see that, rather than having a single instance of the control remaining in existence, you have three created and destroyed in turn. Events related to instance creation and destruction will fire during each of these creation/destruction cycles.
Heres another situation. Suppose you have designed an ActiveX control in Visual Basic, and have also created a test form. When you close the ActiveX Designer and place an instance of the control on the test form, an instance is created (as described in the previous paragraph). If you then re-open the ActiveX Designer, so that the control on the test form is displayed with hatching, the control instance is destroyed. Then, when you again close the ActiveX Designer, a new instance of the control is created (and the control on the test form is displayed without hatching). If you place two or more instances of a control on a form, each instance undergoes its own creation-destruction-creation cycles as described here.
Its pretty complicated, isnt it! You get used to it after a while, particularly after some programming techniques Ill present below that assist you in keeping track of the creation and destruction of control instances. For now, the important thing to remember is that an ActiveX control has certain events that are triggered in response to control creation and destruction. Some of these events have to do with the controls properties, and we will be getting to that topic soon. For now, lets just look at the events without worrying about the details:
To get a handle on when these various control events occur, you can use the same technique that you use earlier for the Resize event procedure: place a Debug.Print statement in each event procedure to print the appropriate message to the Immediate window. The code is shown below. With this code in your ActiveX project, you can trace the occurrence of events as they occur during the lifetime of your ActiveX control.
Private Sub UserControl_Initialize()
Debug.Print "Initialize event"
Private Sub UserControl_InitProperties()
Debug.Print "InitProperties event"
Private Sub UserControl_Paint()
Debug.Print "Paint event"
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
Debug.Print "ReadProperties event"
Private Sub UserControl_Terminate()
Debug.Print "Terminate event"
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
Debug.Print "WriteProperties event"
Responding to Events
When you are dealing with ActiveX controls, it is necessary to look at events at three levels:
There seem to be three layers here, and that is exactly correct. How does this work? Events may need to be "passed along" from one object to another. For example, if the user clicks on the Label control in the demonstration ActiveX control, and we want the container object to be able to respond, it is necessary to pass the event "up" to the container. This is done using Visual Basics RaiseEvent statement. The syntax is as follows:
RaiseEvent EventName [(ArgList)]
EventName is the name of the event to fire, and ArgList is an optional list of arguments. Before you can use RaiseEvent, you must declare an event procedure for the event that you will raise. This declaration must be at the module level of the module in which the event will be raised, and takes the following form:
[Public] Event EventName [(ArgList)]
EventName and ArgList are, respectively, the name of the event and an optional argument list. Include the optional Public keyword if the event needs to be detected within another module; otherwise it will be available only within the module where it is raised. When the event is raised, the argument list used in the RaiseEvent statement must match the list in the event procedure declaration.
What events are available? The usual repertory of Visual Basic events is at your disposal, such as Click and MouseDown. Without RaiseEvent, the control would be able to use such events internally, but if you want the container to be able to respond to events also you will have to raise them.
Before we write the event code for the FancyCmdButton control, lets think for a moment about what it needs to do.
For the first task, we will use the MouseDown event. But where will this event be detected? The ActiveX control consists of both a Label control and a Shape control, plus the underlying UserControl. Clearly the Label control must respond to MouseDown. Since Shape controls do not detect mouse events, any mouse action on our Shape control will be passed through to the underlying UserControl without any coding required on our part. Therefore the UserControls MouseDown event procedure will be used also.
The same logic applies to the release of the mouse button. We will use the Label controls and the UserControls MouseUp event procedures.
It's time to write some code. Open the code window for the FancyCmdButton control. Select General in the object list and Declarations in the Procedure box, then add the code shown below. This code declares a variable and a constant for manipulating the controls color, and declares the Click event procedure so that we can use the RaiseEvent statement.
' Variable for the old color.
Dim OldColor As Long
' Constant for the "clicked" color (this is red).
Const NEWCOLOR = &HFF&
' Declare a Public Click event procedure.
Public Event Click()
Additional code is required in the MouseDown and MouseUp event procedures of the Label and the FancyCmdButton controls. In the code editing window, use the Object and Procedure lists to select these procedures, then add the code shown below. This listing combines the code for both MouseDown and both MouseUp event procedures.
Private Sub lblButton_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
'Save the original fill color.
OldColor = shpButton.FillColor
' Change to the "clicked" fill color.
shpButton.FillColor = NEWCOLOR
Private Sub lblButton_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
' Restore the original fill color.
shpButton.FillColor = OldColor
' Raise the click event.
Private Sub UserControl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
' Save the old fill color.
OldColor = shpButton.FillColor
' Change to the "clicked" fill color.
shpButton.FillColor = NEWCOLOR
Private Sub UserControl_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
' Restore the original fill color.
shpButton.FillColor = OldColor
' Raise a Click event.
This completes the code that enables the FancyCmdButton control respond to user clicks by changing its background color and to raise a Click event for its container to respond to. Before we can test the control we need to add the code to the test project that will respond to the Click event raised by the FancyCmdButton control. Close the UserControl, and display the code window for the test project form (TestAXCtl_Form1). Add the single line of code shown below to the forms Click event procedure.
Private Sub FCB1_Click()
MsgBox ("I've been clicked")
The project is now ready to take for a spin. Be sure that the FancyCmdButton designer is closed, as indicted by the control on the test form displaying without hatch marks. Then, press F5 to run the test project. Youll see the form display with the FancyCmdButton control on it. When you position the mouse pointer over the control and press the mouse button, the buttons background color changes to red. When you release the mouse button, the color changes back to green and a message appears indicating that the form has detected the click.
I fear that I must stop here. I am already well over my allotted space, and there are limits to any editor's patience! We have a working ActiveX control, but there is lots more to cover. In the next column I will show you how to give the control properties and a property page, and deal with security and distribution issues.