Lifecycle of a Control
UserControl are objects,
and as such they receive several events during their lifetime. ActiveX controls
actually have a double life because they're also alive when the environment is
in design mode.
Creation
Initialize
is the first event that a UserControl receives. In this event, no Windows resources
have been allocated yet so you shouldn't refer to constituent controls, exactly
as you avoid references to controls on a form in the form's Initialize event.
For the same reason, the Extender and AmbientProperties objects aren't available
in this event. (These objects are described in the following sections.)
After the Initialize
event, the UserControl creates all its constituent controls and is ready
to be sited on the client form's surface. When the siting completes, Visual Basic
fires an InitProperties or ReadProperties event, depending on whether
the control has been just dragged on the form from the Toolbox or the form is
being reopened from a previous session. During these events, the Extender and
the Ambient objects are finally available.
Just before becoming visible,
the UserControl module receives the Resize event, and then the Show
event. This event is more or less equivalent to the Activate event,
which isn't exposed by UserControl modules. Finally the UserControl module receives
a Paint event (unless its AutoRedraw property is True).
When a control is re-created
at design time because its parent form is closed and then reopened, the complete
sequence is repeated with the only differences being that the InitProperties
event never fires and the ReadProperties event fires instead, immediately
after the Resize event.
Termination
When the developer closes
the parent form at design time, or when the program switches to run-time mode,
Visual Basic destroys the design-time instance of the ActiveX control. If the
developer modified one or more properties in the control, the UserControl module
receives a WriteProperties event. During this event, Visual Basic doesn't
write anything to the FRM file and simply stores values in the PropertyBag object
kept in memory. This event fires only if the programmer modified the attributes
of any control on the form (or of the form itself), but not necessarily the UserControl
you're working with. A control informs you that one of its properties has changed
and that the FRM file needs to be updated by calling the PropertyChanged method.
When the control is removed from its container, a Hide event occurs. (ActiveX
controls in HTML pages receive this event when the user navigates to another
page.) This event broadly corresponds to a form's Deactivate event: The
ActiveX control is still in memory, but it isn't visible any longer.
The last event in the life
of an ActiveX control is Terminate; during this event, you usually close
any open files and return any system resources that you allocated in the Initialize
event procedure. The code in this event can't access the Extender and AmbientProperties
objects.
Other event
sequences
When the developer runs
the program, Visual Basic destroys the design-time instance of the ActiveX control,
and creates a run-time instance so that the control can receive all the events
described previously. The main difference between design-time and run-time instances
is that the latter ones never receive a WriteProperties event.
When you reopen the project,
you start another special sequence of events: Now a new instance of the control
is created, and it receives all the usual events that fire during creation plus
a WriteProperties event that serves to update the PropertyBag object in
memory.
Finally, when a form module
is compiled, Visual Basic creates a hidden instance of it and then queries the
properties of all its ActiveX controls so that the compiled program can use the
most recent property values. Each ActiveX control receives the Initialize,
Resize, ReadProperties, Show, WriteProperties, Hide,
and Terminate events. You don't need to perform any special actions during
these events. I mention this information only because if your code contains breakpoints
or MsgBox commands, they might interfere with the compilation process.
The Extender
Object
When you created a UserControl
module and you placed an instance of it on a client form, you might have noticed
that the Properties window isn't empty, as shown in Figure 17-2. Where did those
properties come from?
It turns out that Visual
Basic's forms don't use the ActiveX control directly. Instead, they wrap the
control within an intermediate object known as the Extender object. This object
exposes to the programmer all the properties defined in the ActiveX control,
plus a number of properties that Visual Basic adds for its own purposes. For
example, Name, Left, Top,and Visible are
Extender properties and so you don't have to implement them in the UserControl
module. Other Extender properties are Height, Width, Align,
Negotiate, Tag, Parent, Container, ToolTipText,
DragIcon, DragMode, CausesValidation, TabIndex, TabStop,
HelpContextID,and WhatsThisHelpID.
The Extender object also
provides methods and events of its own. For example, the Move, Drag,
SetFocus, ShowWhatsThis, and ZOrder methods are provided
by the container (and in fact, all of them are related to Extender properties
in one way or another), as are the GotFocus, LostFocus, Validate,
DragDrop,and DragOver events. The perspective of the programmer
who uses the ActiveX control is different from the perspective of the control's
author, who sees fewer properties, methods, and events.
Reading Extender
properties
At times, however, you need
to access Extender properties from within the UserControl module. You can do
this by means of the Extender property, which returns an object reference
to the same Extender interface that's used by the programmer using the control.
A typical example of why this might be necessary is when you want your ActiveX
control to display its Name property, as most Visual Basic controls do
as soon as they're created. To add this feature to the SuperTextBox ActiveX control,
you simply need a statement in the InitProperties event procedure:
Private Sub UserControl_InitProperties()
On Error Resume Next
Caption = Extender.Name
End Sub
You might wonder why you
need an error handler to protect a simple assignment like the preceding one.
The reason is that you can't anticipate the environments in which your ActiveX
control will be used, so you have no guarantee that the host environment will
support the Name property. If it doesn't, the Extender.Name reference
fails, and the error will prevent developers from using your control in those
environments. In general, different hosts add different Extender members. Visual
Basic is probably the most generous environment in terms of Extender properties.
The Extender object is built
at run time by the host environment, so the Extender property is defined
to return a generic Object. As a result, all the Extender members such as Name
or Tag are referenced through late binding. This circumstance explains
why accessing those members tends to slow down the code inside your UserControl
module and at the same time makes it less robust. Because you can't be sure about
which members the Extender object will expose at run time, you shouldn't let
your ActiveX control heavily rely on them, and you should always arrange for
your control to degrade gracefully when it runs under environments that don't
support the features you need.
Finally, keep in mind that
a few Extender properties are created only under certain conditions. For example,
the Align and Negotiate properties are exposed only if the UserControl's
Alignable property is set to True, and the Default and Cancel
properties exist only if the UserControl's DefaultCancel property
is True. Likewise, the Visible property is unavailable if the InvisibleAtRuntime
property is True.
Setting Extender
properties
In general, modifying an
Extender property from within the UserControl module is considered bad programming
practice. I found that under Visual Basic 6 all the Extender properties can be
written to, but this might not be true for other environments or for previous
versions of Visual Basic itself. In some cases, setting an Extender property
provides added functionality. For example, see how you can implement a method
that resizes your ActiveX control to fit its parent form:
Sub ResizeToParent()
Extender.Move 0, 0, Parent.ScaleWidth, Parent.ScaleHeight
End Sub
This routine is guaranteed
to work only under Visual Basic because other environments might not support
the Move Extender method, and also because you can't be sure that, if
a Parent object actually exists, it also supports the ScaleWidth and ScaleHeight
properties. If any of the preceding conditions aren't met, this method raises
an error 438, "Object doesn't support this property or method."
From the container's point of view, Extender properties have
a higher priority than the UserControl's own properties. For example, if the
UserControl module exposes a Name property, the client code-at least the
client code written in Visual Basic-will actually refer to the Extender property
with the same name. For this reason, you should carefully pick the names of your
custom properties and stay clear of those automatically added by the most popular
containers, such as Visual Basic and the products in the Microsoft Office suite.
|
Tip
You might intentionally expose properties that are duplicated in the Extender
object so that users of your ActiveX control can find that property regardless
of what programming language they're using. For example, you can define a
Tag property (of type String or Variant) so that your control provides
it even when it runs in an environment other than Visual Basic.
|
The Object
property
This visibility rule raises
an interesting question: How can the user of the ActiveX control directly access
its interface and bypass the Extender object? This is possible thanks to the
Object property, another Extender property that returns a reference to
the inner UserControl object. This property is sometimes useful to developers
who are using the ActiveX control, as in this code:
' Set the Tag property exposed by the UserControl module.
' Raises an error if such property isn't implemented
SuperTextBox1.Object.Tag = "New Tag"
You never need to use the
Extender.Object property from within the UserControl module because it
returns the same object reference as the Me keyword.
The AmbientProperties
Object
An ActiveX control often
needs to gather information about the form on which it has been placed. For example,
you might want to adapt your ActiveX control to the locale of the user or to
the font that's used by the parent form. In some cases, you can gather this information
using the Extender or Parent object (for example, using Parent.Font).
But there's a better way.
Conforming to
the parent form settings
The UserControl object's
Ambient property returns a reference to the AmbientProperties object,
which in turn exposes several properties that provide information about the environment
in which the ActiveX control runs. For example, you can find out what font is
being used by the parent form using the Ambient.Font property, and you
can determine which colors have been set for the parent form using the Ambient.ForeColor
and Ambient.BackColor properties. This information is especially useful
when you create the control and you want to conform to the parent form's current
settings. See how you can improve the SuperTextBox control so that it behaves
like Visual Basic's own controls:
Private Sub UserControl_InitProperties()
' Let the label and the text box match the form's font.
Set CaptionFont = Ambient.Font
Set Font = Ambient.Font
' Let the label's colors match the form's colors.
CaptionForeColor = Ambient.ForeColor
CaptionBackColor = Ambient.BackColor
End Sub
The AmbientProperties object
is provided by the Visual Basic runtime, which always accompanies the ActiveX
control, rather than by the Extender object, which is provided by the host environment.
References to the AmbientProperties object rely on early binding, and the Visual
Basic runtime automatically supplies a default value for those properties that
aren't available in the environment. This detail has two consequences: Ambient
properties are faster than Extender properties, and you don't need an error handler
when referring to an Ambient property. For example, the AmbientProperties object
exposes a DisplayName property, which returns the name that identifies
the control in its host environment and lets you initialize the caption of your
control:
Private Sub UserControl_InitProperties()
Caption = Ambient.DisplayName
End Sub
This code should always
be preferred to the method based on the Extender.Name property because
it delivers a reasonable result under any environment and doesn't require an
On Error statement.
Another ambient property
that you might find useful is TextAlign, which indicates the preferred
text alignment for the controls on the form. It returns one of the following
constants: 0-General, 1-Left, 2-Center, 3-Right, 4-FillJustify. If the host environment
doesn't provide any information about this feature, Ambient.TextAlign returns
0-General (text to the left, numbers to the right).
If your control contains
a PictureBox control, you should set its Palette property equal to the
Ambient.Palette property if possible so that the bitmaps on your control
don't look strange when the PictureBox constituent control doesn't have the input
focus.
The UserMode
property
The UserMode property
is probably the most important Ambient property because it lets the author of
the ActiveX control know whether the control is being used by the developer (UserMode
= False) or the user (UserMode = True). Thanks to this property, you
can enable different behaviors at design time and run time. If you find it difficult
to remember the meaning of the return value of this property, just recall that
the "user" in UserMode is the user. See the "Read-Only
Properties" section later in this chapter for an example that shows how
this property can be useful.
The AmbientChanged
event
You can immediately find
out when an ambient property changes by trapping the AmbientChanged event.
This event receives a string argument equal to the name of the ambient property
being changed. For instance, you can allow the BackColor property of your
UserControl to automatically match the background color of the parent form by
writing this code:
Private Sub UserControl_AmbientChanged(PropertyName As String)
If PropertyName = "BackColor" Then BackColor = Ambient.BackColor
End Sub
Here's an exception: If
you change the parent form's FontTransparent or Palette properties,
the ActiveX controls on the form don't receive any notification. The AmbientChanged
event is raised both at design time and at run time, so you might need to
use the Ambient.UserMode property to differentiate between the two cases.
The AmbientChanged event
is most important within user-drawn controls that expose a Default property.
These controls must repaint themselves when the value of this property changes:
Private Sub UserControl_AmbientChanged(PropertyName As String)
If PropertyName = "DisplayAsDefault" Then Refresh
End Sub