Chapter 4

Control Properties


CONTENTS

Properties are the most commonly used elements of an ActiveX control's interface. They're also fairly easy to implement in Visual Basic.

Much of the behavior of Visual Basic 5.0 properties is not new; the concept of properties existed in Visual Basic 4.0 as an element of classes. However, there are a number of new considerations, new language features, and additional tricks and traps to watch out for as you write properties for your ActiveX control.

Creating Property Procedures

You create a property in your control by following these steps:

  1. Declare a module-level variable in the Declarations section of your control designer.
  2. Write a Property Let procedure (or, for object variables, a Property Set procedure) to enable the user to change the property.
  3. Write a Property Get to enable the user to read the variable.
  4. Set the procedure attributes for your property (optional). These attributes determine whether the property is the control's default property and whether the property is database-aware, as well as provide the help text that appears in the Properties window when the property is selected.

After you've written Property Let and Property Get procedures, you'll take care of these additional tasks:

Using the Example Files on the CD-ROM

The example you'll use in this chapter is the LightBulb control, which you'll find on the CD-ROM accompanying this book. The group file this project is based on is called LightBulbGroup.vbg. The version of LightBulbGroup.vbg in the \Before folder contains a minimal version of the LightBulb control, with a visual design only and no code. The version of LightBulbGroup.vbg in the \After folder contains a version of the LightBulb control that includes all the code examples in this chapter. If you want to step through the examples in this chapter one at a time as a tutorial, start by opening the version of LightBulbGroup.vbg in the \Before folder.

Declaring Properties

Creating a property generally involves declaring a variable and creating Property Get and Property Let procedures to read from and write to the variable.

NOTE
Properties that are delegated to constituent controls don't require a variable declaration; the state of their properties is stored in the constituent control's property. This concept was introduced in Chapter 2and is covered in more detail later in this chapter.

This example shows you how to create an Illuminated property for the LightBulb control. The Illuminated property is stored in a Boolean variable, mIlluminated. When the user sets the Illuminated property to True, the LightBulb control displays the illuminated light bulb graphic. When the Illuminated property is set to False, the LightBulb displays the dimmed light bulb graphic.

To implement the Illuminated property, begin by declaring a variable to store the state of the property in the Declarations section of the LightBulb code designer:

Private mIlluminated As Boolean

Declare property variables as Private. This is because you don't want external procedures to get access to the value in the property. Rather than giving external procedures access to the variable directly, you give them access through Property Get and Property Let procedures. This lets you perform validation and other actions (such as changing in the graphical portion of the control) when the variable is accessed or changed.

TIP
If you're coming to Visual Basic from another programming language, you might be familiar with the terms "reader function" and "writer function." You can think of Property Get as a way to declare a reader function and Property Let and Property Set as ways to declare writer functions

Using Property Let and Property Get

After you've declared a variable to store your property, you next need to write Property Let and Property Get procedures. (Properties that can be set to object variables require a Property Set instead of a Property Let, but the syntax is essentially the same.)

Here is the skeletal syntax of a Property Let declaration:

Property Let propertyname ([argument_list,] value)
       .
       .
       .
       Exit Property
       .
       .
       .
End Property

Property Let and Property Get statements can be declared as Public, Private, Friend, or Static. For more information on declaring procedures as Friend, see Chapter 16, "Object-Oriented Programming."

Here is the syntax of the Property Get declaration:

Property Get propertyname [(argument_list)] [As data_type]
       .
       .
       .      [propertyname = expression]
[Exit Property]
       .
       .
       .      [propertyname = expression]
End Property

Note that properties can be declared as any Visual Basic data type, including object variable types and user-defined types. But remember that if your property type is an object variable, you need to use Property Set instead of Property Let to set the value of the property.

Additionally, if you declare a property to be of a particular data type in its Property Let declaration, make sure that its Property Get is of the same type. Don't mix types, as you see here:

Public Property Let Wattage(ByVal NewValue As Wattage) ' Wattage is an
    mWattage = NewValue                                ' enumerated 
    PropertyChanged "Wattage"                          ' data type.
End Property

Public Property Get Wattage() As Integer  ' Integers are not good for
    Wattage = mWattage                    ' children and other living
