Visual Basic 5.0 Relieves the Pain and Discomfort of ActiveX Control Creation, Part II
Guy Eddon and Henry Eddon
Code for this article: VB5Part2.exe (6KB)
In the first part
of our series, (February 1997) we discussed some of the new features of Visual Basic® 5.0 and then dived right into the details of the ActiveX component programming features. We covered properties in extensive detailthey are the data stored inside an ActiveX component that is exposed to the component's container via the standard ActiveX interfaces. Typical examples of properties include simple things such as the color of the control and more complex data, such as text strings. Methods
Methods are procedures built into a control that operate upon it. You can implement methods for ActiveX controls by adding Public Sub and Function procedures to the Declarations section of your control. You can write any method you want, but it is a good idea to implement some of the standard methods that seem to be found in pretty much every ActiveX control. If you have written a method that prints, use a standard method name of "Print," versus something custom like "MyOutput." How can you find a list of standard method names? The control wizard presents a list of properties, events, and methods (PEMs) that most controls expose. Additionally, it displays a list of PEMs found on any controls upon which you are basing controls. Raising Events from Controls
Before we go on, we need to hammer down some events vocabulary. Ever since Visual Basic 1.0, programmers have used events, properties, and methods, but now we have ActiveX events, properties, and methods. An example of the classic Visual Basic event happens when you click on a buttonyou get your Private Sub Command1_Click style event. An ActiveX event is different, though. The event is not sent to your control like the classic Visual Basic event. Instead, your control sends the event to its container. Inside the classic Command1_Click event handler code (actually, anywhere in your code, but generally in a classic event handler), you can "raise" an ActiveX event. This means you send an ActiveX event to the container. Of course, to make things interesting, you would also name this ActiveX event Click. |
Public Event MyEventName()
Next, you use the RaiseEvent statement to fire the event at the desired time with code like |
RaiseEvent MyEventName
The RaiseEvent statement provides functionality similar to simply calling a procedure. When calling a procedure, the procedure must actually exist. Event procedures, on the other hand, cause no errors if they are not implementedthe event is simply ignored. It is also possible to raise events that provide parameters to the event procedure being called. This is useful with events such as MouseMove that require more information about the event that occurredin this case the current mouse coordinates. To implement your own custom event with parameters, simply declare the event as something like this: |
Public Event MyEventName(MyParameter As Integer)
If you want to raise it with a parameter value of, say, 100, just call it like this: |
RaiseEvent MyEventName(100)
The control's container may provide additional events beyond those you define for the benefit of developers using your control. Visual Basic's Extender object automatically raises four events on behalf of ActiveX controls: DragDrop, DragOver, GotFocus, and LostFocus. It is recommended that most controls raise Click, DblClick, KeyDown, KeyPress, KeyUp, MouseDown, MouseMove, and MouseUp events. It is also a good idea to implement events commonly found on controls that provide functionality similar to yourssuch as a scroll event if your control has a scrollbar. Errors should never be raised within an event procedure, since there is nowhere for these errors to be trapped. For example, the Click event is fired when the user clicks on a control. If the control raises an error in response to this event, the container of the form never has a chance to respond. Visual Basic will simply report the errors and kill the application. Procedure Attributes
The majority of advanced control features in Visual Basic are available in the deceptively simple Procedure Attributes dialog box (see Figure 1), accessed from the Tools menu. Once you click the Advanced button, the dialog expands and you find yourself staring at extra queries you have to make decisions about. The Procedure Attributes dialog box allows you to control settings for the various procedures of your control. Remember that properties, methods, and events are all implemented as procedures, so this dialog box is used for all three of these items. Some of the settings will have different effects when applied to different types of procedures, and some areas of this dialog box apply exclusively to either properties or methods. |
Figure 1 Procedure Attributes dialog box |
The Procedure Attributes dialog box is subdivided into four regions, which we'll call name, property, attributes, and data binding. The name section deals with selecting, describing, and identifying specific procedures of your ActiveX control. The property region allows you to set property identification numbers, property pages, and property categories. The attributes region specifies where the property is displayed and allows you to set defaults. The data binding region is used to control Visual Basic's data-bound control model. The Name box is where you select a procedure to work within the Procedure Attributes dialog box. All settings in the Procedure Attributes dialog box affect the current procedure referenced in the Name box. Click the drop-down button on the Name box and scan the list of your procedures. For the purposes of the Procedure Attributes dialog box, procedures include properties, methods, and events. Once you select a procedure to work with in the Name box, you can type helpful text describing this procedure in the Description box. The text contained in the Description box will appear at the bottom of the Object Browser when the user selects the item. If the selected procedure is a property, the description text will also appear at the bottom of the Properties window. The Help Context ID box lets you specify an identification number for the selected procedure. The Help Context ID should match the identification number of the correct topic in the help file. This will allow the Object Browser and the Properties window to display context-sensitive help for that item when the F1 key is pressed. The Project Help File box will display the help file chosen for the Visual Basic project via the Help File Name box of the Project Properties dialog box. The Procedure ID box lets you select the type of procedure you are creating. Every property, method, and event in your type library has a procedure ID. Some have standard procedure IDs defined by the ActiveX control specification. It is always a good idea to assign the standard procedure ID to a property, method, or event, assuming there is one. For example, if you have a Caption property for your control, it would be advisable to set its procedure ID to Caption. Assigning a standard procedure ID to a procedure tells Visual Basic the purpose of the procedure in your program. In some cases, this allowsVisual Basic to enable special behavior for that property, method, or event. Every procedure can have only one procedure ID assigned, and no other procedure can have the same procedure ID. Note that if you do not change the default procedure ID from None, Visual Basic will assign one automatically. If you've worked with Visual C++, you'll recognize procedure IDs as the Visual Basic equivalent of an IDispatch interface identification number, or DISPID. The standard procedure IDs for properties are Default, Appearance, AutoSize, BackColor, BackStyle, BorderColor, BorderStyle, BorderWidth, Caption, DrawMode, DrawStyle, DrawWidth, Enabled, FillColor, FillStyle, Font, ForeColor, hWnd, TabStop, and Text. For methods you can choose from Default, AboutBox, and Refresh. For events you can choose from Click, DblClick, KeyDown, KeyPress, KeyUp, MouseDown, MouseMove, MouseUp, and Error. Property Pages
Simple properties created with public variables or property procedures appear in the Properties window automatically, allowing the user to browse and set them. Sometimes, in more complex controls, a long list of properties appears in the Properties window that relate to one another in a manner not clear to the user. Property pages solve this problem. A tabbed dialog box holds a property page, often connected to the Custom property of a control. When the user double-clicks on the Custom property, the property page dialog box is displayed, allowing the user to set properties. Property Category
The Properties window in Visual Basic has two tabs, one listing all of a control's properties in alphabetical order, and one grouping the properties by category, according to functionality. You can assign each of your control's properties to a particular group by using the Property Category box, which lists the standard categories: Appearance, Behavior, Data, DDE, Font, List, Misc, Position, Scale, and Text. You may either select from this list or create a new category by typing the new name directly into the Property Category box. Assigning categories is highly recommended; if you don't, all properties will be in the Misc group. As a general suggestion, look at other controls containing properties similar to those exposed by your control, and select your categories similarly. The first time, you may have a tendency to create too many new categories. Soon you'll realize that it is much easier to assign properties to existing categories, and that most necessary categories already exist. Attributes
In the Attributes section of the dialog box, you can control certain attributes of the procedure in question. The three choices are "Hide this member," "User Interface Default," and "Don't show in Property Browser." Checking the "Hide this member" box hides the item from view entirely. Properties will not appear in the Properties window, and neither properties nor methods will appear in the Statement Builder. (The Statement Builder automatically provides a popup list of applicable properties, methods, and events in the Code window.) Properties, methods, and events will still appear in the Object Browser because they are still part of the control's type library. This selection is often made to phase out particular items of a control's interface in successive versions of the software. Programs that rely on these properties, methods, and events will still work, but new software is unlikely to use them since they are not easily available. Data Binding
The TextBox control is a standard example of a control that can be bound to a Data control. You can also create controls that can be bound to a Data control in the same manner as the TextBox control. Visual Basic enables you to do this by marking properties of your control as bindable. A developer can then associate bindable properties with database fields, making it easier to use your control in a database application. First, the "Property is data bound" box must be checked. This automatically provides your code with a DataBindings property and dialog box, which allows the user to associate properties with data fields. Then you can choose among the "This property binds to DataField," "Show this property in the Bindings collection," and "Property will call CanPropertyChange before changing" boxes. ActiveX Controls for the Internet Now let's focus on creating ActiveX controls targeted for the Internet in Visual Basic. By this we mean an ActiveX control that runs in a Web browser. How do controls get into a Web browser? While slightly more difficult than placing a control on a Visual Basic form, it's relatively straightforward: simply add an <OBJECT> tag to the HTML document and your control will appear. |
<OBJECT ID="MyControl" WIDTH=100 HEIGHT=50
CLASSID="CLSID:DABD9872-9DA2-A772-00DBA23466AC"
CODEBASE="http://www.mycompany.com/control.ocx">
<PARAM NAME="MyProperty" value="MyValue">
</OBJECT>
For some types of controls, such as buttons and labels, this is sufficient; nothing special is necessary to Internet-enable the control. Asynchronous Downloading
Designing software for the Internet requires a slight modification in the way most people think about writing programs. When you write software for the Internet it is possible that your code will be downloaded dynamically. Even if your code is already on the user's computer, it will probably need to download data. |
AsyncRead(Target As String,
AsyncType As AsyncTypeConstants
[, PropertyName])
The AsyncRead method begins the download operation. The Target argument is the URL that points to the data to be downloaded. The AsyncType argument specifies the type of data to be downloaded. It can be one of the three values in the AsyncTypeConstants enumeration (see Figure 2). The optional Property argument defines the name by which this download operation will be known. Normally this is the property where you want the downloaded data to be stored. Note, however, that specifying a property name does not cause the data to be automatically assigned to the property. Instead, this simply allows a control to differentiate between what might be several different, asynchronous operations proceeding simultaneously. Whatever name is provided in the Property argument is later provided to the control in the AsyncReadCompleted event. Here is a sample AsyncRead call: |
AsyncRead "ftp://ftp.mycompany.com/data.dat",
vbAsyncTypeFile, "MyData"
Now, let's examine the AsyncReadComplete event: |
AsyncReadComplete(AsyncProp As AsyncProperty)
The AsyncReadComplete event notifies your program that an asynchronous download has completed. The AsyncProperty parameter is an object with three properties: AsyncType, PropertyName, and Value (see Figure 3). The first two properties are the same values passed to the AsyncRead method when the download began. This enables a control to determine which download has completed and the type of data returned. The Value parameter is a variant that contains the actual data downloaded. This is normally used to assign the data downloaded to a property of the control. The Value parameter obviously depends on the type of data downloaded. If a picture was downloaded with the vbAsyncTypePicture data type, then the Value parameter will contain an object of the Picture type. This can be immediately assigned to any Picture property, displaying the downloaded file. If a file was downloaded, the Value property will contain a string indicating the complete path of the file on the local machine. Files are normally downloaded to the C:\Windows\Temp folder and given a temporary file name like ~DFA034.TMP. The control can now open the file and read its contents via the normal file-handling features of Visual Basic, such as Open, Input, Close, and so on. If the vbAsyncTypeByteArray was used in the AsyncRead call, then the data is placed in the Value parameter as a byte array. It is assumed that the control will know how to handle this data. The CancelAsyncRead method cancels an asynchronous data download already in progress. |
CancelAsyncRead([PropertyName])
If a PropertyName argument is provided, then only the specified download is canceled. All other downloads continue normally. This method is often called in response to the user aborting a download. So how do you actually use the asynchronous download feature of Visual Basic? Let's say you want to create a control that displays a GIF file downloaded from the Internet. Since it could take a long time, you should download it asynchronously. A weather-map control that always shows the latest satellite image is a good example. This asynchronous download technique described here may be used for files on the local machine in addition to data on the Internet. One popular use of asynchronous download capabilities is progressive image rendering. That means that the image is displayed as it is downloaded, either bit-by-bit (don't take that too literally), or in low-detail followed by mid-detail and high-detail renderings. This works by having the system send notification events periodically during the download process versus only when the download is complete. This idea of progressive rendering can also be used for other types of data besides graphics. An application might simply use the notification events to display a progress bar, indicating to the user what percentage of the download is complete. When graphic data is not involved, the more general term progressive data retrieval is used. Although the Asynchronous Moniker Specification from Microsoft provides support for progressive data retrieval, Visual Basic currently does not. The Hyperlink Object
Part of the ActiveX specification defines an area of functionality called ActiveX hyperlinking, which defines a model for how applications, documents, and controls interact during hyperlink navigation. The Web is known for its ease of use; the ActiveX hyperlinking specification is meant to offer a similar navigation model to your applications. A Sample Internet Control
The kind of control we'll implement here is one you might already have on your machine. In fact, if you use Microsoft Internet Explorer 3.0 as your Web browser, and have ever visited http://www.microsoft.com/activex, then this control was automatically downloaded and installed on your computer. The control I am speaking of is the popup menu that appears when you press on the little down arrows at the top of the Web page. The popup menus allow you to navigate to different resources available on the ActiveX Web site. Let's duplicate the functionality of Microsoft's Internet menu control in Visual Basic.
|
This still lacks one piece. When the control goes to display the popup menu, how does it know what items to include in the menu? Clearly we need some mechanism to enable the container to tell the control ahead of time what items to display in the menu. We have basically two choicesa property or a method. A method is normally used to perform some action on a control, while a property is used to set a data attribute. What we want here, the ability to define the items in a menu, fits better into the model of a property. So let's define a property called MenuItem that the container will set to define the menu. Since there will usually be more than one menu item, we will need an array of MenuItems. To better understand the interface, I find it helpful to imagine the code that the container might execute to program the control. On startup, a container would execute the following code to define the menu items. |
MyMenuControl1.MenuItem(1) = "www.microsoft.com"
MyMenuControl1.MenuItem(2) = "www.netscape.com"
MyMenuControl1.MenuItem(3) = "www.ibm.com"
Implementation Well, that pretty much rounds out the design of the control. Let's see how it is implemented. Obviously, we will use a property procedure for the MenuItem property. Creating a property procedure that pretends to be an array is a bit tricky. The Property Let procedure will take two arguments.The first argument of the Property Let procedure will be the index into the array, and the second will be the actual value, as shown here: |
Public Property Let MenuItem(ItemNumber As Integer,
Caption As String)
If ItemNumber <> 1 Then
Load mnuPopupItem(ItemNumber)
End If
mnuPopupItem(ItemNumber).Caption = Caption
End Property
The code tests if the index number is one because it depends on a menu array, an array of items defined in Visual Basic's Menu Editor. For this control, I have used the Menu Editor to define a menu called mnuPopup that contains an mnuPopupItem. A menu is usually not defined for most controls, since a control does not have a menu bar. The only reason for a control to define a menu at all is the popup menu facility described here. mnuPopupItem is the first item in the menu array, defined by setting the item's Index property to one. If the index in this code is one, then the menu item already exists and the code simply sets the caption. Otherwise, the Load statement is used to create a new menu item in the array before setting the caption. Although not required in this case, it is good design to have a Property Get procedure to go along with the Property Let procedure defined above. What makes this pair of property procedures interesting is that the data sent by the container is not saved by the control. Since the data is being stored by Visual Basic in the popup menu, the data can simply be retrieved from there. |
Public Property Get MenuItem(ItemNumber As Integer) As String
MenuItem = mnuPopupItem(ItemNumber).Caption
End Property
Now that the menu is defined, the control needs to display it when clicked. Responding to the classic UserControl_MouseDown event gives us a chance to do this: |
Private Sub UserControl_MouseDown(Button As Integer,
Shift As Integer,
X As Single,
Y As Single)
PopupMenu mnuPopup
End Sub
Once the menu is displayed and the user makes a selection, the control will be notified by way of the mnuPopupItem_Click event procedure. At this point, the container of the control needs to be notified. Raising events is usually the best way to notify your container that something has happened to the control. While we could have been rude and defined a custom event named, say, ItemSelected, we chose instead to use the standard Click event. First, in the Declarations section of the control, we declared the event: |
Event Click(ItemNumber As Integer)
Then, in response to a selection from the popup menu, we raise the event, passing the Index number of the selected item as a parameter to the receiving event procedure. |
Private Sub
mnuPopupItem_Click(
Index As Integer)
RaiseEvent Click(
Index)
End Sub
At this point we have most of the pieces in place. The Click event procedure is raised in the container in response to a selection. What you do here is entirely up to you. In the Visual Basic form used for testing, we simply display the item that was selected from the popup menu. |
Private Sub
MyMenuControl1_Click(
ItemNumber As Integer)
Print MyMenuControl1.MenuItem(
ItemNumber)
End Sub
Although we used a Visual Basic form to test this control, a popup menu control for Visual Basic programmers would probably not have large appeal since Visual Basic already provides the very easy-to-use PopupMenu statement. However, used in a Web page, this control could help enable users to navigate the page in a simple manner. To insert the control described above into a Web page, you first need to compile it as an OCX. Then you might use the ActiveX control Pad to insert it into a Web page, or you might enter the <OBJECT> tag directly into an HTML document as shown here: |
<OBJECT ID="MyMenuControl" WIDTH=100 HEIGHT=51
CLASSID="CLSID:DDBD9872-9DA2-A772-00DBA23466AC"
CODEBASE="menuctl.ocx">
<PARAM NAME="MenuItem(0)" value="www.microsoft.com">
<PARAM NAME="MenuItem(1)" value="www.netscape.com">
<PARAM NAME="MenuItem(0)" value="www.ibm.com">
</OBJECT>
Be sure to look up the CLSID number from the Windows registry and replace the dummy class identification number shown here. Then when the user has made a selection from the popup menu, you could intercept the event using VBScript code in the Web page: |
<SCRIPT LANGUAGE="VBScript"><!
Sub MyMenuControl_Click(ItemNumber)
Select Case ItemNumber
Case 1
top.location="www.microsoft.com"
Case 2
top.location="www.netscape.com"
Case 3
top.location="www.ibm.com"
End Select
End Sub
></SCRIPT>
ActiveX Documents: The Future of Forms
Visual Basic supports the creation of ActiveX documents via two special project types: ActiveX document DLL and ActiveX document EXE. ActiveX document forms, represented by the UserDocument object, can also be added to ActiveX code components such as ActiveX EXE and ActiveX DLL projects. In fact, an ActiveX document DLL is really an ActiveX DLL with a default UserDocument object instead of a default class module. The same is true of an ActiveX document EXE. ActiveX controls and standard EXEs do not support the UserDocument object. Just as with any other project type class modules, code modules, forms, and other Visual Basic constructs can be added to any ActiveX document projects. |
Figure 5 ActiveX documents are like sophisticated form objects |
Before we go any further, we should explain how ActiveX documents differ from ActiveX controls. While ActiveX controls are used as components to build other applications, ActiveX documents are the program. You don't, for example, put a bunch of ActiveX documents on a form, as you do with ActiveX controls. At runtime ActiveX documents are a combination of a user interface and data. The ActiveX document is the form, and you put ActiveX controls on it. ActiveX documents may have properties, but those properties won't show up in the Properties window. Also, there's not much point in having an ActiveX document fire an event. Who would respond to it? ActiveX documents are really more like sophisticated form objects (see Figure 5). The UserDocument Object
ActiveX documents are supported in Visual Basic by a special UserDocument object, just as controls are supported by the UserControl object. The UserDocument object has a visual designer that looks identical to that of controls. You design and lay out your document using any desired control, just as you would design a standard Visual Basic form. You also set properties, call methods, and respond to events in standard Visual Basic fashion. UserDocument objects are saved in .DOB files by Visual Basic. Conclusion
We have just scratched the surface of the potential uses of Visual Basic 5.0. In addition to its native code generation and vastly improved user interface, developers can now use Visual Basic to write ActiveX controls, document objects, and Web applications. Properties, methods, and events work both in the classic Visual Basic context and in the new ActiveX context. |
From the March 1997 issue of Microsoft Systems Journal. |