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.