End Property                              ' things

This code will produce a compile error when you attempt to instantiate the control. To fix the problem, change the type declaration of the Property Get from Integer to Wattage.

The concept of enumerated properties, such as Wattage, is introduced later in this chapter. For now, you can write the code now and make the declaration it depends on later: .

' Declarations - We'll define the Wattage
' type later

Dim mWattage As Wattage

Public Property Let Wattage(ByVal NewValue As Wattage)
    mWattage = NewValue                               
    PropertyChanged "Wattage"                         
End Property

Public Property Get Wattage() As Wattage
    Wattage = mWattage                  
End Property                            

Public Property Get Illuminated() As Boolean
    Illuminated = mIlluminated
End Property

Public Property Let Illuminated(ByVal bNewValue As Boolean)

    If IsNumeric(bNewValue) Then
        If bNewValue = True Then
            picMain.Picture = picOn.Picture
            mIlluminated = bNewValue
        Else   ' false
            picMain.Picture = picOff.Picture
        End If
        PropertyChanged "Illuminated"
    End If

End Property

Don't test this code yet, because the Wattage property won't work until you declare the Wattage enumeration. You'll do that in the next section.

Enumerated Properties

You know from working with controls in Visual Basic in the past that some properties have the ability to limit the user to a specific range of settings. For example, consider the Alignment property of a TextBox control. This property can be set to one of three numbers: zero, 1, or 2. You can't choose another value in the Property window at runtime, because Visual Basic provides a combo box for the Alignment property that limits you to these three choices.

Fortunately, you don't have to memorize what the values zero, 1, or 2 mean in order to set this property, because the numeric values are associated with textual values in the design-time environment, as illustrated in Figure 4.1. A property that provides a list of choices for the user at design time is called an enumerated property.

Figure 4.1 : Example of an enumerated property.

You can provide a pre-defined list of legal property values for your user by using an enumeration. An enumeration is a new programming feature in Visual Basic 5.0 that enables you to define a related set of constant values.

You define an enumeration in a block of code inserted at the Declarations section of a module using the Enum statement. A typical enumeration looks like this:

Public Enum HappyHourStatus
    WorkTime = 0
    HappyHour = 1
    NightTime = 2
    Holiday = 3
    Weekend = 4
End Enum

NOTE
Remember, an enumeration is a type of constant, so the values you declare in an Enum statement can't be altered at runtime. Also, remember that you can use Enums for purposes other than supplying a list of valid properties; you can use an enumerated variable anywhere you'd use a normal constant.

A cool thing about enumerated constants is that you don't have to declare their values explicitly. If you don't set them to a value, then their values are set automatically, starting at zero.

For example, the following code is functionally identical to the preceding example:

Public Enum HappyHourStatus
    WorkTime
    HappyHour
    NightTime
    Holiday
    Weekend
End Enum

Handling Similarly Named Enumerated Constants

You can have two enumerated values with the same name defined in different enumerations. When you do this, you must refer to the variables by a fully qualified name of the form

EnumName.ConstantName

For example, say you have an application that contains enumerations for both HappyHourStatus and CalendarDay. The CalendarDay enumeration looks like this:

Public Enum CalendarDay
    WorkDay
    Weekend
    Holiday
    Vacation
    SickDay
End Enum

There's a big problem here-the Holiday and Weekend constants exist in both the HappyHour and CalendarDay enumerations. What's more, they are equal to different values. Holiday is equal to 3 in the HappyHour enumeration, while it's equal to 2 in the CalendarDay enumeration.

You can refer to the identically named constants in different enumerations by using code like this:

iToday = HappyHour.Holiday         ' iToday = 2
iTomorrow = CalendarDay.Holiday    ' iTomorrow = 3

You may be shrieking with horror at the prospect of collisions between different enumerated constants, and rightly so. Failing to plan ahead to avoid conflicting enumerations will make your code as confusing as heck. How confusing is heck? That's the point. Try to avoid such conflicts in the first place so you won't have to jump through hoops to deal with them.

One alternative is to provide a unique prefix for each enumerated constant, like this:

Public Enum CalendarDay
    cdWorkDay
    cdWeekend
    cdHoliday
    cdVacation
    cdSickDay
