ActiveX controls

Transparent Controls

Visual Basic offers you many ways to create irregularly shaped controls. To begin with, if you set the BackStyle property of the UserControl object to 0-Transparent, the background of the control-that is, the portion of the control that isn't occupied by constituent controls-becomes transparent and lets the user see what's behind the control itself. When a control has a transparent background, all the mouse events go directly to the container form or to the control that happens to be under the ActiveX control in the z-order. In addition, Visual Basic ignores the BackColor and Picture properties for such an ActiveX control and all the output from graphic methods is invisible. Not surprisingly, transparent controls are also more demanding in terms of CPU time because, while repainting, Visual Basic has to clip all the areas that don't belong to the controls.

Using Label and Shape controls

If your transparent control includes one or more Label controls that use a TrueType font and whose BackStyle property is also set to 0-Transparent, Visual Basic clips all the pixels around the characters in the Label. Only the caption of the Label is considered to belong to the ActiveX control, and all the other pixels in the Label are transparent. For example, if you click inside a letter Oin the caption, a Click event is raised in the parent form or in the control that shows through. I noticed that this feature works decently only with larger font sizes, however.

You can create a large variety of nonrectangular controls using Shape controls as constituent controls. (You can see one example on the companion CD.) If you set the Shape control's BackStyle property to 0-Transparent, all the pixels that fall outside the Shape control are transparent. For example, to create an elliptical radio button, you drop a Shape1 constituent control, set its Shape property to 2-Oval, and set both the UserControl's and Shape control's BackStyle property to 0-Transparent. Then you need only some code that resizes the Shape control when the UserControl resizes and that refreshes the control's appearance when the Value property changes. Following is a partial listing for the UserControl code module.

' Change the color when the control is clicked.
Private Sub UserControl_Click()
    Value = True
    RaiseEvent Click
End Sub

Private Sub UserControl_Resize()
    Shape1.Move 0, 0, ScaleWidth, ScaleHeight
End Sub

Public Sub Refresh()
    ' TrueColor and FalseColor are Public properties. 
    Shape1.BackColor = IIf(m_Value, TrueColor, FalseColor)
    Shape1.FillColor = Shape1.BackColor
End Sub

' Value is also the default property.
Public Property Get Value() As OLE_OPTEXCLUSIVE
    Value = m_Value
End Property
Public Property Let Value(ByVal New_Value As OLE_OPTEXCLUSIVE)
    m_Value = New_Value
    Refresh
    PropertyChanged "Value"
End Property

The problem with using Shape controls to define irregularly shaped controls is that you can't easily use graphic methods to draw over them. The reason is that Visual Basic redraws the Shape control after raising the Paint event, so the Shape control covers the graphic you've produced in the Paint event. An easy way to work around this limitation is to activate a Timer in the Paint event and let the drawing occur in the Timer's Timer procedure, some milliseconds after the standard Paint event. Use this code as a guideline:

Private Sub UserControl_Paint()
    Timer1.Interval = 1        ' One millisecond is enough.
    Timer1.Enabled = True
End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False     ' Fire just once.
    ' Draw some lines, just to show that it's possible.
    Dim i As Long
    For i = 0 To ScaleWidth Step 4
        Line (i, 0)-(i, ScaleHeight)
    Next
End Sub

As far as I know, the only other way to solve this problem is by subclassing the UserControl to run some code after the standard processing of the Paint event. (Subclassing techniques are described in the Appendix.)

Using the MaskPicture and MaskColor properties

If the shape of your transparent control is too irregular to be rendered with one Shape control (or even with a group of Shape controls), your next best choice is to assign a bitmap to the MaskPicture property and then to assign the color that should be considered as transparent to the MaskColor property. The bitmap is used as a mask, and for each pixel in the bitmap whose color matches MaskColor, the corresponding pixel on the UserControl becomes transparent. (Constituent controls are never transparent, even if they fall outside the mask region.) You also need to set the Backstyle property to 0-Transparent for this technique to work correctly.

Using this process, you can create ActiveX controls of any shape, including ones that have holes in them. Probably the only serious limitation of this approach is that you can't easily create a mask bitmap that resizes with the control because you can assign the MaskPicture property a bitmap, GIF, or JPEG image, but not a metafile.

Lightweight Controls

Visual Basic 6 permits you to write lightweight ActiveX controls that consume fewer resources at run time and therefore load and unload faster. The UserControl object exposes two new properties that let you fine-tune this capability.

The HasDC and Windowless properties

The HasDC property determines whether the UserControl creates a permanent Windows device context or uses a temporary device context when the control is redrawn and during event procedures. Setting this property to False can improve performance on systems with less memory. For more information about this property, see the "Fine-Tuning the Performance of Forms" section in Chapter 2.

Setting the Windowless property to True creates an ActiveX control that doesn't actually create a window and therefore consumes even fewer resources. A windowless control has a couple of limitations, however. It must be user-drawn or contain only other windowless controls, and it can't work as a container for other controls. You can't place regular constituent controls on a windowless ActiveX control, and you can't set the Windowless property to True if the UserControl already includes nonwindowless constituent controls. Image, Label, Shape, Line, and Timer are the only intrinsic controls that you can place over a windowless UserControl. If you need features that these controls don't provide, have a look at the Windowless control library mentioned in the "Limitations and Workarounds" section earlier in this chapter.

Not all containers support windowless controls. Among the environments that do are Visual Basic 5 and 6, Internet Explorer 4 or later, and all the environments based on Visual Basic for Applications. Interestingly, when a windowless control runs in an environment that doesn't support this feature, the windowless control automatically turns into a regular control that's backed up by a real window.

