Platinum Edition Using Visual Basic 5

Previous chapterNext chapterContents


- 25 -
Extending ActiveX Controls

There's a special relationship between a control and its container. Specifically, a Visual Basic ActiveX control provides Extender and Ambient objects through which the control can communicate with its container.
You can use standard coding techniques to create property sheets that provide you the greatest flexibility at design time.
Properly debugging and handling errors and exceptions can mean the difference between a usable control and one that is a major security risk to the users of the control.
Using the Application Setup Wizard, you can package your ActiveX control for use in Web pages. Learn how to prepare your control so that it can be used in Web pages and automatically downloaded by users of Microsoft's Internet Explorer Web browser.

In the previous chapter, you received an introduction to the creation of Visual Basic ActiveX controls. In this chapter, you learn how to enhance already existing controls in order to create a new version of the control. In addition, you will learn how to create a control that is able to function without risk of failure.

Introducing the Extender Object

A Visual Basic control's Extender object provides a number of properties, called extender properties, that at first glance may appear to be a part of a control instance. Extender properties, though, are in reality part of the Extender object. You can see extender properties when you place an instance of a control on a Visual Basic form.

For example, start a new ActiveX Control project. Then, in the Project Group window, double-click UserControl1 to open the control's designer window, and display the control's properties in the Property Window. Reduce the size of the control by setting its Height property to 1275 and setting its Width property to 1800.

Take a look at the properties listed in the Properties window. These are your control's properties. What's special about these properties is that only you, the control programmer, can change them. The only way a developer can manipulate the properties currently displayed in the Properties window is if you expose the properties in your control's code.

When you used the ActiveX Control Interface Wizard in the previous chapter, you saw one way to expose control properties to the developer. Figure 25.1 shows the Wizard's Select Interface Members dialog box. The control members in the right box are the properties, events, and methods that the Wizard is exposing to developers. That is, only the members in the Selected Names box can be accessed by a developer after you've completed the control.

FIG. 25.1
The ActiveX Control Interface Wizard's Select Interface Members dialog box is one place you can expose properties, events, and methods to a developer.

Close the designer window so that the control is available on VB5's Toolbox. Now, double-click Form1 in the Project Group window. The test application's designer window appears. Double-click the control's icon in the toolbox to add an instance of the control to the test application's form. When the control instance appears, its properties appear in the Properties window. Because you haven't exposed any of the control's own properties yet, the properties in the Properties window are all extender properties, supplied by the Extender object.

Although you can manipulate extender properties from your control, you usually won't want to, because extender properties are meant to be used by a developer. The developer is the person who will decide things such as where the control is located in its container (determined by the Left and Top extender properties), and how big the control will be (determined by the Height and Width extender properties).