End Enum

Public Enum HappyHourStatus
    hhWorkTime
    hhHappyHour
    hhNightTime
    hhHoliday
    hhWeekend
End Enum

Declaring enumerations this way almost ensures you won't run into problems with two enumerated constants crashing into each other, bursting into flames, and making a mess on your carpet.

One small drawback is that your user will see the prefix when selecting the value in the combo box dropdown Property window, but that seems like a small price to pay. Function over form, and all that.

Here's the code that establishes the Wattage enumeration in the HappyHour example project:

Public Enum Wattage
    VeryDim
    FairlyDim
    Bright
    VeryBright
End Enum

Obviously, the values in the Wattage enumeration are oversimplified to make the example clearer. You can still test these enumerations by doing the following:

  1. Close LightBulb's form designer, if it is open.
  2. Open frmLBTestForm. There is an instance of the LightBulb control on the form called LightBulb1.
  3. Click on LightBulb1 to select it.
  4. In the Properties window, scroll down to the Wattage property. You should be able to change the property to any of the enumerated values, such as FairlyDim, Bright, or VeryBright (see Figure 4.2).

Figure 4.2 : Wattage property enumeration.

Boolean Properties

You can implement a Boolean property by declaring the control's Property Let procedure As Boolean. When you declare a Property Let in this way, the user is presented with two choices: True and False. You don't have to do anything special to make it happen this way; the Visual Basic IDE realizes that a particular property is Boolean and at design time provides a place in the Properties window to select the values True and False.

The Illuminated property of the LightBulb control is an example of this kind of property. To see how it works, try changing the Illuminated property of a LightBulb control on an EXE project form in the Properties window. You should be able to see that only two options are available, True and False.

Using Standard Property Pages

There are a few properties for which there are standard property sheets. When you specify that a property, such as a font or color property, is of a predefined enumeration, Visual Basic displays a predefined Property window for you.

This is similar to the way the Picture property, introduced in Chapter 3 displays a common Windows File Open dialog box when the user attempts to change the control. You don't need to do anything special to get Visual Basic to display a file dialog box for the Picture property; it is enough to declare your Property Set procedure As Picture.

You can provide this functionality in your control's properties by using predefined enumerations. Table 4.1 lists some of these system-supplied enumerations.

Using the Object Browser to Display Enumerations

Table 4.1 is only a partial list of enumerated properties available to you when you're developing properties in your controls. For information on all the enumerations that are available, open the Object Browser (using the menu command View, Object Browser or the function key F2) and browse the list of enumerated properties displayed there, as in Figure 4.3.

Figure 4.3 : Object browser displaying property enumeration.

Table 4.1 Selected standard control property types

Property TypeDeclaration Description
CheckedOLE_TRISTATE The state of a check box (can be either checked, unchecked, or gray)
Color (BackColor, ForeColor, etc.)OLE_COLOR A standard color (stored as a Long)
MousePointerMousePointerConstants The icon associated with the mouse pointer (arrow, cross, I-beam, custom icon, etc.).
ValueOLE_OPTEXCLUSIVE Used by controls that act as grouped option buttons. When the Value property is declared as OLE_OPTEXCLUSIVE, only one such control in a group can have the value of True.
AlignAlignConstants Used for controls (such as the PictureBox) that can align themselves to the top, bottom, left to right sides of their containers
AlignmentAlignmentConstants The alignment of text left, right, or center, such as that found in a TextBox control
BorderStyleBorderStyleConstants The graphical border around a control; it is either None or Fixed Single.
FillStyleFillStyleConstants The graphical fill of a control; it is either solid, transparent, or one of a number of shades, such as horizontal line.

You'll notice that the Object Browser also displays the name and values of the enumerations you've created yourself. To see this:

  1. In the Object Browser, scroll through the list of enumerations until you find the Wattage enumeration.
  2. Click on the Wattage enumeration. You should be able to see its members: Bright, FairlyDim, VeryBright, and VeryDim.
  3. Click on FairlyDim, then look at the bottom of the Object Browser. The status bar tells you that FairlyDim is a constant equal to 1. Your screen should look like Figure 4.4.