A windowless control doesn't expose an hWnd property, so you can't call API functions to augment its functionality. (In some cases, you can use the ContainerHwnd property instead.) Moreover, the EditAtDesign and BorderStyle properties are disabled for windowless ActiveX controls. The HasDC property is usually ignored as well because windowless controls never have a permanent device context. But you should set this property to False because if the control runs in an environment that doesn't support windowless ActiveX controls, it won't, at least, use resources for a permanent device context.

Transparent windowless controls

You can create a windowless control that has a transparent background by setting its BackStyle property to 0-Transparent and assigning a suitable bitmap to the MaskPicture. But you should also consider the new HitTest event and the HitBehavior and ClipBehavior properties.

Before I show you how to use these new members, you need to understand what the four regions associated with a control are. (See Figure 17-9.) The Mask region is the nontransparent portion of a control, which includes all the constituent controls and other areas that contain the output from graphic methods. (In regular controls, this is the only existing region.) The Outside region is the area outside the Mask region, while the Transparent region is any area inside the Mask region that doesn't belong to the control (the holes in the control). Finally, the Close region is an area that encircles the Mask region and whose width is determined by the author of the ActiveX control.


Figure 17-9.
The four regions associated with a transparent control.

The problem with managing mouse actions over a transparent control is that Visual Basic doesn't know anything about the Close and Transparent regions, and it can only determine whether the mouse cursor is on the Mask region or in the Outside region. The problem is even worse when there are multiple overlapping controls, each one with its own Close or Transparent region, because Visual Basic has to decide which one will receive the mouse event. To let the control decide whether it wants to manage the mouse action, Visual Basic fires one or more HitTest events in all the controls that are under the mouse cursor, in their z-order. (That is, it fires the first event in the control that's on top of all others.) The HitTest event receives the x and y coordinates of the mouse cursor and a HitTest argument:

Sub UserControl_HitTest(X As Single, Y As Single, HitResult As Integer)
    ' Here you manage the mouse activity for the ActiveX control.
End Sub

The possible values for HitResult are 0-vbHitResultOutside, 1-vbHitResultTransparent, 2-vbHitResultClose, and 3-vbHitResultHit. Visual Basic raises the HitTest event multiple times, according to the following schema:

  • A first pass is made through the controls from the topmost to the bottommost control in the z-order; if any control returns HitResult = 3, it receives the mouse event and no more HitTest events are raised.

  • If no control returns HitResult = 3, a second pass is performed; if any control returns HitResult = 2, it receives the mouse event and no more HitTest events are raised.

  • If no control returns HitResult = 2, one more pass is performed; if any control returns HitResult = 1, it receives the mouse event.

  • Otherwise, the parent form or the container control receives the mouse event.

Since Visual Basic knows only about the Mask and Outside regions, the value of HitResult that it passes to the HitTest event can only be 0 or 3. If you want to notify Visual Basic that your control has a Close or Transparent region, you must do so by code. In practice, you test the x and y coordinates and assign a suitable value to HitResult, as shown in the following code:

' A control with a circular transparent hole in it.
Sub UserControl_HitTest(X As Single, Y As Single, HitResult As Integer)
    Const HOLE_RADIUS = 200, CLOSEREGION_WIDTH = 10
    Const HOLE_X = 500, HOLE_Y = 400
    Dim distance As Single
    distance = Sqr((X _ HOLE_X) ^ 2 + (Y _ HOLE_Y) ^ 2)
    If distance < HOLE_RADIUS Then
        ' The mouse is over the transparent hole.
        If distance > HOLE_RADIUS _ CLOSEREGION_WIDTH Then
            HitResult = vbHitResultClose
        Else
            HitResult = vbHitResultTransparent
        End If
    Else
        ' Otherwise use the value passed to the event (0 or 3).
    End If
End Sub

Not surprisingly, all these operations can add considerable overhead and slow down the application. Moreover, Visual Basic needs to clip the output accounting for the mask defined by MaskPicture for constituent controls and the output of graphic methods. To keep this overhead to a minimum, you can modify Visual Basic's default behavior by means of the ClipBehavior and HitBehavior properties.

The ClipBehavior property affects how Visual Basic clips the output of graphic methods. The default value is 1-UseRegion, which means that the output of a graphic method is clipped to fit the Mask region. The value 0-None doesn't perform clipping at all, and graphic output is visible also on the Mask and Transparent regions.

The HitBehavior property determines how the HitResult argument is evaluated before calling the HitTest event. When HitBehavior = 1-UseRegion (the default value), Visual Basic sets HitResult = 3 only for points inside the Mask region. If you set HitBehavior = 2-UsePaint, Visual Basic also considers the points produced by graphic methods in the Paint event. Finally, if HitBehavior = 0-None, Visual Basic doesn't even attempt to evaluate HitResult and always passes a 0 value to the HitTest event.

If your Mask region isn't complex and you can easily describe it in code, you can often improve the performance of your ActiveX control by setting HitBehavior = 0-UseNone. In this case, Visual Basic always passes 0 to the HitResult argument, and you change it to account for your Mask, Close, and Transparent regions. If the Mask region is complex and includes irregular figures, you should set ClipBehavior = 0-None, thus saving Visual Basic the overhead needed to distinguish between the Mask and Outside regions.

You can easily create controls with hot spots using ClipBehavior = 0-None and HitBehavior = 1-UseRegion. In practice, you draw your control over its entire client area and use the MaskPicture property to define the areas that react to the mouse.

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.

“An expert is a man who has made all the mistakes that can be made in a very narrow field” - Niels Bohr