ActiveX controls

Tricks of the masters

At this point, you know everything you need to create ActiveX controls that match or even exceed the quality of commercial controls. There are a few advanced techniques, however, that even many experienced programmers aren't aware of. As I'll prove in this section, you don't always need to know all the intricacies of Windows and ActiveX programming to deliver efficient controls because, in most cases, Visual Basic is all you need.

Callback methods

Raising an event in the parent form from within an ActiveX control is easy, but it isn't the only method you can use to let the two objects communicate with each other. In Chapter 16, I showed you how an object can notify another object that something has occurred by using callback methods. Callback methods have several advantages over events: They're about 5 or 6 times faster on average and, more important, they aren't blocked when the client form is showing a message box in an interpreted program.

On the companion CD, you'll find the complete source code for the SuperTimer ActiveX control, which implements a Timer that can communicate with its parent form using a callback mechanism based on the ISuperTimerCBK interface (a PublicNotCreatable class contained in the ActiveX control project). When a form or any other container implements this interface, it can have the SuperTimer control send its notifications through that interface's only member, the Timer method. This is the source code for a typical form that uses this SuperTimer control:

Implements ISuperTimerCBK

Private Sub Form_Load()
    Set SuperTimer1.Owner = Me
End Sub

Private Sub ISuperTimerCBK_Timer()
    ' Do whatever you want here.
End Sub

The SuperTimer control contains a Timer1 constituent control that raises a Timer event in the UserControl module; in this procedure, the control decides whether it has to raise an event or invoke a callback method:

Public Owner As ISuperTimerCBK

Private Sub Timer1_Timer()
    If Owner Is Nothing Then
        RaiseEvent Timer      ' Fire a regular event.
    Else
        Owner.Timer           ' Fire a callback method.
    End If
End Sub

Interestingly, in an interpreted program the Timer event in a standard Timer control doesn't fire if the client form is showing a message box. (Timers are never blocked in compiled programs, though.) You don't suffer from this limitation if you use the ISuperTimerCBK interface of the SuperTimer OCX control, which therefore proves to be more powerful than a regular Timer control. (See Figure 17-17.) But you have to compile the SuperTimer control into an OCX file for this feature to work properly. (When the UserControl module runs in the Visual Basic IDE, modal windows in the client applications block events also in the ActiveX control.)

Tip

The demonstration program of the SuperTimer control displays different messages if the application is running in the IDE or as a compiled program. The Visual Basic language lacks a function that lets you distinguish between the two modes, but you can take advantage of the fact that all the methods of the Debug object aren't compiled in EXE programs and therefore are executed only when the application is running in the IDE. Here's an example of this technique:

Function InterpretedMode() As Boolean
    On Error Resume Next
    Debug Print 1/0                 ' This causes an error
    InterpretedMode = (Err <> 0)    ' but only inside the IDE.
    Err Clear                       ' Clear the error code. 
End Function

The preceding code is based on a routine that appeared in the Tech Tips supplement of the Visual Basic Programmer's Journal.


Figure 17-17.
A compiled SuperTimer control can send callback methods to the parent form even if a message box is being displayed.

Faster calls with VTable binding

As you know, all references to external ActiveX controls-but not intrinsic Visual Basic controls-implicitly use their Extender objects. What you probably don't know is that all references to the Extender use early ID binding instead of the most efficient VTable binding. This means that calling a method in an ActiveX control is slower than calling the same method if the object were encapsulated in an ActiveX DLL component because objects in DLLs are referenced through VTable binding.

In general, ID binding doesn't seriously impair the performance of your ActiveX control because most properties and methods implement the user interface and are sufficiently fast even on low-end machines. But sometimes you might need more speed. Say that you have a ListBox control that you want to fill as rapidly as possible with data read from a database or an array in memory: in this situation, you need to call a property or a method several thousand times, and the overhead of ID binding wouldn't be negligible.

A solution to this problem is conceptually simple. You add a PublicNotCreatable class to your ActiveX Control project that exposes the same properties and methods as those exposed by the ActiveX control. The class does nothing but delegate the execution of the properties and methods to the main UserControl module. Whenever the ActiveX control is instantiated, it creates a companion Public object and exposes it as a read-only property. The client form can store the return value of this property in a specific object variable and call the ActiveX control's members through this secondary object. This object doesn't use the Extender object and therefore can be accessed through VTable binding instead of ID binding.

I found that accessing UserControl's properties through this companion object can be about 15 times faster than through the regular reference to the ActiveX control. On the companion CD, you'll find a demonstration project whose only purpose is to show you what kind of performance you can get using this approach. You can use it as a model to implement this technique in your own ActiveX control projects.

Secondary interfaces

An alternative way to use VTable binding for super-fast ActiveX controls is to have the ActiveX control implement a secondary interface and have the client form access the secondary interface instead of the primary interface. This approach is even faster than the one based on a secondary PublicNotCreatable object because you don't need a separate class that delegates to the main ActiveX control module. Another benefit of this approach is that the same interface can be shared by multiple ActiveX controls so that you can implement a VTable-based polymorphism among different but related ActiveX controls.