Figure 4.4 : Object Browser display of enumerated constant.

TIP
Using Object Browser to keep track of the enumerations you've written can be a great help, because it keeps you from having to hunt through your code when you're trying to remember whether you called something xyzFlag or pdqFlag. It also helps you quickly look up the values of enumerated constants
You can tell at a glance that a particular constant is an element of your project (as opposed to an element of a standard VB type library) by looking at the bottom of the Object Browser; this area is called the Details Pane.

Example of Standard Control Property Types

To give an example of a system-provided enumerated constant, you'll add a BorderColor property to the LightBulb control. For simplicity's sake, the BorderColor property of the LightBulb will delegate to the BackColor property of the UserControl. To do this:

  1. Open the LightBulb control designer.
  2. Open the control designer's code window and enter the following code:
Public Property Let BorderColor(ByVal NewColor As OLE_COLOR)
    UserControl.BackColor = NewColor
    PropertyChanged "BorderColor"
End Property

Public Property Get BorderColor() As OLE_COLOR
    BorderColor = UserControl.BackColor
End Property
  1. Close the code window, close the form designer, and switch back to frmLBTestForm.
  2. Click on LightBulb1. In the Properties windows, you should be able to see that the control now has a BorderColor property.
  3. In the Properties window, click on the BorderColor property. A standard color selection drop-down box appears, as you can see in Figure 4.5.

Figure 4.5 : The BorderColor dropdown list box.

Validating Property Procedures

Property Let and Property Set procedures are the place where you'll write validation procedures. Such procedures should reject all invalid property values, either by raising some kind of error or by ignoring the attempt to change the property value.

From an error-trapping perspective, Boolean properties are nice, because you don't have to do numeric boundary checking. For example, consider the Illuminated property of a LightBulb control.

LightBulbs are either Illuminated or not; that is, their Illuminated properties are either True or False. But because Visual Basic defines False as zero and True as any nonzero value, the code

Lightbulb1.Illuminated = 3.14159

will work fine, triggering no error (LightBulb1's Illuminated property will be set to True). However, the code

Lightbulb1.Illuminated = "ringbo"

generates an error (13-Type Mismatch). You can avoid error in this case by testing the value supplied by the user with an IsNumeric function, like this:

If IsNumeric (NewValue) Then
    ' perform the property change
    .
    .
    .
Else
    ' raise an error
    .
    .
    .
End If

This technique is known as validation, and it's important to do. If a run-time error occurs in your control, there's no way for your user to trap it.

You have the option of exiting a Property Let in situations where the input is just too weird to deal with. For example, say you provide a BorderColor property for your control. Windows colors are expressed as long integers. But if your control encounters the code:

LightBulb1.BorderColor = "václav"

the user gets a Type Mismatch error, because the BorderColor property expects a long integer.

Let's say that instead of raising an error, you want the BorderColor property to ignore, or "eat" the error. To make this happen, you can change your code to look like the following:

Public Property Let BorderColor(ByVal NewColor As OLE_COLOR)
    If IsNumeric(NewColor) Then
        UserControl.BackColor = NewColor
        PropertyChanged "BorderColor"
    Else
        Exit Property
    End If
End Property

The preceding code was just to introduce the Exit property statement to you; in general, your procedures should eat errors as seldom as possible. The preferred option for validation is to raise an error in your Property Let procedure. For more information on raising errors in your control, see Chapter 15, "Debugging and Error Trapping."

Using the PropertyBag Object

You use the PropertyBag object to store the properties set for your control by a programmer at design time. The PropertyBag object has only two methods: ReadProperty and WriteProperty.

You read from the PropertyBag in the ReadProperties event and write to the PropertyBag in the WriteProperties event. The syntax of the WriteProperty method is:

PropertyBag.WriteProperty "property_name", value [, default_value]

The parameter property_name is a string that denotes which property you're saving to the property bag. Value is the value of the property you're saving. The parameter default_value is optional; it exists only to tell Visual Basic not to save the property unless the user has changed the property from its default. This makes for clearer and faster loading .CTL files.

Here is an example of the WriteProperty method used in the WriteProperties event of a UserControl:

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "Wattage", mWattage, 0
    PropBag.WriteProperty "Illuminated", mIlluminated, False
End Sub

The syntax of the ReadProperty method is:

PropertyBag.ReadProperty "property_name" [, default_value]

Just as with WriteProperty, the ReadProperty method has an optional default_value parameter that tells Visual Basic whether the property needs to be read from disk or not. This accelerates loading the control.

Here is an example of the ReadProperty method in the ReadProperties event:

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    Wattage = PropBag.ReadProperty("Wattage", 0)
    Illuminated = PropBag.ReadProperty("Illuminated", False)
End Sub

Where Design-Time Property Data Is Stored

Officially, you aren't supposed to be concerned with what happens to data once it's put in the PropertyBag. This is because the PropertyBag object is an abstraction, standing between the programmer and the property's storage and shielding you from its complexity. But you might need to know what these files are and what they do (especially when you're backing up your project, using version control, or moving your project from one disk to another).