If you do want to manipulate extender properties, you can do so through the control's Extender object. For example, suppose, for some reason, that you want your control to always start off set to a specific size. You could write the control's InitProperties() event procedure, as shown in Listing 25.1. Now, whenever an instance of the control is first created (not when it's recreated), it'll be set to a height of 300 twips and a width of 4000 twips.

Listing 25.1 LSTAA_01.TXT--Setting the Starting Size of a Control

Private Sub UserControl_InitProperties()
    Extender.Height = 300
    Extender.Width = 4000
End Sub


NOTE: Because the InitProperties() event procedure is called only when a control instance is first created, developers can easily resize the control however they like. When the developer reloads the control instance, InitProperties() is not called, so the Height and Width properties don't get reset to their default values. Of course, you can set the starting height and width of a control simply by resizing the control in its designer window. There's no need to write code for setting the Height and Width extender properties.


NOTE: A control's Extender object is not available until the control has been sited on its container. This makes sense because the extender properties are based on the con- tainer's properties. Until the control is placed on the container, there is no way to determine what the container is or what properties it'll support. For this reason, you can't access the Extender object in a control's Initialize() event procedure, which is called before the control is com- pletely sited on its container. You can, however, access the Extender object in InitProperties() and ReadProperties().

The Ambient Object

Of greater interest to a control programmer is the control's Ambient object, which enables a control to respond to changes in the container. The capability of the control to respond to its container provides for the seamless integration of a control. This also increases the control's inherent value through its capability to dynamically adjust itself, in some cases, to the environment of the container.

The Ambient object features many properties, but only a few are especially important to a control programmer. These important ambient properties are listed in Table 25.1 along with their descriptions.

Table 25.1 Important Ambient Properties

Property Description
BackColor Holds the container's background color.
DisplayAsDefault Indicates whether a user-drawn control is the default control in the container.
DisplayName Holds the control instance's name. You can use DisplayName to identify the control in messages presented to the developer at design time.
Font Represents the container's currently selected font.
ForeColor Represents the container's foreground color.
LocaleID Indicates the locale in which the control is being used. You use this property to set things such as the language of the text and the formats of dates and times for different parts of the world.
TextAlign Represents the container's text-alignment setting.
UserMode Indicates whether the control is running at design time or run time. A UserMode value of False indicates design time, and a value of True indicates runtime.

One important reason to use your control's Ambient object is to ensure that the control's colors match those of its container. Imagine what it would look like if your control's background was gray and it was placed into a white spreadsheet. Most likely, the control would work properly, but the color difference would be a distraction to the users and would present an unprofessional appearance. To adjust the colors of your control to match that of its container, you use the BackColor and ForeColor properties. To see how this works, you'll create the AmbientDemo control in the next few sections.

AmbientDemo Control, Example 1

Now that you have the basic control project created, you can experiment to see why ambient properties are so important. To do this, first set the control's Height property to 1125, and set its Width property to 1950. Close the control's designer window so that the control's icon becomes available on VB5's toolbox. Now, in the Project Group window, double-click frmAmbientDemoTestApp. When you do so, the test application's form designer window appears. In the form's Properties window, change the background color to purple.

When you select the new background color, the form's background immediately changes to the selected color. Now, double-click the AmbientDemo control's icon on the toolbox. An instance of the control appears on the form. However, because the control doesn't pay attention to ambient properties, the control's background color is different from the form's background color, as shown in Figure 25.2.

FIG. 25.2
The control's and form's background colors don't match.

AmbientDemo Control, Example 2

To fix this problem with the AmbientDemo control, you have to set the control's colors to match those of its container. This is where the Ambient object enters. First, press your keyboard's Delete key to remove the control instance from the test application's designer window. Then, close the designer window. Double-click AmbientDemo in the Project Group window to open the control's designer window. Double-click the control to display its code window. Next, use the Properties box to find and display the InitProperties() event procedure. Finally, add the following two program lines to the procedure:

    BackColor = Ambient.BackColor
    ForeColor = Ambient.ForeColor

Your control can now match itself to the container's colors. To prove this, close the control's designer window so that it becomes available in the toolbox. Then, double-click frmAmbientDemoTestApp in the Project Group window to reopen the test application's designer window. Finally, double-click the control's icon in the toolbox. Presto! When the new control instance appears, it's the same color as the container (see Figure 25.3).

FIG. 25.3
Now the control is the same color as the container.

AmbientDemo Control, Example 3

What happens, though, if the designer now changes the form's background color again? Go ahead and try it. Set the form's BackColor property to another color. When you do, the form in the designer window immediately changes color to match your selection. The control, however, remains purple. That's because the control doesn't respond to the all-important AmbientChanged event.

You can fix that little problem now. First, double-click AmbientDemo in the Project Group window to open the control's designer window, and double-click the control to open its code window. In the Properties box, select the AmbientChanged event. The AmbientChanged() event procedure appears in the code window. Add the following two lines to the procedure:

    BackColor = Ambient.BackColor
    ForeColor = Ambient.ForeColor

Close the control's designer window so that the control is again available on VB5's toolbox. Reselect the test application's form, and again change its BackColor property. This time, the control keeps up by also changing its background color to match. As you have probably guessed, the AmbientChanged() event procedure is called whenever the developer changes an ambient property. The procedure's single parameter is the name of the property that changed.

Building the Calculator Control

In this section, you learn more about methods and events as you use the Calculator control, located on this book's CD-ROM. The Calculator control is designed to build upon what you have already learned. As a result, the Calculator control will be used as the basis for the majority of the topics presented in this chapter.

The Calculator control enables the user to enter two values that are either summed or multiplied, depending on the setting of a control property, when the user clicks the control's button (see Figure 25.4).

The Calculator control can be found on the CD-ROM included with this book. The calculator control can be found in the controls\calculator\ folder on the CD-ROM.

FIG. 25.4
Here's the Calculator control with its constituent controls in place.

Creating the Interface

The next step, after all the controls are in place, is to create the control interface. As you might recall from the previous chapter, the ActiveX Control Interface Wizard enables you to expose properties, methods, and events of your control. The Calculator control that you are using has a number of properties that can be exposed through this wizard.

See "Using the ActiveX Control Interface Wizard," Chapter 24

Briefly, the Calculator control enables the developer to set the control's main caption, as well as the label shown on the CommandButton control. The Caption property, which handles the main caption, will be delegated to the constituent lblCaption control, whereas the new ButtonCaption property will be delegated to the constituent btnExecute control. Also, the control will need a MultValues property that, when set to True, indicates that the calculator should multiply the entries rather than add them. Finally, the control will need a ValidateEntries() method, which validates user input, and a BadEntries event, which is triggered when ValidateEntries() returns False. Figure 25.5 show the Create Custom Interface Members dialog box with the new members.

FIG. 25.5
The Calculator control needs four new control members.

After using the ActiveX Control Interface Wizard on the Calculator control, the Wizard creates additional source code for your control. Listing 25.2 shows the source code created for the Calculator control. As you can see, the code includes not only all the needed constant, variable, and event declarations, but also all of the property procedures, as well as ready-to-go InitProperties(), ReadProperties(), and WriteProperties() events. Now all you have to do is write the program code that completes the control. You do that in the next section.

Listing 25.2 LSTV_02.TXT--Code Created by ActiveX Control Interface Wizard

`Default Property Values:
Const m_def_BackColor = 0
Const m_def_ForeColor = 0
Const m_def_Enabled = 0
Const m_def_BackStyle = 0
Const m_def_BorderStyle = 0
Const m_def_MultValues = False
`Property Variables:
Dim m_BackColor As Long
Dim m_ForeColor As Long
Dim m_Enabled As Boolean
Dim m_Font As Font
Dim m_BackStyle As Integer
Dim m_BorderStyle As Integer
Dim m_MultValues As Boolean
`Event Declarations:
Event Click()
Event DblClick()
Event KeyDown(KeyCode As Integer, Shift As Integer)
Event KeyPress(KeyAscii As Integer)
Event KeyUp(KeyCode As Integer, Shift As Integer)
Event MouseDown(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
Event MouseMove(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
Event MouseUp(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
Event BadEntries()
Public Property Get BackColor() As Long
    BackColor = m_BackColor
End Property
Public Property Let BackColor(ByVal New_BackColor As Long)
    m_BackColor = New_BackColor
    PropertyChanged "BackColor"
End Property
Public Property Get ForeColor() As Long
    ForeColor = m_ForeColor
End Property
Public Property Let ForeColor(ByVal New_ForeColor As Long)
    m_ForeColor = New_ForeColor
    PropertyChanged "ForeColor"
End Property
Public Property Get Enabled() As Boolean
    Enabled = m_Enabled
End Property
Public Property Let Enabled(ByVal New_Enabled As Boolean)
    m_Enabled = New_Enabled
    PropertyChanged "Enabled"
End Property
Public Property Get Font() As Font
    Set Font = m_Font
End Property
Public Property Set Font(ByVal New_Font As Font)
    Set m_Font = New_Font
    PropertyChanged "Font"
End Property
Public Property Get BackStyle() As Integer
    BackStyle = m_BackStyle
End Property
Public Property Let BackStyle(ByVal New_BackStyle As Integer)
    m_BackStyle = New_BackStyle
    PropertyChanged "BackStyle"
End Property
Public Property Get BorderStyle() As Integer
    BorderStyle = m_BorderStyle
End Property
Public Property Let BorderStyle(ByVal New_BorderStyle As Integer)
    m_BorderStyle = New_BorderStyle
    PropertyChanged "BorderStyle"
End Property
Public Sub Refresh()
     
End Sub
`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=lblCaption,lblCaption,-1,Caption
Public Property Get Caption() As String
    Caption = lblCaption.Caption
End Property
Public Property Let Caption(ByVal New_Caption As String)
    lblCaption.Caption() = New_Caption
    PropertyChanged "Caption"
End Property
Public Property Get MultValues() As Boolean
    MultValues = m_MultValues
End Property
Public Property Let MultValues(ByVal New_MultValues As Boolean)
    m_MultValues = New_MultValues
    PropertyChanged "MultValues"
End Property
`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=btnExecute,btnExecute,-1,Caption
Public Property Get ButtonCaption() As String
    ButtonCaption = btnExecute.Caption
End Property
Public Property Let ButtonCaption(ByVal New_ButtonCaption As String)
    btnExecute.Caption() = New_ButtonCaption
    PropertyChanged "ButtonCaption"
End Property
Public Function ValidateEntries() As Boolean
End Function
`Initialize Properties for User Control
Private Sub UserControl_InitProperties()
    m_BackColor = m_def_BackColor
    m_ForeColor = m_def_ForeColor
    m_Enabled = m_def_Enabled
    Set m_Font = Ambient.Font
    m_BackStyle = m_def_BackStyle
    m_BorderStyle = m_def_BorderStyle
    m_MultValues = m_def_MultValues
End Sub
`Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    m_BackColor = PropBag.ReadProperty("BackColor", m_def_BackColor)
    m_ForeColor = PropBag.ReadProperty("ForeColor", m_def_ForeColor)
    m_Enabled = PropBag.ReadProperty("Enabled", m_def_Enabled)
    Set Font = PropBag.ReadProperty("Font", Ambient.Font)
    m_BackStyle = PropBag.ReadProperty("BackStyle", m_def_BackStyle)
    m_BorderStyle = PropBag.ReadProperty("BorderStyle", _
        m_def_BorderStyle)
    lblCaption.Caption = PropBag.ReadProperty("Caption", "Calculator")
    m_MultValues = PropBag.ReadProperty("MultValues", _
        m_def_MultValues)
    btnExecute.Caption = PropBag.ReadProperty("ButtonCaption", _
        "Execute")
End Sub
`Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    Call PropBag.WriteProperty("BackColor", m_BackColor, _
        m_def_BackColor)
    Call PropBag.WriteProperty("ForeColor", m_ForeColor, _
        m_def_ForeColor)
    Call PropBag.WriteProperty("Enabled", m_Enabled, m_def_Enabled)
    Call PropBag.WriteProperty("Font", Font, Ambient.Font)
    Call PropBag.WriteProperty("BackStyle", m_BackStyle, _
        m_def_BackStyle)
    Call PropBag.WriteProperty("BorderStyle", m_BorderStyle, _
        m_def_BorderStyle)
    Call PropBag.WriteProperty("Caption", lblCaption.Caption, _
        "Calculator")
    Call PropBag.WriteProperty("MultValues", m_MultValues, _
        m_def_MultValues)
    Call PropBag.WriteProperty("ButtonCaption", btnExecute.Caption, _
        "Execute")
End Sub

Testing the Interface

To see the interface in action, close the new control's design window by clicking its close box in the window's upper-right corner. After you close the design window, the control's icon becomes available in VB5's toolbox. Now, double-click frmCalculatorTestApp in the Project Group window. The test application's form designer window appears. Double-click the new control's icon in the toolbox. An instance of the control appears on the test application's form.

To test the control from the developer's point of view, click the control instance to display its properties in the Properties window. You see that the ButtonCaption, Caption, and MultValues properties are now available in this window (see Figure 25.6).

FIG. 25.6
Your control's custom properties appear in the same window as the default properties.

You're now going to use the Calculator control instance to create a simple adding machine. In the Properties window, change the ButtonCaption property to Add, change the Caption property to Adding Machine, and leave the MultValues property set to False.

Now, close the test application's designer window, and save your files. When you do, the WriteProperties() event procedure saves your new property settings to the form's source code. Listing 25.3 shows the newly updated source code. About three quarters of the way down the listing, you can see the settings for the Calculator control instance's properties. Notice that the MultValues property is not listed in the source code. This is because that particular property is still set to its default value. WriteProperties() saves only modified properties, assuming that you've supplied the correct default value to thePropBag.WriteProperty() method. In this case, the ActiveX Control Interface Wizard supplies the code for you.

Listing 25.3 frmCalculatorTestApp.frm--Source Code Containing the Control's Saved Properties

Object = "*\ACalculatorPrj.vbp"
Begin VB.Form frmCalculatorTestApp 
   Caption         =   "Form1"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  `Windows Default
   Begin CalculatorPrj.Calculator Calculator1 
      Height          =   2415
      Left            =   360
      TabIndex        =   0
      Top             =   360
      Width           =   3975
      _ExtentX        =   7011
      _ExtentY        =   4260
      Caption         =   "Adding Machine"
      ButtonCaption   =   "Add"
   End
End
Attribute VB_Name = "frmCalculatorTestApp"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Private Sub Calculator1_Click()
End Sub

Go ahead and reopen the test application. Visual Basic now loads the source code and calls the ReadProperties() event procedure to read the property settings back in to the control. As a result, the test application's form reappears exactly as you left it.

Now run the test application. When you do so, the application's window appears. Type two numbers into the left text boxes and then click the Add button. The result of the addition appears in the Result text box.

What happens, though, if you enter an invalid value or leave one of the text boxes blank, and then click the Add button? As the application stands now, the result box will show the word "Undefined."

The developer can, however, add more error handling to the program by responding to the BadEntries event that you defined when you programmed the control. Whenever the user tries to perform a calculation with invalid entries, the control generates the BadEntries event. To see how the developer can supply additional error handling, close the test application. Then, double-click the control in the test application's form designer window. The code window appears. In the Procedures box, select BadEntries. The Calculator1_BadEntries() event procedure appears in the code window. Add the following line to that event procedure:

    MsgBox "Please enter valid numerical values."

Now, rerun the application and enter invalid values into the text boxes. When you click the Add button, not only does the "Undefined" result appear, but a message box also appears, telling the user how to fix the problem (see Figure 25.7).

FIG. 25.7
By responding to the BadEntries event, the control can display error messages.

As a final experiment, close the test application and then click the control in the test application's form designer window. In the Properties window, change ButtonCaption to Multiply, set Caption to Multiplying Machine, and set MultValues to True. Now, when you run the application, the Calculator will multiply the given values, rather than add them.

Understanding the btnExecute_Click() Procedure

When the user clicks the btnExecute CommandButton control, Calculator must perform the requested operation, whether it be addition or multiplication. The program lines that handle this task are found in the btnExecute_Click() event procedure, which responds to the button click. That procedure first declares the local variables it'll use, as shown in Listing 25.4.

Listing 25.4 LSTV_04.TXT--Declaring Local Variables

Dim EntriesOK As Boolean
Dim s As String
Dim value1 As Integer
Dim value2 As Integer
Dim result As Integer

The procedure then calls the ValidateEntries() method to ensure that the text boxes contain numeric values:

EntriesOK = ValidateEntries

If EntriesOK gets set to False, there are invalid values in the text boxes. In this case, the procedure triggers the BadEntries event and sets the Result text box to "Undefined":

If Not EntriesOK Then
    RaiseEvent BadEntries
    txtResult.Text = "Undefined"

If the text boxes do contain valid values, the procedure can do the requested processing. In this case, the procedure first extracts the strings from the text boxes and converts the strings to integers, as shown in Listing 25.5:

Listing 25.5 LSTV_05.TXT--Converting Strings to Integers

s = txtValue(0).Text
value1 = Val(s)
s = txtValue(1).Text
value2 = Val(s)

After converting the strings, the procedure can perform the requested operation, which is controlled by the MultValues property, as shown in Listing 25.6:

Listing 25.6 LSTV_06.TXT--Performing the Requested Operation

If MultValues Then
    result = value1 * value2
Else
    result = value1 + value2

Finally, the procedure converts the result to a string and displays the string in the Result text box:

s = str(result)
txtResult.Text = s

Understanding the ValidateEntries() Method

As you just saw, before the btnExecute_Click() event procedure can perform its addition or multiplication, it must be sure that the given values are valid. It does this by calling the ValidateEntries() method, which returns True if the entered values are valid, and returns False if they're not valid. This method first declares the local variables it uses, as shown in Listing 25.7.

Listing 25.7 LSTAA_07.TXT--Declaring ValidateEntries()'s Local Variables

Dim str As String
Dim asciiValue As Integer
Dim x As Integer
Dim EntriesOK As Boolean

The Boolean value EntriesOK will hold the result of the validation. The method initializes this value to True:

EntriesOK = True

The method then starts a For loop that will iterate through the control array:

For x = 0 To 1

In this case, there are only two controls in the control array, so the loop goes from 0 to 1.

Inside the loop, the method first extracts the string from the indexed text box:

str = txtValue(x).Text

If it's not an empty string (the text box isn't empty), the method gets the ASCII code for the first character in the string. Otherwise, if the text box is empty, the method sets the ASCII value to 0, as shown in Listing 25.8.

Listing 25.8 LSTV_08.TXT--Getting the ASCII Value of the First Character in the String

If str <> "" Then
    asciiValue = Asc(str)
Else
    asciiValue = 0
End If

The method then compares the returned ASCII code with the ASCII codes for the digits 0 and 9. To be valid, the value being tested must lie within that range. If it doesn't, the method sets EntriesOK to False:

If (asciiValue < 48) Or (asciiValue > 57) The EntriesOK = False

After the For loop completes, the method returns the value of EntriesOK:

ValidateEntries = EntriesOK

In the next section, you take the newly created Calculator control and create property pages.

Using Property Pages

While using Visual Basic, you may have noticed that when you select a Windows 95 control in a project, the Properties window displays not only all the control's properties but also a selection called Custom. This custom selection contains an ellipsis button that, when clicked, displays a property sheet for the control. The property sheet contains all the control's custom properties, and a developer can use the property sheet to set properties even when not working under Visual Basic.

For example, suppose you add a property sheet to the Calculator control that you created earlier in this chapter. After you've added the property sheet, Visual Basic displays the Custom ellipsis button in the properties section for the control. When you click the ellipsis button, the control's property sheet pops up. When working in Visual Basic on a project containing the control, you can set the control's custom properties from Visual Basic's Property window or from the control's property pages. Figure 25.8 shows what such a property sheet might look like.

FIG. 25.8
A control's property sheet often contains several pages of properties.

Creating Property Page Objects

The previous chapter taught how to create property pages by using the Property Page Wizard. The property sheet created with the Wizard in that chapter was easy to construct and required very little additional code to be functional. The Property Page Wizard is designed to provide you with a quick way of completing a property sheet; however, the Wizard does not provide many options from which to choose for your own modifications. In this section, you will create property sheets the old-fashioned way, with code. The advantage to this method is that you are able to make design and feature choices that otherwise would be unavailable through the Property Page Wizard.

The first step in creating your custom control's property sheet is to create property page objects for each group of properties. How you organize your properties into groups is up to you, although you'll want to use common sense. If your control has only a few properties, you can probably get away with representing them on a single property page. If the control has many properties, though, you should group related properties onto their own property pages. The typical steps involved in creating a property page for a control are as follows:

1. Create property page objects for each page you want in the property sheet.

2. On the property pages, place controls that the developer will use to edit each of the properties. (For example, you might use a TextBox control to represent a control's Caption property.)

3. Implement the SelectionChanged() event procedure for each property page. This event procedure is called when a developer opens the property page or selects one or more controls in the current project. The SelectionChanged() event procedure is responsible for reading in the current values of each controls' properties.

4. Implement the Change() event procedure (or sometimes Click()) for each control in each property page. This event procedure is called whenever the developer changes the contents of one of a property page's controls. The Change() event procedure notifies Visual Basic of the change, so that Visual Basic can enable the property sheet's Apply button.

5. Implement the ApplyChanges() event procedure for each property page. This event procedure is called when the developer closes a property page or when the developer clicks the property sheet's Apply button. Its task is to copy the new property values from the property page's controls to the custom control's properties.

6. Connect the property pages to the custom control.

For the calculator project, you'll create three property pages called General, Captions, and Flags. Follow these steps to create these property page objects:

1. Choose File, Open Project to load the Calculator project into VB5.

2. Select Calculator in the Project Group Window.

3. Choose Project, Add Property Page from the menu bar. The Add Property Page property sheet appears.

4. On the Add Property Page property sheet's New page, double-click the Property Page icon to add a property page object to the project. The property page's designer window appears on the screen.

5. Double-click the Name property in the property page's Properties window and type CalculatorGeneral.

6. Also in the Properties window, double-click the Caption property and type General.

A property page's tab uses the Caption property as its label. Because the page's tab is provided by the property sheet that contains the pages, the caption doesn't appear in the designer window.

7. Create two more property sheet objects named CalculatorCaptions and CalculatorFlags, with Caption properties of Captions and Flags, respectively.

Placing Controls on the Property Pages

Now that you have created the property page objects, you can add the control you need to represent the properties of the custom control to which the property sheet will be attached. The types of controls to use are up to you. Usually, though, you'll use TextBoxes to represent text properties, CheckBoxes to represent Boolean properties (properties that can be set to True or False), ListBoxes to represent properties with multiple settings, and so on. Follow these steps to add controls to the property pages you created in the previous section:

1. Double-click CalculatorGeneral in the Project Group window. The CalculatorGeneral property page's designer window appears.

2. Add four Label controls to the property pages, using the following properties:

Name Caption
lblBackColor Backcolor
lblBackStyle BackStyle
lblBorderStyle BorderStyle
lblForeColor ForeColor

3. Add four TextBox controls to the property page, using the following properties:

Name Height Left Top Width
txtBackColor 330 90 370 3700
txtBackStyle 330 90 1020 2700
txtBorderStyle 330 90 1670 2700
txtForeColor 330 90 2970 2700

4. Add a CheckBox control to the property page, using the following properties. When complete, the CalculatorGeneral property page should look like Figure 25.9.
Name chkEnabled
Caption "Enabled"

FIG. 25.9
Here's the CalculatorGeneral property page with all of its controls.

5. Double-click CalculatorCaptions in the Project Group window. The CalculatorCaptions property page's designer window appears.

6. Add two Label controls to the property pages, using the following properties:

Name Caption
lblButtonCaption "ButtonCaption:"
lblCaption "Caption:"

7. Add two TextBox controls to the property page, using the following properties. When complete, the CalculatorCaptions property page should look like Figure 25.10.

Name txtButtonCaption
Name txtCaption

FIG. 25.10
Here's the CalculatorCaptions property page with all its controls.

8. Double-click CalculatorFlags in the Project Group window. The CalculatorFlags property page's designer window appears.

9. Add a CheckBox control to the property page (see Figure 25.11), using the following properties:

Name chkMultValues
Caption "MultValues"

FIG. 25.11
Place the CheckBox control as shown here.

Implementing the SelectionChanged() Event Procedure

Imagine for a moment that a developer is using the Calculator control as part of an application. The developer has placed two Calculator controls in the application's window. One Calculator control will perform addition and the other will perform multiplication. Now the developer wants to set the controls' properties and therefore calls up the controls' property sheet. When this happens, each property page's SelectionChanged() event procedure gets called, so that each control's current property settings can be loaded into the property pages' controls, where the developer can change them.

Of course, at this point, all you've done is create the property page objects and position Label, TextBox, and CheckBox controls on them. Now you need to write the code for the SelectionChanged() event procedure. To do that, follow these steps:

1. Double-click CalculatorGeneral in the Project Group window to display the CalculatorGeneral property page's designer window.

2. Double-click the property page to display its code window. The window appears, showing the SelectionChanged() event procedure.

3. Add the program lines shown in Listing 25.9 to the SelectionChanged() event procedure.

Listing 25.9 LSTV_9.TXT--Program Lines for the SelectionChanged() Event Procedure

    txtForeColor.Text = SelectedControls(0).ForeColor
    chkEnabled.Value = (SelectedControls(0).Enabled And vbChecked)
    txtBorderStyle.Text = SelectedControls(0).BorderStyle
    txtBackStyle.Text = SelectedControls(0).BackStyle
    txtBackColor.Text = SelectedControls(0).BackColor
4. Double-click CalculatorCaptions in the Project Group window to display the CalculatorCaptions property page's designer window.

5. Double-click the property page to display its code window. The window appears, showing the SelectionChanged() event procedure.

6. Add the following program lines to the SelectionChanged() event procedure:
    txtButtonCaption.Text = SelectedControls(0).ButtonCaption
    txtCaption.Text = SelectedControls(0).Caption
7. Double-click CalculatorFlags in the Project Group window to display the CalculatorFlags property page's designer window.

8. Double-click the property page to display its code window. The window appears, showing the SelectionChanged() event procedure.

9. Add the following program line to the SelectionChanged() event procedure:
    chkMultValues.Value = (SelectedControls(0).MultValues And vbChecked)

If you examine the program lines you added to the SelectionChanged() event procedures, you'll see that each control in each property page copies a value from the control into the property page's control. For example, the Calculator control's Caption property is represented in SelectionChanged() by the following program line:

txtCaption.Text = SelectedControls(0).Caption

Recall from the previous section that the developer placed two Calculator controls on the application's window. If the developer selects both controls before displaying the controls' property sheet, the property sheet represents a multiple selection. In the previous code line, you can see that the SelectedControls collection object represents the selected controls. SelectedControls(0) is the first selected control and SelectedControls(1) is the second.

Currently, the property page can handle only single control selections because it processes only SelectedControls(0), copying the selected control's Caption property into the property page's txtCaption TextBox control. You'll learn about multiply selected controls later in this chapter in the section titled "Handling Multiple Control Selection."

Understand that when the property page first appears, its SelectionChanged() event procedure copies the current property values into the property page's controls. This enables the property page to display the current settings to the developer. Each control in each property page must be represented by a line in SelectionChanged().

Implementing the Change() Event Procedure

When the developer changes the contents of a TextBox (or other similar Visual Basic controls), the control's Change() event procedure is called. In the case of a property page, this gives you a chance to notify Visual Basic that the contents of a property have changed, allowing Visual Basic to enable the property sheet's Apply button. All you have to do to tell Visual Basic about this change is to set the control's Changed property to True.


NOTE: Some controls, such as CheckBoxes, use their Click() event procedure instead of Change() to handle the change notification.

To handle this important event in the CalculatorGeneral property page, bring up the property page's code window and add the lines shown in Listing 25.10 to the window, outside of any other methods or event procedures. Add the procedures shown in Listing 25.11 to the CalculatorCaptions property page's code window. Finally, add the following procedure to the CalculatorFlags property page's code window.

Private Sub chkMultValues_Click()
    Changed = True
End Sub

Listing 25.10 LSTV_10.TXT--New Event Procedures for the CalculatorGeneral Property Page

Private Sub txtForeColor_Change()
    Changed = True
End Sub
Private Sub chkEnabled_Click()
    Changed = True
End Sub
Private Sub txtBorderStyle_Change()
    Changed = True
End Sub
Private Sub txtBackStyle_Change()
    Changed = True
End Sub
Private Sub txtBackColor_Change()
    Changed = True
End Sub

Listing 25.11 LSTV_11.TXT--New Procedures for the CalculatorCaptions Property Page

Private Sub txtCaption_Change()
    Changed = True
End Sub
Private Sub txtButtonCaption_Change()
    Changed = True
End Sub

Implementing the ApplyChanges() Event Procedure

The ApplyChanges() event procedure is the opposite of SelectionChanged(). Whereas SelectionChanged() copies the custom control's current properties into the property page, the ApplyChanges() event procedure copies the property page's contents back into the custom control's properties. This happens when the developer clicks the property sheet's Apply button or when the developer closes a property page.

To add program code for the ApplyChanges event, add the procedure shown in Listing 25.12 to the CalculatorGeneral property page's code window. Then, add the procedure shown in Listing 25.13 to the CalculatorCaptions property page's code window. Finally, add the following procedure to the CalculatorFlags property page's code window.

Private Sub PropertyPage_ApplyChanges()
    SelectedControls(0).MultValues = (chkMultValues.Value = vbChecked)
End Sub

Listing 25.12 LSTV_12.TXT--The ApplyChanges() Event Procedure for the CalculatorGeneral Property Page

Private Sub PropertyPage_ApplyChanges()
    SelectedControls(0).ForeColor = txtForeColor.Text
    SelectedControls(0).Enabled = (chkEnabled.Value = vbChecked)
    SelectedControls(0).BorderStyle = txtBorderStyle.Text
    SelectedControls(0).BackStyle = txtBackStyle.Text
    SelectedControls(0).BackColor = txtBackColor.Text
End Sub

Listing 25.13 LSTV_13.TXT--The ApplyChanges() Event Procedure for the CalculatorCaptions Property Page

Private Sub PropertyPage_ApplyChanges()
    SelectedControls(0).ButtonCaption = txtButtonCaption.Text
    SelectedControls(0).Caption = txtCaption.Text
End Sub

Connecting the Property Pages to the Control

The property pages you've been building are now complete. All you have to do now is connect the property pages to the control whose properties they represent. To perform that task, follow these steps:

1. Double-click Calculator in the Project Group window. The control's designer window appears.

2. In the control's Properties window, double-click the PropertyPages property. The Connect Property Pages dialog box appears.

3. Place check marks in the CalculatorGeneral, CalculatorCaptions, and CalculatorFlags check boxes (see Figure 25.12).

FIG. 25.12
The checked property pages will be attached to the control.

4. Click OK in the Connect Property Pages dialog box to associate the selected property pages with the Calculator control.

Using the Property Sheet

You've completed the creation of your Calculator control's property sheet. When you're working on an application that contains the control, you can set the control's properties from Visual Basic's Properties window or from the control's property sheet. To see this in action, first make sure that you have closed all of your control's design forms. After you have done this, double-click frmCalculatorTestApp in the Project Group window. When the test application's designer window appears, click the Calculator control to select it. When you do, the control's properties appear in the Properties window. If you look closely, you'll see a new property entry called Custom.

This new Custom property entry is your gateway (under Visual Basic) to the control's property sheet. To see the property sheet, double-click the Custom entry. You also can display the property sheet by clicking Custom and then clicking the ellipsis button. When you do, the property sheet appears. Using this property sheet, you can set the properties of the selected control (see Figure 25.13). Notice that, as soon as you start typing in one of the TextBoxes, or when you click on a CheckBox, the property sheet's Apply button becomes enabled.

FIG. 25.13
Here's your finished property sheet showing the Caption page.


NOTE: Remember to save the changes you've made to the Calculator project. Notice that when you do save the changes, the property page files are stored with .PAG file extensions.

Handling Multiple Control Selections

You may recall that the SelectedControls collection object represents all the controls that a developer has selected when he calls up the property sheet. You use the SelectedControls object in the SelectionChanged() and ApplyChanges() event procedures to access the selected controls' properties. For example, suppose the developer has two Calculator controls selected. Then, in the appropriate property page, SelectedControls(0).ButtonCaption represents the ButtonCaption property of the first control and SelectedControls(1).ButtonCaption represents the ButtonCaption property of the second control. The developer can set both controls' button captions to the same value by selecting both controls and then setting the ButtonCaption property in the property page that displays that property.

This brings up an important point. Not all properties should be set the same in multiple controls. Suppose that the developer selects two Calculator controls and uses the property sheet to change ButtonCaption to "Add." In this case, both instances of the control will have a button labeled "Add," which may be what the developer wants. However, it's not likely that two Calculator controls will have the same Caption property because it is the caption that labels the control for the user. So, when dealing with multiple control selections, you have two types of properties:

You can tell how many controls are selected by examining the SelectedControls object's Count property. If Count is greater than 1, you're dealing with a multiple selection. You also can simply code ApplyChanges() such that only the properties of the first control in the control collection get assigned properties that should be unique, whereas all controls get assigned properties that can logically be the same from one control to another. Listing 25.14 shows a version of ApplyChanges() that takes this latter approach.

Listing 25.14 LSTV_14.TXT--An ApplyChanges() Event Procedure That Can Handle Multiple Controls

Private Sub PropertyPage_ApplyChanges()
    Dim control As Calculator
    
    ` Set only the first control's caption.
    SelectedControls(0).ButtonCaption = txtButtonCaption.Text
    
    ` Set every control's button caption.
    For Each control In SelectedControls
        control.Caption = txtCaption.Text
   Next control
End Sub

The special For Each loop enables the program to iterate through the control collection, without dealing with indexes and without needing to know how many controls the collection contains.

Visual Basic's Debugger

As the developer changes property settings, Visual Basic calls the properties' Get and Let property procedures. Event procedures may also be called if the developer does something such as resize the control instance. The important thing to notice is that none of the control's methods or procedures that implement its runtime functionality are called at design time. Therefore, you have to test the control's design time and runtime features separately.

To test design time features, you must act as a developer, placing the control on a test application's form and manipulating it as a developer would, changing property values and resizing the control. To test your control's runtime features, you must run the test application and manipulate the control as the user would.

One very effective way to fully test a control is to create a test project. The test project should call out to all the properties, methods, and events that your control uses. The test project should also test the validation routines that you have in place by providing values outside of the accepted range. The test project should be an .exe program; this way, it can be fully compiled into a stand-alone program.

The Calculator control that you just completed is a good example of a control that needs a test project to fully test its functionality. The test project should try to invoke the Add method when there is no data or the multiply method when the value is a negative number. What you learn from your test application will help to produce better exception and error handling routines for your control.

Your test project should be capable of locating most of the errors that could occur in your control. It is especially important to test out any error handling that you have in your control. This will verify that your control will not cause damage to the user's machine and that you have properly handled the error.


NOTE: The test project can detect a lot of potential problems; however, it can't trap all of the errors that may occur. This can be done only through the use of good code design and solid unit-level testing of each component of the control.

To make the testing and debugging task a little easier, Visual Basic includes a built-in debugger. You can use the debugger to set breakpoints in a control's code or to step through program code one line at a time. As you're stepping through program lines, you can watch how the values of variables change. Visual Basic 5 provides you with the debugging commands shown in Table 25.2.

Table 25.2 Visual Basic Debugger Commands

Command Description
Step Into Steps into the procedure being called
Step Over Executes the procedure being called without stepping into the procedure
Step Out Executes the remaining lines in a procedure
Run to Cursor Executes all lines up to the current position of the cursor
Add Watch Adds a variable to the Watch window
Edit Watch Edits an entry in the Watch window
Quick Watch Displays the value of the expression at the mouse cursor position
Toggle Breakpoints Turns a breakpoint on or off
Clear All Breakpoints Removes all breakpoints that were set
Set Next Statement Sets the next statement to execute
Show Next Statement Shows the next statement to execute

To get some practice with Visual Basic's 5 debugger, first create a folder called Calculator2 and copy all the files from the section exercise to the new Calculator2 folder. Then, follow these steps to see Visual Basic's debugger in action:

1. Load the CalculatorGroup.vbg project group from the new Calculator2 folder.

2. In the Project Group window, double-click Calculator. The Calculator control's designer window appears.

3. Double-click the designer window. The Calculator control's code window appears, showing the InitProperties() event procedure.

4. Click the gray bar to the left of the text cursor. Visual Basic sets a breakpoint on the selected line. This line is marked as a red bar in the code window. You also can turn on the breakpoint by selecting Debug, Toggle Breakpoint from the menu bar or by pressing F9 on your keyboard.

When the control's code is running, Visual Basic stops program execution when it reaches a breakpoint. This enables you to examine a procedure more carefully, as you'll soon see.

5. Close the code and designer windows. The Calculator control becomes available on VB5's toolbox.

6. In the Project Group window, double-click frmCalculatorTestApp. The test application's designer window appears.

7. In the designer window, click the instance of the Calculator control to select it. Then, press your keyboard's delete key to remove the instance from the test application.

8. Double-click the Calculator control's icon in VB5's toolbox. Visual Basic creates an instance of the control, but stops the control's initialization on the first line of the InitProperties() event procedure. The line is marked with a red and yellow bar.

Visual Basic stopped execution on this line because that's where you set the breakpoint in Step 4. Now that the control's code execution has stopped, you can use other debug-ger commands to examine the event procedure line by line. The red part of the bar tells you that there's a breakpoint set on this line. The yellow portion of the bar indicates that this is the next line that will execute.

9. Place the mouse cursor over m_BackColor in the code window. The value of the m_BackColor variable appears in a small tip box (see Figure 25.14).

FIG. 25.14
You can examine the contents of variables just by placing the mouse cursor over the variable's name in the code window.

10. Press F8 (Step Into) a few times. Each time you do, Visual Basic executes the current program line and moves the yellow bar to the next line (see Figure 25.15). This line-by-line execution enables you to examine variables that may have been set or how the program flow is progressing. This knowledge can help you to determine if certain code is being executed, or if your variables have the expected values.

11. Scroll the code window up until you can see the Get Caption() property procedure. Set a breakpoint on the Caption = lblCaption.Caption line.

12. Press F5 (Start) to continue the program execution. When you do so, the program runs until it hits the breakpoint you set in the Get Caption() property procedure.

FIG. 25.15
The yellow bar moves to always indicate the next line to execute.

13. Select Debug, Add Watch from VB5's menu bar. The Add Watch dialog box appears (see Figure 25.16).

FIG. 25.16
You use the Add Watch dialog box to add expressions to the Watch window.

14. Type Caption in the Expression box, and click OK. The Watch Window appears at the bottom of VB5's main window, showing the variable's name, current value, data type, and the context in which you're viewing the expression.

15. Press F8 to execute the current program line. Visual Basic assigns a new value to the Caption variable.

Many program bugs can be traced to bad values being assigned to variables. By using the Watch Window, you can quickly locate these kinds of problems.

16. Select Debug, Clear All Breakpoints from the menu bar. Visual Basic removes all the breakpoints you set.

17. Select Run, End to end the debugging session. Then, close the open code window, and delete the Calculator control instance from the test application's designer window.

The VB5 debugging tools can be a powerful way to look inside your code while it is executing. The previous example enabled you to walk through the process of debugging your code. If you used the code contained on the CD-ROM, you probably found that there were no errors in the source code. To fully experience the power of the debugger, you need to use it on code that has bugs in it. To help you, Listings 25.15 and 25.16, which have typical programming bugs included in them, provide sample code segments from the Calculator.ctl project.

Listing 25.15 LSTV.15--The Entry Validation Function

Public Function ValidateEntries() As Boolean
    Dim str As String
    Dim asciiValue As Integer
    Dim x As Integer
    
    EntriesOK = True
    For x = 0 To I
        str = txtValue(x)Text
        If str <> "" Then
            asciiValue = Asc(str)
        Else
            asciiValue = 0
        End If
        If (asciiValue < 48) Or (asciiValue > 57) Then 
            Entries = False
   ext x
    ValidateEntries = EntriesOK
End Function

Listing 25.16 LSTV.16--Read Properties Subroutine

`Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    m_BackColor = PropBag.ReadProperty("BackColor", m_def_BackColor)
    m_ForeColor = PropBag.ReadProperty("ForeColor", m_def_ForeColor)
    m_Enabled = PropBag.ReadProperty("Enabled", m_def_Enabled)
    Set Font = PropBag.ReadProperty("Font" Ambient.Font)
    m_BackStyle = PropBag.ReadProperty("BackStyle", m_def_BackStyle)
    m_BorderStyle = PropBag.ReadProperty("BorderStyle", m_def_BorderStyle)
    lblCaption.Caption = PropBag.Property("Caption", "Calculator")
    m_MultValues = PropBag.ReadProperty("MultValues", m_def_MultValues)
    btnExecute.Caption = PropBag.writeProperty("ButtonCaption", "Execute")
End Sub

Control Error Handling

The importance of controls running correctly cannot be overstated. Developers and users are depending on you to create a product that they can use with confidence. If a developer incorporates your control into an application and the application then fails for the user because of your control, it's the developer who'll take the blame. It'll appear to the user that the developer's application is at fault. (Of course, in this situation, both you and the developer would have to share the blame. The developer is responsible for testing that his application runs correctly with your control.) There are a few things you should do, and a few things you should not do, to help ensure that your controls won't present developers and users with nasty surprises.

First, use plenty of error handling, especially in event procedures. If an error occurs in an event procedure, and your program code doesn't handle it, the control will crash--bringing down the container in which the control is sited. Notice that you are advised to provide plenty of error handling. Do not raise your own errors in event procedures. If you do, your application will surely crash and take everything down with it.


NOTE: One way of generating errors is through the Raise method. You can use this method if you want more information than generally is available using the Error statement. One of the bits of information that the Raise method provides is the source of the error. Additional help related information can also be provided The information that the Raise method provides can be useful during the creation of classes in VB.


NOTE: Any Raise methods that are executed within your ActiveX during run time will cause your control to escape beyond the boundaries of the control.

Typically, all events that have any code attached to them should be error handled. There are some key areas that need special attention:


CAUTION: Do not be tempted into taking the easy way out of error handling with the "on error resume next" method. This method enables your program or ActiveX control to continue working even if there was a error, serious or otherwise. This method of error handling does not provide any method of gracefully handling errors.

Properly error handling your procedures and functions takes only a few lines that can easily be cut and pasted where they are needed. In Listing 25.17 you see one example of how to trap for the "No Current Record" error during database activity.

Listing 25.17 errordemo.bas--Proper Error Handling

on error goto errhandler
`Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    Call PropBag.WriteProperty("BackColor", m_BackColor, m_def_BackColor)
    Call PropBag.WriteProperty("ForeColor", m_ForeColor, _
         m_def_ForeColor)
    Call PropBag.WriteProperty("Enabled", m_Enabled, _
         m_def_Enabled)
    Call PropBag.WriteProperty("Font", Font, Ambient.Font)
    Call PropBag.WriteProperty("BackStyle", m_BackStyle, _
         m_def_BackStyle)
    Call PropBag.WriteProperty("BorderStyle", m_BorderStyle, _
         m_def_BorderStyle)
    Call PropBag.WriteProperty("Caption", lblCaption.Caption, _
         "Calculator")
    Call PropBag.WriteProperty("MultValues", m_MultValues, _
         m_def_MultValues)
    Call PropBag.WriteProperty("ButtonCaption", btnExecute.Caption, _
         "Execute")
exit sub
errhandler:
` Here is where you would put your error handling routines 
` such as this:
` if err = 3021 then
`  msgbox("There are no records in the database")
` endif
` Note: You may find it more appropriate to use a CASE 
` construct rather than a If..Then..Else.
End Sub

In Listing 25.17 you saw how to trap for a specific type of error in a procedure. Notice that the first line of code:

on error goto errhandler

refers to the errhandler routine. This routine, located at the end to the parent procedure, actually looks for and traps the error as shown in the following lines of code:

errhandler:
` Here is where you would put your error handling 
` routines such as this:
` if err = 3021 then
`  msgbox("There are no records in the database")
` endif

Although these lines only trap one error, they could easily be modified to trap more than one or to call out to a global error handling procedure. The following code examples shown how a global error handling procedure might be constructed. They are meant to illustrate a point only, your actual code will need to be tailored to the specific needs of the application.

The following code segment would be placed the procedures that you want to trap for errors. The errortrap() function is passed the value contained in err object.

Errhandler:
errortrap(err)

The following code segment would be placed in a .BAS file or a .CLS file. The purpose is to assign a message to a global variable based upon the error number passed to it.

Function errortrap(err as integer)
Select Case err
     Case 3021
         gError_Msg = "A problem has occurred with your database."
     Case 13
         gError_Msg = "A System error has occurred"
  
... (more error handling code)
     Case Else
         gError_Msg = "An unexplained error has occurred"
End Select
... (any additional code)

Another tip is to always use Get and Let property procedures for a control's properties. Don't create properties by adding public variables to a control. If you use the Get and Let mechanism, you can validate a property's value once, in the property's Let procedure. The property is then guaranteed to be valid in the rest of the program.

If you use public variables for properties, the developer has complete access to the properties' values and can change them behind your program's back. This means that you must check the properties' values every time they're used. Not only is such a practice a pain, it's also a good way to crash a program. You can't raise errors in event procedures, should that be where you discover that a property has been assigned an invalid value.

In general, wherever the developer can change values of data in your control, be sure that the changes are valid. Any such values should be implemented as properties, giving your control the capability to verify the values in the property's Let procedure. Similarly, at run time, be sure that any values that the control accepts from the user are valid. A good example is the Calculator control you worked with in this chapter. When the user enters values into the control's test boxes, you want to be sure that those values can be added or multiplied. The ValidateEntries() method of the Calculator control handles that little detail as described in the "Understanding the ValidateEntries() Method" section.

Distributing ActiveX Controls by Using Setup Wizard

The Setup Wizard is a tool used with the Visual Basic Setup Toolkit that aids you in the creation of application setup and distribution. The Setup Wizard sets up distribution of your application across the Internet using automatic code download from Microsoft Internet Explorer, versions 3.0 and 4.0. The Setup Wizard enables automatic downloading of your object with the initialization of the page that contains it with cabinet (.cab) files. Also, the Setup Wizard analyzes your project and determines what supporting files need to be included with your control.


NOTE: Cabinet files are specially formatted files that contain ActiveX controls along with all of the necessary support files. These files contain information about the control and the necessary support files that tell the Web browser what files need to be downloaded. The Web browser takes the information from the cabinet file and compares it to the files already on the computer to determine whether any of the files need to be downloaded. This enables the Web browser to avoid downloading any unnecessary files.

You can start the Setup Wizard by choosing Application Setup Wizard from your Start menu or Setupwiz.exe from the \Setupkit\Kitfil32 directory where you installed Visual Basic.

Then you choose to build an Internet Download package with the Application Setup Wizard, and you'll be presented with a series of dialog boxes.

Introduction Dialog Box The Introduction dialog box makes you aware of the capabilities of the Setup Wizard. The first time you run the Setup Wizard, read the description and then select the Skip the Screen in the Future check box, shown in Figure 25.17. Choose Next to go to the next dialog box.

FIG. 25.17
The Setup Wizard Introduction dialog window provides you with some basic information about using the Wizard to build application distribution files.

Select Project and Options Dialog Box Type the name of the project file or use the Browse button to find the project file. For the distribution options, select Create Internet Download Setup, as seen in Figure 25.18. Choose Next to go to the next dialog box.

FIG. 25.18
To build the necessary files for use in an HTML document, you need to use the Application Setup Wizard to create an Internet Download Setup.

Internet Distribution Location Dialog Box Use the file browser to specify where you want your ActiveX control to be placed for Internet download, as shown in Figure 25.19. Choose Next to go to the next dialog box.

FIG. 19.19
You need to specify a location for the Setup Wizard to build the Internet Download files.

Internet Package Dialog Box You must decide where you want to locate the runtime components of your applications. You can either select your own location on one of your own servers or download runtime components from the Microsoft Web site. Specifying a location on an internal server might be better if you don't have a fast connection to the Internet. On the other hand, by specifying the Microsoft Web site at http://www.microsoft.com/vbasic/icompdown, you guarantee that your users always get the latest copies of the runtime components. Click the Safety command button to set safety levels for each ActiveX control in your project. If your control is safe for initialization, check Safe for Initialization. If your control is safe for scripting, check Safe for Scripting, as in Figure 25.20. (See the later section "Making Your Controls Safe for Scripting and Initialization" for more information.) Choose OK on the Safety dialog box, and Next on the Internet Package dialog box to go to the next dialog box.

FIG. 25.20
By marking your control as safe for initialization and scripting, you are placing your guarantee in your control that it cannot harm the user's computer, even if being used in HTML documents that you did not build.

ActiveX Server Components Dialog Box The Setup Wizard analyzes your project and looks for any ActiveX controls that you might be using as server-side controls. If your control uses any server-side components, either as local or remote components, add them with this dialog box. Choose Next to go to the next dialog box. If your control will be distributed to other developers for use in building Web pages, you need to include the Property Page DLL with your file distribution. This provides developers with the ability to specify property settings for the control in other, non-Visual Basic development environments. The Setup Wizard stops and asks you if you want to include this DLL with the setup files, as seen in Figure 25.21.

FIG. 25.21
The Setup Wizard asks you if you want to include the Property Pages DLL with your file distribution package.

File Summary Dialog Box The File Summary dialog box lists all of the files that are distributed with your ActiveX control, as shown in Figure 25.22. If there are any other files you want to include with your file distribution, including readme and licensing files, this is where you add them. Use the Add button to add additional files. Choose Next to complete the Setup Wizard.

FIG. 25.22
The File Summary dialog box shows you what files will be included in the Internet download files that will be packaged for including on a Web site.

Finally, the Setup Wizard provides you with the opportunity to save the information you have specified as a template to be used every time you run the Setup Wizard on this same project. Click the Finish button to build your distribution package.

Viewing Your ActiveX Control in a Web Browser

The Setup Wizard creates a default Web page that has your ActiveX control inserted on it. This Web page is located where you decide to place your ActiveX control. Open this page with a Web browser that supports ActiveX, such as Internet Explorer 3.0, as seen in Figure 25.23. If you view the source code of the page, you see a simple HTML document that contains the <OBJECT> element, specifying the Class ID of your control, as seen in Listing 25.18.

FIG. 25.23
Once you have finished building your Internet distribution files, you can open the HTML file that was generated by the Setup Wizard in Internet Explorer to test your control.

Listing 25.18 AXYESNO.HTM--Source of AXYesNo.HTM

<HTML>
<!--     If any of the controls on this page   require licensing,
 you must create a license package file.
     Run LPK_TOOL.EXE in the tools directory to create the 
required LPK file.
<OBJECT CLASSID="clsid:5220cb21-c88d-11cf-b347-00aa00a28331">
     <PARAM NAME="LPKPath" VALUE="LPKfilename.LPK">
</OBJECT>
-->
<OBJECT
     classid="clsid:B7C523AE-6500-11D0-AB01-444553540000"
     id=AXYesNo
     codebase="AXYesNo.CAB#version=1,0,0,0">
</OBJECT>
</HTML>


NOTE: The Class ID included in the HTML in Listing 25.18 will be different from the Class ID generated in your HTML document. This Class ID is a globally unique identifier automatically generated by Visual Basic when you build your control. You will never generate two matching Class IDs, and the Class IDs generated by Visual Basic to identify your controls will never be the same as any other Class IDs generated by anyone else to identify anyone else's control. This Class ID is automatically registered with your system Registry database and is used by the operating system to determine whether the control needs to be downloaded from the Web site, or whether you already have the control on your system.

All object information is defined between the <Object> and the </Object> tags. The following lists some important tags you should be familiar with:

ClassID The unique ID for this object
ID The name that you use to specify this control in script
Codebase Location of distribution files for your ActiveX control


NOTE: For information about accessing your ActiveX control from HTML using VBScript, see Chapters 35, "Internet Programming with VBScript," and 36, "Programming Web Pages."

Responsible ActiveX Creation and Distribution

ActiveX controls give the control programmer a new tool that can be used responsibly or otherwise. We as ActiveX programmers have a obligation to provide controls that are safe to use and instill a high level confidence in the developers who use our controls. This type of responsible creation encompasses the following ideas which you look at in detail in this section.

Marking Your Controls Safe for Scripting and Initialization

Safe for Scripting means that there is no way to harm a user's computer or obtain information about the user's computer without permission, no matter what commands are scripted to the control. Safe for Initialization means that there is no way to harm a user's computer or obtain information about a user's computer by just executing the control.

If you have not marked your control as safe for scripting and safe for snitialization, then the user's Web browser warns the user that the control is not safe and does not load the control. Only if the user has set the security level on his or her browser to its lowest and least safe setting does the browser download and run your control.

Marking Your ActiveX Controls Safe for Initialization By marking your control safe for initialization, you guarantee users that there is no way to harm their computer or steal information about their computer by loading your control, regardless of the initialization parameters specified in the HTML file. If a user can specify parameters in the <PARAM> tags that accompany the <OBJECT> element that could cause your control to damage or alter the user's system in any way, you should not mark your control as safe for initialization. An ActiveX control's initial state is defined by the PARAM statement on the HTML page that calls the object. If your control is safe for initialization, you must verify all properties given in PARAM statements in the controls InitProperties and ReadProperites events.

To mark your control safe for initialization, select the Safe for Initialization check box on the Setup Wizard Safety dialog box. Keep in mind that you are specifying that your control is safe for initialization by using the honor system. If you have not thoroughly tested your control by using all sorts of corrupt and malicious initialization settings, then you may be dishonestly stating to your users that your control is safe when it really may not be safe.

Marking Your ActiveX Control Safe for Scripting By marking your control safe for scripting, you guarantee your control's users that there is no way to harm their computer or steal information about their computer no matter what scripting commands are given to your control. Following are things that are not safe:

Be careful when using constituent controls. Many have methods that can be used maliciously. Choose cautiously what properties and methods you make available.

To mark your control safe for initialization, select the Safe for Initialization check box on the Setup Wizard Safety dialog box. Once again, you are specifying this on your honor. You are giving your users your word that your control is safe for their use.

Control Certification

Users of your control might never have heard of your company and might be skeptical of downloading objects from you. Unlike when you buy a program from the store, there is no accountability when you distribute controls over the Internet--your controls are not shrink-wrapped with your company logo on the package. A programmer who is malicious, or at least not careful, can cause bad things to happen to a user's computer. A poorly written ActiveX control can do the following among other things:

Digital signatures through third parties give a path back to you in case something unethical is performed with your control. By signing your control with a digital certificate, you are essentially placing your control into an electronic shrink-wrap and placing your company logo on the package, providing your customers with some degree of comfort that they know who built the controls.

Licensing Issues

When you build ActiveX controls with Visual Basic and use Visual Basic intrinsic constituent controls to aid in your product development, you do not need to include any additional support files with your distribution files, other than the VB run-time DLL. Also, by using the Visual Basic constituent controls, you do not need to worry about any licensing issues.

In most cases, but not all, the authors of programmable objects VBXs, OCXs, and ActiveX controls give you the right to distribute them with your finished project without royalties. However, when you build ActiveX controls that you plan to distribute, you are giving a developer, the Web designer, the ability to create new things with your ActiveX control. Most licensing agreements that come with custom controls do not give you the right to distribute any part of the author's control in a non-finished product form. Also, if the author uses Standard Registry Licensing Scheme, you cannot distribute the author's controls on a Web server.

Versioning of Controls

After you have created and distributed some of your ActiveX controls, you will most likely need to address the question of version control. When you enhance an existing version of a ActiveX control, you run the risk of adding or removing code that will cause the previous version of your control to fail for the user. Consider the Calculator control: If you created a new version that added a division function, existing users may not be affected. However, if you created a new version that changed the way the add function worked, you would run the risk of control failure for your installed user base. Versioning of ActiveX controls is not hard, but it does take a little forethought and planning.

A complete discussion of versioning of ActiveX controls can be found in the Books Online menu option under the Help menu. You can also visit Microsoft's Site Developer Network located at www.microsoft.com/msdn/ for more information about versioning of ActiveX controls.

From Here...

In this chapter you looked at how to enhance existing controls. You looked at the advantages of using existing controls and how they can be used in our application. You also looked at how to extend ActiveX controls by creating property sheets for your controls. Finally, you looked at how to responsibly create and distribute controls.

The following chapters provide additional coverage on creating ActiveX controls:


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.