ActiveX controls

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 

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.

“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.” - Donald Knuth