For forms, most design-time property settings are saved to a .FRM file. This is also true for controls, except the settings are saved in the .CTL file. You can inspect the format of these files by opening a .FRM or .CTL in a text editor; just make sure not to make any changes to these files, or Visual Basic won't be able to read them.

Some properties, such as the Picture property of a PictureBox control, can't be stored in standard form or control files, because they are too large and consequently need to be stored in a binary format. When you set a property such as Picture in a form or control designer, Visual Basic creates a binary file that has the same name as your file, but with a different extension. This file is created and updated when the user saves the corresponding form or user control file; under normal circumstances, Visual Basic programmers never work with these files directly. Table 4.2 summarizes the files' extensions.

Properties of Constituent Controls

You learned in Chapter 3how to pass through, or delegate, the property of your UserControl to a constituent control. In general, the code you use to read and write the properties of constituent controls to your UserControl is very simple. Given a UserControl called LightBulb with a constituent PictureBox control called picMain, the code to read and write the control's Picture property looks like this:

Public Property Get Picture() As Picture
    Picture = picMain.Picture
End Property

Public Property Set Picture(ByVal NewPic As Picture)
    picMain.Picture = NewPic
    PropertyChanged "Picture"
End Property

This is a code cliché-a piece of code you'll write dozens if not hundreds of times in your career as a Visual Basic control developer. (The code is so straightforward, in fact, it makes more sense for you to let the ActiveX Control Interface Wizard write it for you. The ActiveX Control Interface Wizard is introduced in Chapter 2)

Limitations of Constituent Controls

When using constituent controls, there is a caveat you must bear in mind. When you place a constituent control on your UserControl designer, the constituent control is in run-time mode, even though no code is being executed. That means you will not be able to gain access to runtime-only properties of the constituent control (such as the Sorted property of the ComboBox control).

Table 4.2 Filename extensions of Visual Basic binary files

File Type
Filename Extension
Binary Filename Extension
Forms
.FRM
.FRX
Controls
.CTL
.CTX
Property pages
.PAG
.PGX

Fortunately, the number of runtime-only properties of standard Windows control are few, so hopefully this shortcoming won't hinder you too often. But it is something to bear in mind as you build controls comprised of constituent controls.

Properties Your Control Should Provide

