Lightweight controls, sometimes referred to as windowless controls, differ from regular controls in one significant way: They don't have a window handle (hWnd property). Because of this they use fewer system resources, making them ideal for Internet applications, distributed applications, or any application where system resources may be a limiting factor. Examples of lightweight controls include the Label control and the Image control.
When designing a user control, you should consider creating a lightweight control unless your requirements meet one or more of the following criteria:
You create a lightweight user control by setting the Windowless property to True at design time. Lightweight user controls can only contain other lightweight controls: If you attempt to place a windowed control on a lightweight user control, you will get a design-time error. The same is true if you attempt to set the Windowless property to True on a user control that already contains windowed controls.
Not all containers support lightweight controls. When your lightweight user control runs in a host that doesn't support it, it will automatically run in windowed mode. The system will assign it an hWnd on the fly. Hosts that are known to support lightweight controls include Visual Basic 4.0 or later, Internet Explorer 4.0 or later, and Visual Basic for Applications.
Lightweight controls expose several new properties and a new event that are useful in creating controls with transparent backgrounds. With a windowed control, the MaskPicture and MaskColor properties are used to determine if a mouse click occurs within a valid area of the control's window; if not, the event is routed to the form. Because a lightweight control doesn't have a window, it's up to the control to determine what should receive the click event. The HitTest event provides notification when the cursor is over your control; you control the behavior of the HitTest event by setting a combination of several properties.
To understand how a lightweight control manages hit testing, you must first be familiar with the possible regions of a user control. A control has four possible regions:
Figure 9.8
The actual composition of the mask region is dependent upon the setting of the BackStyle property:
Visual Basic doesn't know about the Transparent and Close regions: You have to define these regions yourself and test for them in the HitTest event.
The HitTest event is fired whenever the cursor is over a lightweight user control when the BackStyle is set to Transparent. You can add code to the HitTest event to determine whether mouse messages will be received by your control or passed on to the next object beneath it in the ZOrder. The HitTest event fires before any other mouse messages.
Because it's possible to layer several controls at design time, portions of other controls may be visible beneath the transparent areas of your control. In some cases, you might want to test for the existence of another control under your control. If one exists, you might allow it to receive the mouse events; if not, you might want to handle the mouse events yourself. The HitTest event gives you a second (and third) chance to handle the events if there is nothing underneath or if the controls underneath decline the events.
The HitTest event takes three arguments: the x and y coordinates specifying the location of the cursor, and a HitResult argument that corresponds to a control region. There are four possible settings for HitResult:
Hit testing is performed in the following order for layered controls:
There's just one problem with this — the HitTest event will never return a HitResult of 1 or 2 because Visual Basic doesn't know about the close or transparent regions. You need to define these regions yourself in code; then if the X and Y coordinates fall within your defined region, you change the HitResult accordingly. The following code shows a HitTest event for a control with a square "hole" in its center:
Private Sub MyControl_HitTest(X As Single, _
Y As Single, HitResult As Integer)
' Determine if the X,Y coordinates fall
' within the Close region.
If (X > 200 And X < 210) Or (X > 390 And X < 400) Then
If (Y > 200 And Y < 210) Or (Y > 390 And Y < 400) Then
' Coordinates are within the Close region.
HitResult = vbHitResultClose
' We got a hit, so we can exit.
Exit Sub
End If
End If
' Now check for the Transparent region.
If (X > 210 And X < 390) Then
If (Y > 210 And Y < 390) Then
' Coordinates are within the Transparent region.
HitResult = vbHitResultTransparent
End If
End If
End Sub
In the above example, we first test to see if the coordinates fall within our Close region, in this case within 10 twips of the edge of our square hole. If so, we change the HitResult to 2 (vbHitResultClose) and exit. If not, we test to see if the coordinates are within the Transparent region, and if so we change the HitResult to 1 (vbHitResultTransparent). If neither test returns a hit, we go with the HitResult that was passed in.
Of course, in the case of a square region, it's fairly easy to determine the regions in code. As you might imagine, this procedure would be much more complex for a circular region, an irregularly shaped region, or for multiple regions within a control.
There's one more aspect to consider: With complex code being executed in the HitTest event, and with the HitTest event firing repeatedly as the cursor moves over your control, you might suspect that performance might be less than optimal. Your suspicion would be correct.
Hit testing can be a pretty expensive operation in terms of performance. This isn't surprising when you consider what's going on behind the scenes. First of all, Visual Basic must perform calculations to clip your control to the mask defined by MaskPicture and MaskColor. Next, if your control contains other controls or user-drawn graphics, it must also clip around them. Finally, if you've defined Close and Transparent regions in your control, it needs to execute the code to check for those regions. With the HitTest event being fired repeatedly, this process could cause your control to perform very slowly.
Fortunately user controls have two additional properties, ClipBehavior and HitBehavior, that can be set in combination to improve performance. The following table shows the results of different pairs of settings:
0 ClipBehavior None | 1 ClipBehavior Use Region | |
0 HitBehavior None | Hit test always returns 0 - no clipping. | Hit test always returns 0 - clipped to mask region. |
1 HitBehavior Use Region | Hit test returns 0 or 3 - no clipping. | (Default) Hit test returns 0 or 3 - clipped to mask region. |
2 HitBehavior Use Paint | Hit test returns 0 or 3 - clipped to paint. | Hit test returns 0 or 3 - clipped to paint inside mask region. |
When HitBehavior is set to 0 (vbHitBehaviorNone), the HitTest event will always return a HitResult of 0 (Outside). You can add code to determine where the hit occurred and change the HitResult accordingly. This may improve performance if there isn't a lot of code in the HitTest event procedure.
Setting ClipBehavior to 0 (vbClipBehaviorNone) can often improve performance because Visual Basic doesn't need to determine the boundaries between the Mask and Outside regions. This is especially true if the Mask region is complex — for instance, when it includes TrueType® fonts or irregular shapes.
If your control will use hot spots (in other words, only portions of the visible area will accept clicks), setting ClipBehavior to 0 and HitBehavior to 1 (vbUseRegion) can improve performance.
Setting HitBehavior to 2 (vbUsePaint) may be necessary for user-drawn controls where the visible portion of the control isn't the same as the Mask region. This is the most expensive operation because the control needs to repaint each time the HitTest event is executed.
Important When a lightweight user control is sited on a container that doesn't support the Windowless property, some HitBehavior and ClipBehavior settings may cause unpredictable results. If your control will be used in containers that don't support lightweight controls, you should use the default settings.