The implementation of this approach isn't difficult, but beware of one difficulty. Say that you create an ActiveX control that contains an Implements IControlInterface statement at the beginning of its code module. Your goal is to take advantage of this common interface in the client form by assigning a specific ActiveX control instance to an interface variable. Unfortunately, the following sequence of statements raises an error:

' In the client form
Dim ctrl As IControlInterface
Set ctrl = MyControl1                      ' Error "Type Mismatch"

The problem, of course, is that the MyControl1 object in the client code uses the ActiveX control's Extender interface, which doesn't inherit the IControlInterface interface. To access that interface, you need to bypass the Extender object, as follows:

Set ctrl = MyControl1.Object 

Trapping events with multicasting

Multicasting lets you trap events raised by any object that you can reference through an object variable. (I described multicasting in Chapter 7, so you might want to review those pages before reading what follows.) The good news is that multicasting also works with ActiveX controls, even if a control has been compiled into a stand-alone OCX file. In other words, your ActiveX control can trap events fired by the parent form, or even by other controls on the form itself.

To give you a taste of what you can do with this technique, I have prepared a simple ActiveX control that automatically resizes itself to cover the entire surface of its parent form. If it weren't for multicasting, this feature would be extremely difficult to implement because it requires you to subclass the parent form to be notified when it's being resized. Thanks to multicasting, the amount of code you need to implement this feature is amazingly little:

Dim WithEvents ParentForm As Form

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    On Error Resume Next         ' In case parent isn't a form.
    Set ParentForm = Parent
End Sub

' This event fires when the parent form resizes.
Private Sub ParentForm_Resize()
    Extender.Move 0, 0, Parent.ScaleWidth, Parent.ScaleHeight
End Sub

The multicasting technique has an infinite number of applications. For example, you can build an ActiveX control that always displays the sum of the values contained in TextBox controls on the form. For this task, you need to trap those controls' Change events. When trapping the events of an intrinsic control, your UserControl module must declare a WithEvents variable of a specific object type, but when trapping events from external ActiveX controls-for example, a TreeView or MonthView control-you can use a generic VBControlExtender object variable and rely on its one-size-fits-all ObjectEvent event.

ActiveX Controls for the Internet

Many programmers believe that the Internet is the natural habitat for ActiveX controls, so you might have been surprised that I haven't described Internet-specific features until the end of the chapter. The plain truth is that, Microsoft's plans notwithstanding, Microsoft Internet Explorer still is, as I write these pages, the only popular browser that natively supports ActiveX controls, at least without any plug-in modules. So if you heavily use ActiveX controls in HTML pages, you automatically reduce the number of potential users of your Web site. You see, ActiveX controls probably aren't very useful for the Internet, even though they might find their way into intranets, where administrators can be sure about which browser is installed on all client machines. As far as the Internet is concerned, however, Dynamic HTML and Active Server Pages seem to offer a better solution for building dynamic and "smart" pages, as I explain in the section devoted to Internet programming.

Programming Issues

In general, ActiveX controls in HTML pages can exploit the additional features provided by the browser in which they're running. In this section, I briefly describe the new methods and events that such controls can use. But first of all, you need to understand how an ActiveX control is actually placed in an HTML page.

ActiveX controls on HTML pages

You can place a control in a page using a number of HTML Page editors. For example, following is the code that Microsoft FrontPage produces for an HTML page that includes my ClockOCX.ocx control, whose source code is available on the companion CD. Notice that the control is referenced through its CLSID, not its more readable ProgID name. (The HTML code that refers to the ActiveX control is in boldface.)

<HTML>
<HEAD>
<TITLE>Home page</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF">
<H1>A web page with an ActiveX Control on it.</H1>
<OBJECT CLASSID="clsid:27E428E0-9145-11D2-BAC5-0080C8F21830"
    BORDER="0" WIDTH="344" HEIGHT="127">
    <PARAM NAME="FontName" VALUE="Arial">
    <PARAM NAME="FontSize" VALUE="24">
</OBJECT>
</BODY>
</HTML>

As you can see, all the information concerning the control is enclosed by the <OBJECT> and </OBJECT> tags, and all initial properties values are provided in <PARAM> tags. These values are made available to the control in its ReadProperties event procedure. (If there are no <PARAM> tags, the control could receive an InitProperties event instead, but the exact behavior depends on the browser.) ActiveX controls intended to be used on Web pages should always expose Fontxxxx properties instead of, or together with, the Font object property because assigning object properties in an HTML page isn't simple.

When you're using an ActiveX control on a Web site, many things can go wrong-for example, references to Extender properties that aren't available under the browser. Visual Basic 6 offers a couple of ways to reduce the guesswork when it's time to fix these errors. The first option is to start the component from within the IDE and wait until the browser creates an instance of the control. The second option is to have Visual Basic create an empty HTML page with just the ActiveX control on it and automatically load it into the browser. You can select these options in the Debugging tab of the Project Properties dialog box, as shown in Figure 17-18.


Figure 17-18.
The Debugging tab of the Project Properties dialog box.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone” - Bjarne Stroustrup