There are a number of properties that your control is supposed to always provide. This being a free country and all, you don't have to provide any properties you don't want to. If your control is unusual (for example, it's invisible at runtime), then you obviously would.

The properties your control should provide are:

You should also provide properties for controls that are similar to your control. Make sure your control's property interface make sense, and you will avoid legions of users throwing rocks through the windows of your home in the middle of the night.

Creating a Procedure Description

One helpful new feature of the VB5 IDE is the procedure description of properties. When you click on a property in the Properties window, a (hopefully) helpful piece of text appears at the bottom of the Properties window (see Figure 4.6) telling you what the property is used for.

Figure 4.6 : Example of a procedure description.

You can implement procedure descriptions in properties you create. To do this:

  1. Open the form designer for your control.
  2. Double-click on the designer to open a code window.
  3. Choose the menu command Tools, Procedure Attributes. The Procedure Attributes dialog appears.
  4. Using the Name combo box, choose the property of your control you wish to annotate.
  5. In the Description text box, type the description of your property. The Procedure Attributes dialog box will look like Figure 4.7.
  6. Click on OK.

Figure 4.7 : Procedure Attributes dialog box.

To test your property description:

  1. Click on the control LightBulb1 on frmLBTestForm.
  2. Scroll through the list of properties in the Properties window.
  3. Click on the Illuminated property. As shown in Figure 4.8, you should be able to see the description text you entered at the bottom of the Properties window.

Figure 4.8 : Procedure description in the Properties window.

Designating a Property as the Default

Most controls have a default property. If you choose to designate a default property in your control, the user will not have to explicitly type the name of the property when referring to it in code. Denoting a default property in your control saves the user time at the expense of clarity. For example, the default property of a TextBox is its text property. If you have a TextBox called Text1, you can either type

MyString = Text1.Text

or

MyString = Text1

and the two lines of code will mean the exact same thing.

NOTE
The default property is not to be confused with the Default property (also referred to in VB5 as the user interface default). The Default property is a property of command buttons and similar controls. When the Default property is set to true, striking the Enter key triggers the command button's Click event

To designate a property of your control as its default property, do the following:

  1. With the control designer open, choose the menu command Tools, Procedure Attributes.
  2. The Procedure Attributes dialog box appears. In the Name combo box, select the property you want to designate as the default, then click on Advanced.
  3. In the Procedure ID combo box, select (Default). The screen will look like Figure 4.9.

You can test your new default property by entering code in the Immediate window while the form is running. To do this:

  1. Close the control designer, if it is still open.
  2. Return to frmLBTestForm. Create a LightBulb control there, if one does not already exist.
  3. Run the EXE project by choosing the menu command Run, Start or using the function key F5.
  4. Pause execution by typing Ctrl+Break or clicking on the Break button on the toolbar (see Figure 4.10).
  5. Open the Immediate window, if it's not already open. (To open the this window, choose the menu command View, Immediate Window or use the keystroke shortcut Ctrl+G.)
  6. In the Immediate window, type the following code:
Print LightBulb1

    and press Enter. The value of LightBulb1's Illuminated property appears in the Immediate window. Now when you type
LightBulb1 = True
LightBulb1 illuminates.

Figure 4.9 : The completed Procedure Attributes dialog box.

Figure 4.10: The toolbar Break button.

Grouping Properties

You can organize properties into groups in order to make it easier for users to find them in the Properties windows. It's particularly useful to do this if your control exposes a large number of properties.

For example, you might want to create a bunch of electricity-related properties together in one group. The Wattage and Illuminated properties would be more easily accessible if they belonged to this group. To place the LightBulb control's Illuminated property in a property group, do the following:

  1. Open the HappyHour control's designer, if it's not open already.
  2. From the menu, choose the command Tools, Procedure Attributes.
  3. The Procedure Attributes dialog box appears. Select the Illuminated property from the Name combo.
  4. Click on Advanced. The dialog box expands.
  5. The Property Category combo determines which category the property is placed in. You can select from one of the choices in the list (Appearance, Data, Font, and so forth) or you can create your own. To create your own property category, type the word Electrical in the Property Category box. The dialog box will look like Figure 4.11.
  6. Click on OK to apply the changes.

To see that the Illuminated property has been assigned to a category, do the following:

  1. Open the test form frmLBTestForm.
  2. An instance of the LightBulb control should already be on the form. Click on it to select it.
  3. In the Properties window, click on the Categorized tab. Scroll through the list of properties; you should be able to see that Illuminated now falls under the Electrical category, as illustrated in Figure 4.12.

Figure 4.11: Custom property category.

Figure 4.12: Electrical category.

Synchronizing a Property with Properties of Its Container

You can use the AmbientProperties object to gain access to information about the properties of your control's container. You do this in order to synchronize your control's properties with those of its container.

For example, when you set a form's Font property to Times New Roman 12 Bold and then place a Label control on the form, the Font property of the label is automatically set to Times New Roman 12 Bold as well. The default properties of the Label are synchronized with the properties of its container.

When you write properties, consider whether it is appropriate to synchronize properties of your control with properties of its container. For more information on the container, including an example of how to use the AmbientProperties object, see Chapter 7, "Interacting with the Container."

Creating Custom Property Pages

A custom property page can go a long way toward making the properties of your control easier to manipulate at design time. This is particularly true if your control has numerous properties or an otherwise complicated interface. You can assign a property page to your entire control or to a particular property in your control.

In this section you'll step through the construction of a simple property page using a wizard, then you'll write code to create a property page manually.

Using the Property Page Wizard

You can use the Property Page Wizard as a quick way to set up a basic property page for your control. After you've created a property page using the wizard, you can further customize the property page using the same visual design techniques you'd use to create Visual Basic applications and ActiveX controls.

Begin by setting up a minimal property page for the LightBulb control.

To do this:

  1. Open the LightBulb control project in LightBulbGroup.vbg, if it is not open already.
  2. Choose the menu command Add-Ins, Property Page Wizard.
  3. The first Property Page Wizard screen appears. Click on Next.
  4. As in Figure 4.12, the screen labeled Select the Property Pages appears. Here you have the option to designate an existing property page for your control or to create a new page.
  5. The Property Page Wizard starts by allowing you to select the property pages to use for your control.
  6. Click on Add to create a new page.
  7. The Property Page Name dialog box appears, prompting you to name your new property page (see Figure 4.13). In place of PropertyPage1, type LightBulbPage, then click on OK.
  8. The new page is added to your project and you are returned to the Select the Property Pages dialog box. Click on Next.
  9. The Add Properties window appears. This window lets you add properties from your UserControl to the property page.
  10. Double-click on Illuminated in the Available Properties list. The Illuminated property moves to the LightBulbPage page. Click on Next.
  11. The Finished! window appears. Click on Finish.
  12. The Property Page Created dialog box appears. Click on OK.
  13. A report appears giving you information on what else needs to be done to make your property page functional (see Figure 4.14). Click on Close when you're done reading it.

Figure 4.13: The Property Page Name window is where you name a new property page.

Figure 4.14: Property Page Wizard summary report.

Your property page is now functional. To test it:

  1. Close the LightBulb control's designer, if it is open.
  2. Open the EXE project form. There should be an instance of the LightBulb control there already.
  3. Right-click on LightBulb1. A pop-up menu appears. Click on Properties. As in Figure 4.15, your custom property page appears.

Figure 4.15: The Properties Pages showing the LightBulb tab.

You should also be able to see that a new file has been added to your project, a custom property page called LightBulbPage. Locate it in the Project Explorer and double-click it to open it.

You can see that at design time, a property page looks not unlike a control designer; in fact, they are called property page designers. You can manipulate this designer and add controls and code to it just like a control designer or a form. You'll do that in the next section.

Programming the PropertyPage Object

The PropertyPage object is a fully programmable Visual Basic object similar to a Form or a UserControl object. Tables 4.3 and 4.4 describe the important properties and events of the PropertyPage.

Table 4.3 Important properties of the PropertyPage object

PropertyDescription
ActiveControlThis is the control that has the focus. You can use this property to determine which type of control the user has selected when the PropertyPage is displayed.
ChangedThis is a flag that indicates whether the user changed a property through the PropertyPage.
SelectedControlsThis is a collection containing all the controls selected when the PropertyPage was activated.

Table 4.4 Important events of the PropertyPage object

EventWhen Triggered
ApplyChangesThis is triggered when the user clicks on the Apply or OK button or switches tabs in a PropertyPage comprised of multiple pages.
EditPropertyWhen the user clicks on the ellipsis button in the Properties window, the property page is opened. It exists so you can set the focus to the appropriate control.
SelectionChangedThis is triggered when the PropertyPage is first opened and when you select or deselect one or more controls while the page is still open.
UnloadThis is triggered when the PropertyPage is about to be unloaded (usually as a result of the user closing the page). This is similar to the Unload event of a form.
TerminateThis event occurs after the Unload event. All references to the PropertyPage control go out of scope (or are set to Nothing).

Modifying the Property Page

Property pages created by the Property Page Wizard are generally adequate for most types of properties. But you might want to take your control's properties sheet further by applying additional code and custom controls to the property page's interface.

To demonstrate this, you'll convert the check box that controls the Illuminated property to a group of option buttons with graphical representations of the "on" and "off" states of the light bulb. To do this:

  1. Open the property page designer LightBulbPage. There should be a single checkbox there for the Illuminated property; this checkbox was created by the Property Page Wizard in the preceding demonstration of property pages.
  2. Beneath the Illuminated checkbox, create a Frame control.
  3. Inside the Frame control, create two PictureBoxes and two OptionButton controls. Make sure that all the controls are contained inside the Frame control or the option buttons won't work properly.
  4. Give one OptionButton the name optBulbOff. Name the other one optBulbOn.
  5. Assign the properties shown in Tables 4.5-4.9 to the controls Picture1, Picture2, optBulbOff, optBulbOn, and Frame1, respectively.

Table 4.5 Picture1 control properties

PropertyValue
Pictureslite-off.bmp (on the CD-ROM)
AutoSizeTrue

Table 4.6 Picture2 control properties

PropertyValue
Pictureslite-on.bmp (also on your CD-ROM)
AutoSizeTrue

Table 4.7 optBulbOff properties

PropertyValue
CaptionOff
ValueTrue

Table 4.8 optBulbOn properties

PropertyValue
CaptionOn
ValueFalse

Table 4.9 Frame1 control properties

PropertyValue
CaptionIlluminated

  1. Delete chkIlluminated. The graphical interface of your property page should look like Figure 4.16.
  2. Double-click on the property page designer to open its code window. You should be able to see existing code for changing the Illuminated property using the checkbox, chkIlluminated.
  3. Delete the procedure chkIlluminated_Click and replace it with the following code. This code provides Click events for the two option buttons:

Figure 4.16: New Property page.

Private Sub optBulbOff_Click()
    Changed = True
End Sub

Private Sub optBulbOn_Click()
    Changed = True
End Sub

This code flags the property page as "dirty," meaning that the user has altered a property in the page. When the property page is dirty, the Apply button is enabled; the property page then knows to apply the property changes to the control when the user clicks on Apply or OK.

Next you'll need to alter the ApplyChanges event of the PropertyPage so the page applies the property change appropriately. To do this, make the following changes to the code in the property page's ApplyChanges event:

Private Sub PropertyPage_ApplyChanges()
    SelectedControls(0).Illuminated = optBulbOn.Value
End Sub

Finally, you'll have to alter the property page's SelectionChanged event so it accurately reflects the state of the selected control when the property page was opened. This code is in the SelectionChanged event because this event is triggered when the property page is first opened. It is also triggered when the user changes the control that is selected. However, the event is also triggered when you click on the property sheet's Apply button, which is bad, because when you change the property to Off and press Apply, the code will change the property sheet to indicate that the property has been set to On. We'll include a workaround for this puzzling anomaly by placing a flag in the ApplyChanges event.

TIP
It's important to remember that the set of selected controls can change after the user has brought up the Property sheet. This is the case because property pages are modeless (meaning that users can access windows in the background while the Property page is still being displayed).

To make this change, start by creating the flag in the Declarations section of the code:

Private mApplyingFlag As Boolean

Next, alter the ApplyChanges event so it sets the flag. Its code should look like this:

Private Sub PropertyPage_ApplyChanges()
    mApplyingFlag = True
    SelectedControls(0).Illuminated = optBulbOn.Value
End Sub

Finally, alter the code in the SelectionChanged event as follows:

Private Sub PropertyPage_SelectionChanged()
    If mApplyingFlag = False Then
        optBulbOn.Value = SelectedControls(0).Illuminated
    Else
        ' do nothing
        mApplyingFlag = False
    End If
End Sub

To test your new Property page, do the following:

  1. Close the Property page designer.
  2. Open frmLBTextForm, the EXE project form that contains an instance of the LightBulb control.
  3. Right-click on LightBulb1. From the pop-up menu, select Properties.
  4. Your Property page appears. Using the Property page, change the Illuminated property of the selected control, then click on Apply. You should be able to see the control change.

Summary

This chapter discussed how to create properties in the ActiveX controls you create. Using an example project, you created properties, wrote validation procedures, and developed custom property pages.

In the next chapter, you'll learn about another new element of control interfaces: custom events.