Visual Basic 5.0 Relieves
the Pain and Discomfort of
ActiveX Control Creation Guy Eddon and Henry Eddon
Code for this article: VB5P1.exe (16KB)
Yes, it's true, the Visual Basic® 5.0 programming systems does have a native code compiler. But the really interesting feature of Visual Basic is the support for ActiveX programming. In this article, we will look at how Visual Basic 5.0 has improved, then we'll focus on using it to create two unique types of ActiveX components: controls and documents. |
BASIC has a long and venerable history as the first product Microsoft® ever produced. It became visual when it arrived on the Windows® platform. Successive versions added support for database applications (3.0) and classes (4.0) to the language. With version 5.0, Visual Basic continues its steady evolution
into an object-oriented development environment based on componentsor more precisely on COM. In addition, the Visual Basic Control Creation Edition is a pared-down p-code generating version of Visual Basic 5.0 that can only be used to create ActiveX controls. It's available from http://www.microsoft.com/vbasic. Until now, Visual Basic was often seen as a "glue language," suitable to instantiate objects developed by other development tools. Now Visual Basic has become a language able to produce ActiveX components on its own. Let's begin by examining several of the new Visual Basic 5.0 features that relate to ActiveX technologies. At the top of the list is the ability to create ActiveX components. Visual Basic 4.0 enabled developers to create OLE automation servers. In Visual Basic 5.0, you call them ActiveX code components. What's the difference? Nothing really, but the change in nomenclature reflects the fact that everything you build in Visual Basic is a component. Previously, if you built an application that called an OLE automation server to do some work, and then that server tried to shirk its responsibilities and employ another server to do part of its work, things could get pretty confusing. What should you call the server-turned-client? Once you make the metaphysical transition to components, everything becomes streamlined. The application is calling a component, which in turn calls another component. Visual Basic 5.0 allows you to build components that are also connectable objects and therefore have the ability to communicate with their containerin other words, the ability to "fire events." These components are known as ActiveX controls. ActiveX controls built in Visual Basic can be used by programmers developing software in Visual Basic, Visual C++®, or in HTML documents. Another type of component you can create using Visual Basic is ActiveX documents. These components can be opened inside of Microsoft Office Binder or Microsoft Internet Explorer just like Microsoft Word documents. Good news for those of you who wanted to call a Windows API procedure that requires a callback address but couldn't because Visual Basic didn't support pointers. Visual Basic still doesn't support pointers, but an exception is made for callback functions. The new AddressOf keyword enables you to pass the address of a Visual Basic routine to a DLL-based function. For more details see the sidebar: "The New AddressOf Keyword". Native Code Compiler
The inability to compile programs to native code has been one of the most maligned deficiencies of Visual Basic. Previous versions of Visual Basic compiled applications into p-code (packed code, sometimes called pseudo-code), which meant that an interpreter was needed at runtime, slowing execution. Now Visual Basic has a code generator that can even optimize instructions to favor the Pentium Pro. Code generated with this option will still run on earlier processors, but less efficiently. When you build your executable in Visual Basic you can now choose native code or p-code. Note that whether you use native code or p-code in your applications, all the Visual Basic runtime files must be shipped with your application. Object-Oriented Programming in Visual Basic
Is Visual Basic an object-oriented programming language? Before I stand on the soapbox and issue a pronouncement, let's review what it means to be an object-oriented programming language and then you decide for yourself. Object-oriented programming is built on the fundamental pillars of encapsulation, inheritance, and polymorphism. |
Friend Function Sum(X As Integer, Y As Integer)
Sum = X + Y
End Function
Inheritance refers to the ability to create a new object based on another object, copying its code and data members. Inheritance is best used when an obvious "is a" relationship can be recognized between objects. You might have a basic Tree class from which a DeciduousTree class is derived. Then you inherit Oak and Maple classes from the DeciduousTree class. Inheritance would allow your code to express the fact that an Oak "is a" DeciduousTree. Visual Basic does not currently support this type of inheritance. Aggregation, sometimes known as containment, can be used in Visual Basic to simulate, to a certain extent, a type of inheritance. Aggregation simply means building a new object by including existing objects within. This is more easily understood by way of an example. Say you have defined a door class and now want to define a car class. Most sedans have four doors, so you simply put an array of four door objects within your car class. Thus your car contains four doors. This is not a true example of inheritance because no "is a" relationship is present. It is correct to say that a car contains doors, but not that a car "is a" door. Polymorphism means that many classes can provide the same property or method, and a caller doesn't have to know what class an object belongs to before calling the property or method. Say you create a basic Vehicle class and implement a Go method. Then you create Airplane and Car classes, both of which inherit from the Vehicle class. In each of these subclasses you override the Go method so that the Go method in the Car class causes the car to drive, and the Go method in the Airplane class causes the plane to fly. Now, polymorphism says that if you call the Go method on a Vehicle object that actually happens to be an Airplane, then the Airplane object's Go method is called, which causes the plane to fly, whereas if it was a Car object, its Go method would cause it to drive. The clear benefit of polymorphism is the ability to write relatively general code that can then work correctly with specialized types of objects. Most object-oriented languages provide support for polymorphism through inheritance. The Car and Airplane classes were derived from the Vehicle class. Since Visual Basic does not support implementation inheritance (the inheriting of code), you might assume that polymorphism cannot be supported. This is a common misunderstanding, since polymorphism is so often implemented as a by-product of inheritance. Visual Basic 5.0 solves this dilemma in the same fashion as COM. Inheritance is not supported in COM because the purpose of COM is to integrate objects. Polymorphism, however, lies at the heart of how COM works. You define an interface, and then any object can choose to implement it. In Visual Basic 5.0, polymorphism is supported via interface inheritance. An interface in Visual Basic is a set of related properties, methods, and events. This interface inheritance is accomplished with the Implements keyword. Let's put Implements through its paces to see what it is really made of. In the following example, we have created three class modules: Vehicle, Car, and Plane. |
Class Vehicle
Public Function Go() As String
End Function
Class Car
Implements Vehicle
Private Function Vehicle_Go() As String
Vehicle_Go = "vrooom"
End Function
Class Plane
Implements Vehicle
Private Function Vehicle_Go() As String
Vehicle_Go "fly away"
End Function
Then we put together a simple form in Visual Basic with a command button. When the button is clicked, the following code is executed to test the classes. |
Private Sub Command1_Click()
Dim MyVehicle As New Vehicle
Dim MyCar As New Car
Dim MyPlane As New Plane
Dim AnyVehicle As Vehicle 'Only a reference!
Dim MyVehicles As New Collection
MyVehicles.Add MyVehicle
MyVehicles.Add MyCar
MyVehicles.Add MyPlane
For Each AnyVehicle In MyVehicles
Print TypeName(AnyVehicle) & " goes " & AnyVehicle.Go
Next AnyVehicle
End Sub
When run, the program's results should look something like this: |
Vehicle goes
Car goes vrooom
Plane goes fly away
The Implements keyword can also be used to implement interfaces defined in type libraries. This enables you to implement any COM interface you wish. Is Visual Basic an object-oriented programming language? Visual Basic currently supports two of the three basic requirements of a true object-oriented language. So while purists might not consider it an object-oriented programming language, Visual Basic continues to evolve in that direction. Creating ActiveX Controls
ActiveX controls are supported by a wide assortment of development tools, including Visual C++, Visual J++, Visual Basic, Visual FoxPro, Borland Delphi, Power Builder, and Microsoft Access. Until recently, only Visual C++ allowed developers to create ActiveX controls. Now Visual Basic 5.0 has added ActiveX control creation to its bag of tricks. Using Visual Basic you can create controls for consumption in your own software projects, or for distribution and sale to other developers for use in their software. In fact, the ActiveX controls you create in Visual Basic can be used by a developer writing an application in Visual C++. Types of ActiveX Controls
Visual Basic defines three basic models for control creation. You can write your own control from scratch, enhance an existing control, or assemble a new control from several existing controls. The UserControl Object
An ActiveX control created in Visual Basic is always composed of a UserControl object plus any additional controlscalled constituent controlsthat you choose to place on the UserControl. This is tantamount to saying that a standard Visual Basic application is always composed of a Form object. Like a Visual Basic form, UserControl objects have a code module and a visual designer. Constituent controls are used only if you wish to enhance an existing control or to create a hybrid control consisting of several existing controls. You place constituent controls on the UserControl designer and set their properties in the same manner as you place controls on a form. Life and Times of a UserControl
While the UserControl designer is open, an ActiveX control is actually in design mode. As soon as the UserControl designer is closed, the control enters run mode automatically. The appearance of the control's icon in the Toolbox becoming enabled is the only indication that the control has entered a running state. Ambient Properties
Now let's turn our attention to properties. ActiveX controls have three basic categories of control properties: ambient, extender, and custom. Ambient properties give your control information about the state of its container. Extender properties are those that seem to be part of your control but are actually provided at runtime by the container. Custom properties are ones that you implement entirely on your own. The Extender Object
Another class of properties known as extender properties would certainly be regarded by an average user as being associated with the control but are actually implemented by the container. For example, a control's size and position, its order in the tab sequence, and the Tag property fall into the category of extender properties. These properties are associated with something called the extender object, which is an object implemented by the container, generally by aggregating with the control. When the user gets or sets a property or invokes a method on the control, the extender object gets access first. If the extender object recognizes the property or method as belonging to it, it performs the actions required; if the extender object doesn't recognize it, it is passed on to the control itself. Adding Custom Properties Custom properties are the most interesting type to talk about, primarily because it is you who implements them. Custom properties may be named anything you wish and may accept any parameters you desire. Controls rarely become useful until you add some custom properties and methods. Just as in Visual Basic class modules, the simplest type of property to implement is the kind created from a public variable with a line of code like this: |
Public PropName As String
Simple properties declared with the statement |
Public PropName As Type
are not a good idea for controls. Control properties should always be implemented with property procedures instead of public data members; otherwise your control will not work correctly in Visual Basic. This is because you must notify Visual Basic whenever a property value changes. This is done by invoking the PropertyChanged method of the UserControl object, as shown in the following code fragment: |
Private m_MyProperty As Boolean
Public Property Get MyProperty() As Boolean
MyProperty = m_MyProperty
End Property
Public Property Let MyProperty(ByVal NewValue As
Boolean)
m_MyProperty = NewValue
UserControl.PropertyChanged "MyProperty"
End Property
Without calling the PropertyChanged method, Visual Basic does not know that the property has been changed and needs to be saved. Since property values may be displayed in more than one place, the development environment must be notified when a property value changes so that it can synchronize the values. For example, you might have a property in both a property page and the Properties window that need to be synchronized. Property Persistence
If you create a program that uses an ActiveX control, and you set up the properties of that control to be just right for your application, it'll be a little disconcerting when you restart the program the next day and find that all the property values you entered have evaporated. That's why you need property persistence. |
Private Sub UserControl_InitProperties()
Private Sub UserControl_ReadProperties(PropBag As
VB.PropertyBag)
Private Sub UserControl_WriteProperties(PropBag As
VB.PropertyBag)
Visual Basic programs use the PropertyBag object to implement property persistence. A PropertyBag is just what its name implies, a "bag" in which property values are saved. You can't see into it, and you have no idea where or how the data is saved. All you can do is put values in and take them out. The PropertyBag class defines two methods, ReadProperty and WriteProperty. |
ReadProperty(Name As String, [DefaultValue])
WriteProperty(Name As String, Value, [DefaultValue])
As you can see, the first argument of the WriteProperty method is the name of the property. The Name parameter references the property name under which the Value argument will be saved. A property value is saved as a Variant. The third argument is a default value. Why provide a default value when saving a property? Before saving the value, WriteProperty compares the default value with the value passed in for saving. If the Value and DefaultValue parameters are identical, then nothing is saved. The property value doesn't need to be saved because default values will be set automatically when the control is reloaded. If you consider the great number of properties most controls have, it becomes obvious that storing and then reloading the default values for each and every one of them is time-consuming. It is usually best to define a global constant such as PROPDEFAULT_MYPROPERTYNAME to contain the default value for each property, because you need to supply it in three different places: the InitProperties, ReadProperties, and WriteProperties event procedures. The ReadProperty method works similarly. ReadProperty accepts the name of the property whose value you wish to read, and returns that value. The optional DefaultValue parameter allows you to specify the default value to be returned in the event that the property you are looking for has not been previously saved. This will always happen the first time the user puts your control on a form, since the user will not have set any properties till that point. It will also happen if the value has never been changed from the default, and therefore never saved by WriteProperty. Consequently, it is a good idea to include error-trapping code in the ReadProperties event procedure to protect your control from invalid property values that may have been entered directly into the .FRM file by overzealous users armed with text editors. Coming Soon…
In this part of the series, we breezed through some of the new features in Visual Basic 5.0 and talked in depth about the property features of the object-oriented programming supported by Visual Basic 5.0. |
From the February 1997 issue of Microsoft Systems Journal. |