Visual Basic Concepts

Exposing Properties of Constituent Controls

By default, the properties of the UserControl object — and the constituent controls you add to it — are not visible to the end user of your control. This gives you total freedom to determine your control's interface.

Frequently, however, you will want to implement properties of your control by simply delegating to existing properties of the UserControl object, or of the constituent controls you've placed on it. This topic explains the manual technique of exposing properties of the UserControl object or its constituent controls.

Understanding delegation and property mapping will help you get the most out of the ActiveX Control Interface Wizard, which is designed to automate as much of the process as possible. It will also enable you to deal with cases that are too complicated for the wizard to handle.

Exposing Properties by Delegating

Suppose you want to create a control that allows the end user to edit a field with a special format, such as a Driver's License Number. You start by placing a single text box on a UserControl, and naming it something catchy like txtBase.

Because your new control is an enhancement of a single Visual Basic control, you also resize txtBase to fit the UserControl. You do this in the UserControl's Resize event, as discussed in "Providing Appearance Using Constituent Controls," earlier in this chapter.

To create the BackColor property of your control, you can simply expose the BackColor property of the text box, as shown in this code fragment from the UserControl's code module.

Public Property Get BackColor() As OLE_COLOR
   BackColor = txtBase.BackColor
End Property

Public Property Let BackColor(ByVal NewColor _
      As OLE_COLOR)
   ' . . . property validation code . . .
   txtBase.BackColor = NewColor
   PropertyChanged "BackColor"
End Property

The purpose and importance of PropertyChanged are discussed in "Adding Properties to Controls," earlier in this chapter.

The BackColor property you create for your control simply saves its value in the BackColor property of the text box control. Methods are exposed in similar fashion, by delegating the work to the corresponding method of the control you're enhancing. Delegation is discussed in "Composing Objects," in "Programming with Objects," in the Visual Basic Programmer's Guide.

Tip   When you use the OLE_COLOR data type for color properties, the Properties window will automatically show the ellipsis button that brings up the standard color selection dialog box. Standard property types are discussed in the related topic "Using Standard Control Property Types."

Note   User controls don't support Dynamic Data Exchange (DDE). If you delegate DDE-related properties such as LinkMode or events such as LinkOpen, they will be displayed at design time but will cause an error at run time.

Important   Because properties of the UserControl object and constituent controls are exposed by delegation, you cannot expose design-time-only properties such as Appearance and ClipControls. The settings you choose for such properties will be fixed for your ActiveX control.

Mapping Properties to Multiple Controls

Frequently you will want to map more than one constituent control property to a property of your control. Delegation gives you the flexibility to handle this situation.

Suppose, for example, that you've created your control's appearance by placing several check boxes, option buttons, and labels on a UserControl. It makes sense for the BackColor of your UserControl to be the background color of your control. However, it also makes sense for the BackColor properties of the constituent controls to match this color.

The following code fragment illustrates such an implementation:

Public Property Get BackColor() As OLE_COLOR
   BackColor = UserControl.BackColor
End Property

Public Property Let BackColor(ByVal NewColor _
      As OLE_COLOR)
   Dim objCtl As Object
   ' . . . property validation code . . .
   UserControl.BackColor = NewColor
   For Each objCtl In Controls
      If (TypeOf objCtl Is OptionButton) _
            Or (TypeOf objCtl Is CheckBox) _
            Or (TypeOf objCtl Is Label) _
         Then objCtl.BackColor = NewColor
   Next
   PropertyChanged "BackColor"
End Property

When the property value is read, the value is always supplied from the BackColor property of the UserControl. Always choose a single source to be used for the Property Get.

Note   When you give your control a property which the underlying UserControl object already possesses, using that property name in your code will refer to the new property you have defined, unless you qualify the property name with UserControl, as shown above.

For More Information   Using the Controls collection to iterate over all constituent controls is discussed in "Control Creation Terminology," earlier in this chapter. The purpose and importance of PropertyChanged are discussed in "Adding Properties to Controls," earlier in this chapter.

Multiple BackColor Properties

The implementation above raises an interesting question. What if you want to provide the user of your control with a way to set the background color of all the text boxes on your control? You've already mapped BackColor to its most natural use, but you can always get creative with property names.

For example, you might add a TextBackColor property, modeled on the example above, that would set the BackColor properties of all the text boxes on your control. Choose one text box as the source of the TextBackColor, for the Property Get, and you're in business. (It doesn't make much sense to use the UserControl's BackColor for this purpose.)

Mapping to Multiple Object Properties

As another example of multiple property mapping, you might implement TextFont and LabelFont properties for the control described above. One property would control the font for all the labels, and the other for all the text boxes.

When implementing multiple mapped object properties, you can take advantage of multiple object references. Thus you might implement the LabelFont property as shown in the following code fragment:

Public Property Get LabelFont() As Font
   Set LabelFont = UserControl.Font
End Property

' Use Property Set for object properties.
Public Property Set LabelFont(ByVal NewFont As Font)
   Set UserControl.Font = NewFont
   SyncLabelFonts
   PropertyChanged "LabelFont"
End Property

Private Sub SyncLabelFonts()
   Dim objCtl As Object
   For Each objCtl In Controls
      If TypeOf objCtl Is Label Then
         Set objCtl.Font = UserControl.Font
      End If
   Next
End Sub

The code in the SyncLabelFonts helper function assigns to each Label control's Font property a reference to the UserControl object's Font object. Because all of the controls have references to the same Font object, changes to that font will be reflected in all the labels.

A helper function is used because the same code must be executed when your control is initialized, and when saved properties are read.

Note   The purpose and importance of PropertyChanged are discussed in "Adding Properties to Controls," earlier in this chapter.

The code to initialize, save, and retrieve the LabelFont property is shown below. Optionally, you can set the characteristics of the UserControl's font to match those of the container's font, as discussed in "Using the AmbientProperties Object to Stay Consistent with the Container."

Private Sub UserControl_InitProperties()
   SyncLabelFonts
End Sub

Private Sub UserControl_ReadProperties(PropBag As _
      VB.PropertyBag)
   On Error Resume Next
   Set LabelFont = PropBag.ReadProperty("LabelFont")
End Sub

Private Sub UserControl_WriteProperties(PropBag As _
      VB.PropertyBag)
   PropBag.WriteProperty "LabelFont", LabelFont
End Sub

Because the Font object is a standard object, it can be saved and retrieved using PropertyBags.

The developer using your control can now use the Property window to set a font for the LabelFont property. Supposing that the name you give your control is MultiEdit, she can also use code like the following at run time:

Private Sub Command1_Click()
   YourControl1.LabelFont.Bold = True
   YourControl1.LabelFont.Name = "Monotype Sorts"
End Sub

The beauty of this code is that it never calls the Property Let for the LabelFont property, as you can verify by adding the code above to a UserControl that has several constituent Label controls, and putting breakpoints in the Property Get and Property Let.

When Visual Basic executes the first line above, the Property Get is called, returning a reference to the UserControl's Font object. The Bold property of the Font object is set, using this reference. Because the constituent Label controls all have references to the UserControl's Font object, the change is reflected immediately.

Don't Expose Constituent Controls as Properties

You might wonder why you shouldn't simply expose constituent controls whole. For example, if your UserControl has a text box control named Text1 on it, you might try to write the following:

' Kids, don't try this at home.
Property Get Text1() As TextBox
   Set Text1 = Text1
End Property

The user of your control could then access all the properties and methods of Text1, and you've only written one line of code.

The code above will not compile, because TextBox is not a public data type. But that's not the real reason this is a bad idea.

It might simplify your life to expose all the properties and methods of a constituent control, rather than selectively exposing them, but consider the experience that awaits the user of your control. He now has direct access to the Text property of Text1, bypassing any validation code you might have written in a Property Let. He can also change the height and width of the text box, which may completely wreck the code you've written in your UserControl_Resize event procedure.

All in all, the developer is likely to conclude that your control is too buggy to be worth using. But wait, there's more. If the developer uses your control with other development tools, such as Microsoft Excel or Microsoft Access, type library information for the constituent controls will not be available. All references to Text1 will be late bound, so your control will be not only buggy, but slow.

Exposing constituent controls also limits your ability to change your control's implementation in the future. For example, you might want to base a future version of your control on a different constituent control than the intrinsic TextBox control. Unless the properties and methods of this SuperTextBox exactly matched those of the intrinsic TextBox, your users would be unable to upgrade without rewriting their code.

It's good programming practice to expose only those constituent control properties required for the operation of your control. For example, if the text box mentioned above holds a user name, you might expose the value of Text1.Text through a UserName property.

Using the ActiveX Control Interface Wizard

When you have a large number of constituent controls, or even one constituent control with many properties you wish to expose, the ActiveX Control Interface Wizard can significantly reduce the amount of work required to expose constituent control properties.

Using the ActiveX Control Interface Wizard is discussed in the related topic "Properties You Should Provide."