Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Web Database Developer's Guide with Visual Basic 5

World Wide Web Database Developer's Guide with Visual Basic 5




-16-

Creating ActiveX Controls for the Web with VB 5

Perhaps the most interesting feature of Visual Basic 5 is the capability to create ActiveX controls. What was once reserved for C++ programmers with a master's degree in Microsoft's Component Object Model now is available to Visual Basic programmers. ActiveX controls are presented in a simplified, object-oriented form with which VB programmers have become familiar. The simplification, however, does not hinder the possibilities available; you can create robust, distributable controls with ease.

This chapter presents a background on the creation of ActiveX controls and starts you on your way to creating custom controls. You will build some simple controls in this chapter that introduce you to the facilities available as a VB programmer.

An Overview and Introduction to the Terminology

Why build ActiveX controls? You might think that there are plenty of control vendors out there. "Why should I build controls when I need to get business-critical work done?" Or, "Hasn't XYZ company already built and tested the do-everything widget?" The answer is no. All programmers have controls they know and love, and they continue to write the same code over and over to get a familiar behavior out of that control. By creating reusable components, you might spend less time implementing common code required to use tools created by others. Creating ActiveX controls is a convenient way to encapsulate common functionality.

Programmers also have controls that can do almost everything they need. Sometimes they sacrifice functionality in an application because their current controls don't provide it. At times, programmers must write ugly looking code to accomplish a task their controls do not provide. Now Visual Basic provides a way for programmers familiar with its object model to access the desired functionality without having to build the entire control from scratch.

Visual Basic 5's new control-creation capabilities are based on the Component Object Model (COM). This component standard enables the controls you create to be fully functional in environments that support COM. You can use ActiveX components in Visual Basic, C++, Delphi, PowerBuilder, ActiveX-capable Internet browsers, and the Microsoft Office suite of applications.

Using ActiveX controls on the Internet/intranet can provide a dynamic diversion from static HTML. You can place ActiveX controls on Web pages to present a more exciting environment.

Variations of ActiveX Components in VB 5

New terminology has come along with Visual Basic's new look and feel. What were once called OLE servers have become ActiveX components. Although the names have changed, the underlying idea is still the same; the notion of the client using some functionality provided by the server still exists. The new terminology allows for further classification of the types of servers (or components) that might be created.

When you start a new project in VB 5, you are presented with the types of projects you can create. Of the component types, you can create code components or interactive components. Code components provide an easy way to package libraries of code for reuse. ActiveX executables and ActiveX DLLs are types of code components. These component types are different in the way in which they are executed. ActiveX executables are out-of-process components; they run in their own address space. You must start an ActiveX executable manually. ActiveX DLLs (formerly known as OLE DLLs) are in-process types of code components; they run in the address space on another application. DLL components cannot operate on their own; other applications or components must initiate the DLLs. An ActiveX control is an example of an interactive component. Like ActiveX DLLs, ActiveX controls run in-process, but they are more suited for interacting with the user or developer. These controls can provide both design-time and runtime behaviors. ActiveX controls (formerly known as OLE controls) compile into .ocx files and work in all environments that support OCXs. This process is called component-based software development. This development can cut production cycles and enable you to assemble robust applications from tested standard objects.

VB 5 Control-Creation Features

Visual Basic has many features dedicated to the authoring of ActiveX controls. This section lists the enhancements to the design environment and the language in the ActiveX author's bag of tricks. IDE Features The Visual Basic design environment provides the features listed in Table 16.1 to help you develop custom ActiveX controls.

Table 16.1. VB 5 IDE Features.

Feature Function
ActiveX Interface Wizard Walks you through adding properties, methods, and events to your control.
Control Designer Works much like the Form Designer; you use it to build the appearance of your control.
Project groups Enables you to open related projects in IDE at one time. Great for testing controls.
Property Page Wizard Walks you through designing and implementing Property pages for your controls.
Setup Wizard Packages your components specifically for deployment on the Web.


Language Features The Visual Basic language has been enhanced to facilitate the construction of ActiveX controls. Some of the language enhancements that may help the control developer are listed in Table 16.2.

Table 16.2. VB 5 Language Features.
Feature Function
Ambient object Provides hints to your control about how to best display itself in the container
Asynchronous downloading Speeds the downloading of a control's properties over the Internet
Enumerations Add enumerated constants to your control just like Visual Basic's built-in constants
Events Enable you to declare, raise, and handle any existing or new events
Extender object Specifies properties or methods provided by the container of your control
Hyperlink Enables your control to specify a URL for an Internet-aware container
Implements statement Offers polymorphism features by enabling your control to provide multiple interfaces
PropertyBag object Holds persistent information to be stored across invocations of your control
PropertyPage object Adds robust dialog boxes for users of your control to adjust its behavior
UserControl object Provides a base on which you can build your control

An Overview of ActiveX Control Development

Normal application development follows a familiar cycle. Here is a simplified version of this cycle:

1. Design the physical interface in Design mode.

2. Modify some properties and add some code.

3. Run the project and test the functionality.

4. Create an executable that runs on its own.

To build ActiveX controls, you must follow a more complex procedure. The process you follow to design and implement the appearance of your control is similar to the one you use to create a standard program. You cannot execute controls directly, however. This requires that you, the control author, also produce an application that tests the features and properly debugs the control.


NOTE: Remember that the idea behind building ActiveX controls is that you will encapsulate some common functionality to be used by other applications. You might need to put some more thought into making your control generic enough to be used by many types of environments. If you properly debug your control, it may be more functional in those environments.

The standard procedure for building most ActiveX controls follows:

1. Determine the look and feel of your control.

2. Decide what type of functionality you want to provide with your control.

3. Design the appearance of your control.

4. Design the public interface (properties, methods, and events).

5. Create the project group with your control project and a test application project.

6. Implement the appearance of your control.

7. Implement the interface of your control.

8. Test each interface or appearance modification.

9. Design and implement your Property pages.

10. Compile your control and test it with all potential target environments.

Examining Methods of Control Creation

You can build an ActiveX control in three ways. You can subclass an existing control, assemble a new control composite from several existing controls, or write your own control from scratch. The method you select depends on the appearance or behavior you want your control to provide and whether existing controls can provide some of that behavior for you. This section examines the methods and looks at some examples that use each method.

Subclassing an Existing Control

Control subclassing is perhaps the easiest method to use when creating an ActiveX control. First, you select and draw the control you want to subclass. You begin with a completely tested control, such as a textbox. Then you decide whether to inherit all its current features or some subset of those features. Next, you add some behavior (property or method) that the existing control does not have. You can even change the default behavior of the events or methods of the subclassed control.

All physical painting behavior of your control on the form is handled internally by Windows and the subclassed object. All you have to worry about is the location of the subclassed control on your ActiveX.


NOTE: You should consider some licensing issues when using other authors' controls. You must have purchased a valid copy of a control in order to use it in controls you create. Just keep in mind that if you use Visual Basic's intrinsic control (built into VB), no additional .ocx files need to be distributed.

Examples of a subclassed control follow:

Assembling a New Control Composite

Another method of creating an ActiveX control is to combine several existing controls into one logical unit. This sort of encapsulation allows for speedy development cycles when common user interface (UI) features are used throughout an application. How many times have you used separate controls for name, address, city, state, and ZIP code in a customer screen, or the user ID/password combination for a logon screen? You can create these types of objects easily as a composite control. Each interface element has been previously tested and debugged. You then are free to concentrate on the functions you want to provide. You are limited only by the huge array of available controls from which to build. You can choose to expose certain properties to the constituent controls, gathering the strengths from each control and masking any of the complexities.

Like the subclassing method, the composite control method is dependent on its constituents. All runtime painting behavior is controlled by the contained controls.

Any properties or methods your composite exposes can map directly to the default behavior of the constituents. Suppose that a user changes the State listbox on your Address control. In the code for the clicked event of the listbox, you can fire off a Changed event in your control.

Examples of a composite control follow:

Building a Control from Scratch

Building a control from scratch offers the most flexibility of all the methods. You must write all the code that handles the appearance of your control. No constituent controls or confines are placed on your control's painting behavior. All drawing must be done by graphics methods or Windows API calls in your control's Paint event. You have ultimate control over the interface (properties, methods, and events) that you provide. This method is recommended if your ActiveX control needs complete authority over its runtime or design-time appearance.

One advantage of building a control from scratch is that it has no dependencies on other controls. You can distribute this control without any licensing issues or added weight of constituent .ocx files.

Examples of controls that are built from scratch follow:

Understanding the UserControl Object

Any ActiveX control you build is based on a UserControl object. Like a form in a standard executable file, a UserControl object is composed of code elements and visual elements. Each object has its own Code window and Visual Design window. You place controls on a UserControl object just as you would any Visual Basic form. The UserControl object is maintained as a plain text file with a .ctl extension. Visual elements that cannot be represented textually are stored in a binary .ctx file.

A project can contain more than one UserControl object. You can package a set of common controls in this manner. After the controls are compiled, they are placed in a single .ocx file. If these controls are made public, they may be used to develop other applications.

Remember that a UserControl cannot execute by itself; it must be placed on a form in order to operate. The form it is placed on is referred to as the container (or parent) of the UserControl. You also can have the control itself serve as a container by placing controls directly on its surface during UserControl authoring or by allowing others to place controls on it during application development.

The interface (properties, methods, and events) exposed by the control is entirely up to you, the author. If the UserControl contains other controls, you can present properties of those constituents to look like properties of the UserControl. You accomplish this task by using delegation. You can delegate a particular interface from one control to the UserControl object itself. You might want an occurrence of a Changed event in a contained textbox to fire the Changed event of the UserControl, for example. To the user, typing in the textbox simply appears to fire the UserControl's Changed event directly. You will see examples of delegation when we map events in "Using the ActiveX Control Interface Wizard," later in this chapter.

Key UserControl Properties

The control author interacts with UserControl properties exactly as if he or she were manipulating form properties. Table 16.3 lists some properties useful in control creation.

Table 16.3. UserControl Properties.

Property Function
Alignable Specifies whether a control can be aligned to the top, bottom, left, or right of its containing object. If so, it can use the extender Align property.
Ambient Returns the AmbientProperties object of the control. This is where the UserControl can access some current properties of the containing object. (See "Working with the Parent Container," later in this chapter.)
CanGetFocus Specifies whether the UserControl can receive the focus.
ContainedControls Returns the collection of controls in the UserControl.
ControlContainer Specifies whether the UserControl can act as a container other controls.
DefaultCancel Specifies whether the UserControl can act as a default or Cancel button for the form. If so, the extender properties Default and Cancel are available.
EditAtDesignTime Specifies whether a control can become active during developer's design-time.
EventsFrozen Specifies whether the container currently is ignoring events raised by the control.
Extender Returns the Extender object of the control. The Extender object holds properties of the control that actually are managed by the container of the control instead of by the control itself. (See "Working with the Parent Container," later in this chapter.)
Hyperlink Returns the Hyperlink object of the control. By using properties and methods of the Hyperlink object, your ActiveX control can request a hyperlink-aware container, such as Microsoft Internet Explorer, to jump to a given URL.
InvisibleAtRuntime Specifies whether the control has a visible representation during runtime.
ParentControls Returns a collection of controls owned by the parent of UserControl.
PropertyPages Specifies a string array containing the names of the Property pages in the project associated with this control.
Public Determines whether the UserControl control can be shared with other applications.
ToolBoxBitmap Specifies the image that will represent the UserControl in the Visual Basic toolbox during design-time. The size of the bitmap should be 16x15 pixels, but the bitmap specified by this property is scaled to these dimensions if necessary.

Key UserControl Methods

Table 16.4 lists some key UserControl methods available to the control author at design-time.

Table 16.4. UserControl Methods.

Method Function
AsyncRead Begins the background process of reading a property value of the UserControl from a file or URL.
CancelAsyncRead Stops a background-download process of a property value.
CanPropertyChange Asks the container whether a UserControl property bound to a data source can have its value modified.
PropertyChanged Indicates that a UserControl property has been modified. This is required to determine whether a control needs to save its properties (in the WriteProperties event) to the containing form file.

Key UserControl Events

Whenever you add a UserControl object that you have authored to a form, a design-time instance is created. At this point, the control is in Run mode. Any behavior that you have put in the UserControl now is active. When you run the form, the design-time instance is destroyed, and a runtime instance is created. As properties are changed at design-time, they are saved to a copy of the form (.frm). Table 16.5 lists the key events in a UserControl's lifetime.

Table 16.5. UserControl Events.

Event Description
AmbientChanged Occurs after one of the Ambient property values is changed in the container.
AsyncReadComplete Occurs when a specific property finishes downloading.
Initialize Occurs when an instance of the control is created.
InitProperties Occurs once when an instance of a control is added to a form. This is where you can supply default values for a control's properties.
Paint Fires whenever the container instructs the control to itself.
ReadProperties Occurs after the control is initialized. This is where persistent property values are set from the containing form file (.frm).
Resize Occurs after the control is initialized and every time it is resized--whether in Design mode or at runtime.
Show Called before the container begins to display itself.
Terminate Occurs after the control is destroyed.
WriteProperties Occurs when a design-time instance of the control is being destroyed and at least one property has changed. The properties are written to the containing form file.

Working with the Parent Container

This section explores how the UserControl object can communicate with its environment--most often, the form on which it is sitting. As a control author, you can access the containing object through the read-only Parent and ParentControls properties. The ParentControls property is a collection that is useful when performing some actions on the controls on the form. This property allows iteration through these controls.


NOTE: Remember that the control's container is not always a form. Many other types of containers exist, such as tabs, picture boxes, and frames. Do not count on the container object having all the properties or methods of a standard form when accessing the Parent properties.

In addition to the Parent properties, the Extender object and AmbientProperties objects enable you to interact with the container.

Extender Object

The containing object best handles some of a control's interface. The Top and Left properties, for example, determine where on the form your control is placed. This type of container- specific information is supplied to the ActiveX author by means of the Extender object. This object supplies properties, methods, and events that do not need to be implemented by your UserControl.


NOTE: The Extender object is not available when the Initialize event is raised, but it is available when the InitProperties event or ReadProperties event is raised. These properties are said to be late bound.

Table 16.6 lists the set of Extender properties implemented by all containers.

Table 16.6. Extender Properties.

Property Function
Default, Cancel Determines whether the control behaves as a default or Cancel button on the form
Name Specifies the user-defined name of the control
Enabled Specifies whether the control has been enabled
HelpContextID Specifies a numeric reference used for Help lookups
Index Keeps track of control-array instances
Left, Top, Width,
Height Determines the location on the Parent container
Parent Specifies an object that represents the container of the control, such as a form
Visible Determines whether the control is visible at runtime
ToolTipText Optional text pop-up hints


One example of a common use of an Extender property is to help supply default text for a contained TextBox control. Suppose that your control is called TextWidget. When an instance of this control is added to a form, it has a default name supplied for it, such as TextWidget1. This widget may have a subclassed textbox contained in it. One common behavior is to make the default Text property of a textbox the same as the name of the control. You can accomplish this easily by using the Extender.Name property in the InitProperties event, as this example shows:

Private Sub UserControl_InitProperties()
   Text = Extender.Name
End Sub

Public Property Text(Byval sNewVal As String)
   txtBase.Text = sNewVal
End Sub

When a TextWidget control is added to a form, its Text property is set to its name--for example, TextWidget1 and TextWidget2.


NOTE: Remember that the InitProperties event is fired only once for each control. It occurs after the control instance is added to the form. So when you change the name of the control, the Text property in the TextWidget example is not altered.

AmbientProperties Object

Containers provide hints to your UserControl about how they should best display themselves. These hints enable the UserControl to provide an appearance and behavior that is consistent with the container. An example is the Ambient Font property. Ambient.Font references the font that the containing form is using. For your control to appear similar to the form, you should use the same font.

Like the Extender properties, the Ambient properties also are specific for each container object. Several standard properties should be implemented by all containers. Table 16.7 lists some of the key Ambient properties.

Table 16.7. Ambient Properties.

Property Function
DisplayName Specifies the user-defined name of the control. This property is identical to the Extender.Name property.
Font, TextAlign Specifies the current font settings for the container.
ForeColor, Specifies the colors your control can use to keep
BackColor, Palette it consistent with the container.
UserMode Determines when the instance of the control is executingUserMode is false during design-time and true during runtime.

WARNING: A control can access Ambient properties that are specific to a particular container, but this limits its use to containers that have that property. When the control is compiled, it has no way of knowing in what type of container it may be placed. Be aware that, in order to use non-standard properties, the control should handle any errors in cases where the Ambient property does not exist.

Perhaps the most useful Ambient property is the UserMode property. UserMode offers a higher level of control over the design-time and runtime behaviors your control provides. Suppose that you want to change the way your control handles Resize events during design-time. Examine the following code:

Private Sub UserControl_Resize()

    `-Do not allow sizing of the control design time
    `  Keep the control the size of the contained TextBox
    If Not Ambient.UserMode Then
        Height = txtBase.Height
        Width =  txtBase.Width

    End if
End Sub

The Resize event of a UserControl can fire at design-time or runtime. This code allows dynamic runtime changes to the control's appearance, but it restricts the size to the constituent TextBox dimensions at design-time.

Building an ActiveX Control

Now you'll start to build a simple control. What should you build? They say that necessity is the mother of invention, and ActiveX development is a perfect example of this. In the last application I worked on, it was proclaimed that all textual input from the user was to be uppercase. Simple enough, right? Did I mention that all 20 or so previously built forms needed this new behavior? That meant that whenever a KeyPress event occurred in any of the forms, I had to convert that key to an uppercase character. Each form's KeyPreview property was turned on, a piece of common code was checked if the active control was a TextBox, and then the key press was converted. Accommodating, but repetitive and very ugly! If the TextBox control only had a Case property, I could set this at design-time and leave it up to the control to specify lowercase, uppercase, or whatever.

Never fear--ActiveX control creation is here. I simply could subclass the TextBox control and add the Case property to it. This is the background for the NewText control that follows. You will use only one control to build it (a TextBox), and you will implement the features described so far in this chapter.

Creating a New Control Project

Before you can begin work on a new control, you must set up the required project files. Follow these steps:

1. Choose File | New Project. In the New Project dialog box that appears, double-click the ActiveX control icon to create a new project. The ActiveX Control Designer and default project are created for you, as Figure 16.1 shows.

2. Rename Project1 by right-clicking it in the Project window and choosing Properties. In the Properties dialog box that appears, change Project1 to NewTextControl and click OK.

3. Double-click UserControl1 in the Project window and edit its Name property in the Properties window. Change it to NewText.

WARNING: Make sure that you name your control appropriately. Each time someone adds your control to his form, a unique name is generated. An incremental number is appended to the name by default. An ActiveX control with the name UserControl1 at design-time, for example, becomes UserControl11 when added to a form.

FIGURE 16.1. Newly created UserControl1 and Project1.

4. Add a test project to the group. Choose File | Add Project. Select Standard Exe from the list of available project types and press OK. A Form1 is added to this project.

5. Rename Form1 to frmTest and Project1 to NewTextTester.

6. Save all files by using Table 16.8 as a guide.

Table 16.8. Objects and Their Associated Filenames.

Object Filename
NewTextControl project NewTextControl.vbp
NewText control NewText.ctl
NewTextTester project NewTextTester.vbp
FrmTest form Test.frm
NewText project group NewText.vbg


The project group and associated project files now are all set and should look similar to those in Figure 16.2.

FIGURE 16.2. The NewText project group.

Adding a UserControl to the Test Application

As you develop features in your UserControl, it helps to see the immediate results. Visual Basic programmers have become used to this familiar code-run-debug cycle. The UserControl code begins to run as soon as you place an instance on the test form. You can step through the Initialize or InitProperties just as you would the Load event of a standard form.

To add the current NewText control to the test form, follow these steps:

1. Double-click the area of UserControl to display the Code window. Add the following code to the UserControl_Initialize and UserControl_InitProperties events:
Private Sub UserControl_Initialize()
    Debug.Print "A NewText control was Initialized, but I do not know its name"
End Sub

Private Sub UserControl_InitProperties()
    Debug.Print "A NewText control called "& Ambient.DisplayName & "has been added"
End Sub
2. Close the Code window for the NewText control and close the NewText Design window (if it is open). The default icon for a user control appears in the toolbox.

3. Double-click frmTest in the Project window. The Form Designer appears.

4. Click the NewText icon in the toolbox. A set of cross hairs appears when the mouse cursor is over the test form.

5. Click-and-drag on the test form to create a copy of the NewText control on the form. This control appears as a flat, transparent rectangle with sizing handles.

NOTE: If you look at the Properties window, you will see the default set of properties with which each UserControl begins. The Extender object of the control provides these properties. These properties appear to the user to be seamlessly integrated.
6. Double-click the NewText UserControl object in the Project window to bring the NewText Designer to the front. Notice that the instance of NewText1 on the test form has been filled with hatch marks to indicate that it is inactive, as Figure 16.3 shows. You can resize the UserControl on the test form; however, when it is inactive, no UserControl events will occur.

FIGURE 16.3. The NewText instance is deactivated during design-time.


NOTE: Opening any object (a Code window or a Control Designer) in a control project deactivates all running instances of the control. When switching from different projects in a grouped project such as this, you are prompted to close Control Designers and Code windows that are open.

Adding Constituent Controls

Your control still does not have a function, so now you'll add a constituent control using the Designer Form. Follow these steps:

1. Double-click the icon for the TextBox control in the toolbox to add a TextBox to the UserControl. The UserControl now looks similar to Figure 16.4.

FIGURE 16.4. Adding a textbox to the NewText control.

2. Name the textbox txtBase. This is the control on which you will base all your subclassing.

3. Double-click any blank area on UserControl to bring up the Code window. Add the following lines to the UserControl_Resize event to ensure that the TextBox control always is resized to fill the entire UserControl area:
Private Sub UserControl_Resize()
     `-Force the TextBox control to fill the NewText's
     `  visible surface area
     txtBase.Move 0, 0, ScaleWidth, ScaleHeight
End Sub
4. Add the following lines to the InitProperties event:
    `--Default the TextBox to show the name
    `  of the control
    txtBase.Text = Extender.Name
5. Close the NewText Code window and the Designer to put the control into Run mode. Bring frmTest to the front by double-clicking it in the Project window.

6. The NewText control now appears as a TextBox on the form. Test the Resize event code by resizing the control at design-time. Add new controls to test the InitProperties code you changed, as shown in Figure 16.5.

FIGURE 16.5. Effects of the InitProperties event.


NOTE: The NewText control originally added to your test form does not show its name, because the InitProperties event for that control fired before you added that code. InitProperties is executed only once when the control is added to the container. Also, you have yet to tell your control how to save property values from one design invocation to the next. The next time you open the test form, the InitProperties event does not fire and your NewText boxes are empty.

Using the ActiveX Control Interface Wizard

Now that you have placed the required appearance elements on your NewText control, you need to define the public interface it exposes to the developer using it. To do this, you use an add-in that ships with Visual Basic called the ActiveX Control Interface Wizard. This wizard simplifies the building of subclassed or composite type controls by enabling you to use interface elements (also called members) from the constituent objects used to build your control. The members that you decide to expose make up your new control's interface.

You can use the ActiveX Control Interface Wizard to perform these tasks:

The ActiveX Control Interface Wizard presents the following sections:

To use the ActiveX Control Interface Wizard, you must add it to the development environment. Follow these steps to add and execute the Interface Wizard:

1. Choose Add-Ins | Add-In Manager. Click in the checkbox to the left of VB ActiveX Control Interface Wizard to enable it, as shown in Figure 16.6. Also enable VB Property Page Wizard. You will use this wizard in a later section, "Adding Property Pages," to design Property pages for your control. Click OK to close the Add-In Manager.

FIGURE 16.6. The Visual Basic Add-In Manager.

2. The VB ActiveX Control Interface Wizard option now is visible at the bottom of the Add-Ins menu. Click that option to launch the wizard.
The wizard introduction screen appears, giving you a quick overview of the process to follow. It then notes that you already should have added all the design elements (UI objects) to your control prior to running the wizard. The wizard inspects those controls and provides their properties, methods, and events for you to pick from in the next step.

3. Enable the Skip this screen in the future checkbox to bypass the introduction screen on successive uses of the wizard; then click Next to move to the next step.
The Select Interface Members screen appears, as shown in Figure 16.7. The wizard has inspected the public interface of all the objects in your UserControl. A list of all these available interface members appears in the Available names box. A list of the selected members your control is to expose appears in the Selected names box. The wizard preselects the set of properties, methods, and events common to most controls.

FIGURE 16.7. The Select Interface Members step.

4. Add the following interface members for the Change event and the Text property to the Selected names list. Remove the BackStyle property from the list, because your TextBox will fill the entire UserControl. Click Next to advance to the next step of the wizard.

NOTE: In order for your control to provide all the functionality of a common TextBox control, you would have needed to select additional interface members. Properties like PasswordChar and MultiLine would give your control the look and feel consistent with the built-in TextBox.
5. The Create Custom Interface Members screen appears next. Here, you add any additional properties, methods, or events to the control. You will add a property called TextCase to this control. Click New to add a new member. You are then presented with the Add Custom Member dialog box. Type TextCase in the Name field and click OK. The wizard then shows this new member in the My Custom Members list, as Figure 16.8 shows. Click Next to continue.

FIGURE 16.8. Adding a custom interface member.

6. The Set Mapping screen appears next, as shown in Figure 16.9. This step enables you to map the functionality of the public property, method, and event names to underlying private property, method, and event names on the user control and constituent controls. Select each member in the Public Name list and then choose a control in the Maps to Control drop-down listbox. If a member of the same name exists, it is filled in the Maps to Member drop-down listbox. After you map the public KeyDown event to the KeyDown in your base TextBox, for example, any occurrence of a KeyDown in the TextBox fires a KeyDown in the NewText control. You do not need to map custom functions because you will be implementing those yourself.

TIP: You can map multiple public members to one control at a time. To do this, click the public names you want while pressing Ctrl. Then choose the control you want.
7. Map all public names to the TextBox control with the exception of your new TextCase property. Then click Next.

FIGURE 16.9. The Set Mapping wizard step.

8. Now you need to set the attributes of the new TextCase property. The Set Attributes screen shown in Figure 16.10 enables you to set the attributes for any property, method, and event names that you did not map in step 7. Here, you can select the data type, default value, and behavior of the control at runtime or design-time. You will add an Enumerated type to use with this property, so you can accept the default settings and exit this screen. Fill in the Description field to show a description in the Property window. Then click Next.

FIGURE 16.10. The Set Attributes wizard step.

9. The Finish screen appears, explaining that it has gathered all the required information to create your control's interface. It also enables you to view a summary report. The summary is simply a to-do list you can follow to complete modifications to your control. You can save this report to a text file for later inspection.

When the wizard is finished, it generates all the code required to implement the selections you made. Listing 16.1 displays the code generated from these steps. Various comments are added with warning messages. These comments help the wizard remember the choices you made in previous visits.

Listing 16.1. Code Created by the Interface Wizard.

`Default Property Values:
Const m_def_TextCase = 0
`Property Variables:
Dim m_TextCase As Variant
`Event Declarations:
Event Click() `MappingInfo=txtBase,txtBase,-1,Click
Event DblClick() `MappingInfo=txtBase,txtBase,-1,DblClick
Event KeyDown(KeyCode As Integer, Shift As Integer) `MappingInfo=txtBase,txtBase, Â-1,KeyDown
Event KeyPress(KeyAscii As Integer) `MappingInfo=txtBase,txtBase,-1,KeyPress
Event KeyUp(KeyCode As Integer, Shift As Integer) `MappingInfo=txtBase,txtBase, Â-1,KeyUp
Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) `MappingInfo=txtBase,txtBase,-1,MouseDown
Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) `MappingInfo=txtBase,txtBase,-1,MouseMove
Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) `MappingInfo=txtBase,txtBase,-1,MouseUp
Event Change() `MappingInfo=txtBase,txtBase,-1,Change


`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,BackColor
Public Property Get BackColor() As OLE_COLOR
    BackColor = txtBase.BackColor
End Property

Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)
    txtBase.BackColor() = New_BackColor
    PropertyChanged "BackColor"
End Property

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,ForeColor
Public Property Get ForeColor() As OLE_COLOR
    ForeColor = txtBase.ForeColor
End Property

Public Property Let ForeColor(ByVal New_ForeColor As OLE_COLOR)
    txtBase.ForeColor() = New_ForeColor
    PropertyChanged "ForeColor"
End Property

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,Enabled
Public Property Get Enabled() As Boolean
    Enabled = txtBase.Enabled
End Property

Public Property Let Enabled(ByVal New_Enabled As Boolean)
    txtBase.Enabled() = New_Enabled
    PropertyChanged "Enabled"
End Property

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,Font
Public Property Get Font() As Font
    Set Font = txtBase.Font
End Property

Public Property Set Font(ByVal New_Font As Font)
    Set txtBase.Font = New_Font
    PropertyChanged "Font"
End Property

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,Refresh
Public Sub Refresh()
    txtBase.Refresh
End Sub

Private Sub txtBase_Click()
    RaiseEvent Click
End Sub

Private Sub txtBase_DblClick()
    RaiseEvent DblClick
End Sub

Private Sub txtBase_KeyDown(KeyCode As Integer, Shift As Integer)
    RaiseEvent KeyDown(KeyCode, Shift)
End Sub

Private Sub txtBase_KeyPress(KeyAscii As Integer)
    RaiseEvent KeyPress(KeyAscii)
End Sub

Private Sub txtBase_KeyUp(KeyCode As Integer, Shift As Integer)
    RaiseEvent KeyUp(KeyCode, Shift)
End Sub

Private Sub txtBase_MouseDown(Button As Integer, Shift As Integer, X As Single,  ÂY As Single)
    RaiseEvent MouseDown(Button, Shift, X, Y)
End Sub

Private Sub txtBase_MouseMove(Button As Integer, Shift As Integer, X As Single,  ÂY As Single)
    RaiseEvent MouseMove(Button, Shift, X, Y)
End Sub

Private Sub txtBase_MouseUp(Button As Integer, Shift As Integer, X As Single,  ÂY As Single)
    RaiseEvent MouseUp(Button, Shift, X, Y)
End Sub

Private Sub txtBase_Change()
    RaiseEvent Change
End Sub

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=txtBase,txtBase,-1,Text
Public Property Get Text() As String
    Text = txtBase.Text
End Property

Public Property Let Text(ByVal New_Text As String)
    txtBase.Text() = New_Text
    PropertyChanged "Text"
End Property

Public Property Get TextCase() As Variant
    TextCase = m_TextCase
End Property

Public Property Let TextCase(ByVal New_TextCase As Variant)
    m_TextCase = New_TextCase
    PropertyChanged "TextCase"
End Property

`Initialize Properties for User Control
Private Sub UserControl_InitProperties()
    m_TextCase = m_def_TextCase
End Sub

`Load property values from storage
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)

    txtBase.BackColor = PropBag.ReadProperty("BackColor", &H80000005)
    txtBase.ForeColor = PropBag.ReadProperty("ForeColor", &H80000008)
    txtBase.Enabled = PropBag.ReadProperty("Enabled", True)
    Set Font = PropBag.ReadProperty("Font", Ambient.Font)
    txtBase.Text = PropBag.ReadProperty("Text", "")
    m_TextCase = PropBag.ReadProperty("TextCase", m_def_TextCase)
End Sub

`Write property values to storage
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)

    Call PropBag.WriteProperty("BackColor", txtBase.BackColor, &H80000005)
    Call PropBag.WriteProperty("ForeColor", txtBase.ForeColor, &H80000008)
    Call PropBag.WriteProperty("Enabled", txtBase.Enabled, True)
    Call PropBag.WriteProperty("Font", Font, Ambient.Font)
    Call PropBag.WriteProperty("Text", txtBase.Text, "")
    Call PropBag.WriteProperty("TextCase", m_TextCase, m_def_TextCase)
End Sub

Examine the code generated by the Interface Wizard. Notice that each event is declared near the beginning. These events and the events provided by the Extender object are available to the user of this control at design-time. Following the events is a set of Let and Get procedures for each added property. Also notice the delegation of events from the TextBox control to the UserControl's events declared earlier. This is accomplished by the RaiseEvent function. Finally, note the ReadProperty and WriteProperty events. Here, you can see the procedure for saving and reading persistent property values. You should use the code generated by the wizard as a model if you create controls from scratch or add any interface members to a control.

Modifying the Controls Code

Your NewText control still requires a little work to be functional. The first item on your list is to set up the types of cases (uppercase, mixed case, and so on) your NewText control will handle. You need the NewText control to handle normal upper- and lowercase as well as mixed-case letters if you want to leave the case as the user enters it. You will make mixed case (or no case distinction) the default for this property. One last case would be useful--one that capitalizes the first character of each word; this type of case commonly is called initial caps.

To save the user of your control from having to remember any codes for these types, you will create an enumeration. The enumeration will hold all the possible TextCase values. Your Enum statement for those cases looks like this:

`-- Define an enumerated type for the
`  new TextCase property
Public Enum Cases
    MixedCase = 0
    UpperCase = 1
    LowerCase = 2
    InitialCaps = 3
End Enum

You then can replace the private member variables and the default values generated by the wizard from this:

`Default Property Values:
Const m_def_TextCase = 0
`Property Variables:
Dim m_TextCase As Variant

to this:

`Default Property Values:
Const m_def_TextCase = MixedCase

`Property Variables:
Dim m_TextCase As Cases

You then can replace the standard property Let and Get procedure stubs that were generated by the wizard with the two functions in Listing 16.2.

Listing 16.2. Modified Property Let and Get Procedures for TextCase.

Public Property Get TextCase() As Cases
    TextCase = m_TextCase
End Property

Public Property Let TextCase(ByVal New_TextCase As Cases)

    Select Case New_TextCase
        Case MixedCase
        Case UpperCase
            Text = UCase$(Text)
        Case LowerCase
            Text = LCase$(Text)
        Case InitialCaps
            Text = InitialUCase(Text)
        Case Else
            MsgBox "Invalid Property Value: " & New_TextCase
            Exit Property
    End Select
    m_TextCase = New_TextCase
    PropertyChanged "TextCase"
End Property

As you can see, the TextCase Let procedure now accepts a parameter of type Cases, which you defined with your Enum statement. For mixed case, you do not care what the case is in the Text field, so no code is needed. For uppercase and lowercase, you can use the appropriate Visual Basic functions. Next, you must add a function for handling your initial caps type of case. Listing 16.3 shows the code you need to convert any string to initial caps.

Listing 16.3. Converting Strings to Initial caps.

Private Function InitialUCase(ByVal sInText As String) As String
`-- convert the incoming string to initial caps
Dim sOutText As String
Dim i As Integer
Dim sCurrChar As String
Dim sLastChar As String

    `-- Loop through the input string and add each
    `  character to the output string
    For i = 1 To Len(sInText)

        `-- get the current character
        sCurrChar = Mid$(sInText, i, 1)

        If sLastChar = " " Or sLastChar = "" Then
            `-- Force uppercase if it is a new word
sOutText = sOutText & UCase$(sCurrChar)
        Else
            sOutText = sOutText & LCase$(sCurrChar)
        End If

        `-- save the last character
        sLastChar = sCurrChar

    Next
    `-- return the result
    InitialUCase = sOutText
End Function

The last enhancement you need to make before you begin to test your control is to change the way you set your default Text property. In the section "Adding Constituent Controls," you added code to set the Text property of the txtBase control to default to the name of the control. You did this in the InitProperties event. Now, through delegation, you are directing the contents of your txtBase control to the public Text property of NewText. Your line in the InitProperties event now should look like this:

Text = Extender.Name

Text in this line references the NewText.Text property.

You also should make sure that your properties are being written to the form file correctly. Between coding and debugging sessions, you need to make sure that properties in your control are saved to the plain text file of the containing form. The events that handle property persistence across sessions are WriteProperties and ReadProperties. These events both manipulate an object called the PropertyBag. The PropertyBag stores the control's property values in the containing form's file. When the form is read into memory, those data values also are read. When the developer changes properties, a flag is set to alert the WriteProperties event procedure to fire, thereby writing those changes.


NOTE: It is the responsibility of the control author to ensure that all properties enable procedures to signal that a modification has been made. The PropertyChange method alerts the system that it needs to save a particular property value.

Modify the ReadProperties event so that the Text property defaults to the control's name:

txtBase.Text = PropBag.ReadProperty("Text", Extender.Name)

Set the same default value when the PropertyBag is written to the file in the WriteProperties event:

Call PropBag.WriteProperty("Text", txtBase.Text, Extender.Name)

Now your default text value should work and be persistent across development sessions. You now should test the functionality you added.

Testing the Control

To test the functionality of your control, you need to revisit your test project and update it to use the new features of NewText. Follow these steps to enhance the test form:

1. Close the Design window and the Code window for the NewText control. Double-click frmTest in the Project window to bring it to the front.

2. Remove all instances of your NewText control so that it is a blank form. Double-click the NewText control in the toolbox. This adds a new instance of NewText to your form. The name of the control should appear in the NewText box. Resize the control to the appropriate size to hold one line of text.

3. Examine the Property window. A new property should be listed for TextCase. The default value (MixedCase) is visible. Notice that a description should be listed below the Property window stating the purpose of the TextCase property. Click the uppercase type from the combo box. If all is well, expect to see the text in NewText1 in all uppercase.

4. Close the form and reopen it. ReadProperties and WriteProperties should have ensured that the changes to any property values persisted.

You now have tested the behavior of the control at design-time. In order to test the behavior at runtime, you must add additional support on your test form. Follow these steps:

1. Add a control array of four option buttons called optCase to the form. Set the following properties:
Object Property Value
optCase(0) Caption Mixed case (default)
Value True
optCase(1) Caption Uppercase
optCase(2) Caption Lowercase
optCase(3) Caption Initial caps
2. Double-click one of the buttons. This brings up the Code window. Add the following code to the optBase_Click event:
Private Sub optCase_Click(Index As Integer)

    Select Case Index
        Case 0
            NewText1.TextCase = MixedCase
        Case 1
            NewText1.TextCase = UpperCase
        Case 2
            NewText1.TextCase = LowerCase
        Case 3
            NewText1.TextCase = InitialCaps
    End Select

End Sub
3. Add the following code to the KeyPress event of the textbox so that text entered into the NewText control at runtime follows the case guidelines you have set:
Private Sub txtBase_KeyPress(KeyAscii As Integer)

Dim nLoc As Integer

    `-- Check to see whether a letter was pressed
    If UCase(Chr$(KeyAscii)) <= "Z" And UCase$(Chr$(KeyAscii)) <= "Z" Then
        Select Case TextCase
            `-- Mixed case allows any combination of cases
            Case MixedCase

            `-- force the character to uppercase
            Case UpperCase
                KeyAscii = Asc(UCase$(Chr$(KeyAscii)))

            `-- force the character to lowercase
            Case LowerCase
                KeyAscii = Asc(LCase$(Chr$(KeyAscii)))

            `-- force the character to upper or lower
            `  based on what the previous character was
            Case InitialCaps
                `-- Sel start tells us where the caret is
                nLoc = txtBase.SelStart

                `-- if we are at the beginning of a word
                `  force this character UP
                If nLoc = 0 Or Trim$(txtBase.Text) = "" Then
                    KeyAscii = Asc(UCase$(Chr$(KeyAscii)))
                Else
                    `-- if the last character was a space
                    ` force it UP
                    If Mid$(txtBase.Text, nLoc, 1) = " " Then
                        KeyAscii = Asc(UCase$(Chr$(KeyAscii)))

                    Else `-- force it down
                        KeyAscii = Asc(LCase$(Chr$(KeyAscii)))
                    End If

                End If
        End Select

    End If

    `Send the Keypress event on to the NewText control
    RaiseEvent KeyPress(KeyAscii)
End Sub
4. Run the project by clicking the Start button or by choosing Run | Start. The test form should look similar to Figure 16.11.

FIGURE 16.11.The NewText test application in action.

Adding Property Pages

After you complete all the properties you want for your control, you can choose to implement Property pages. Property pages enable you to represent more complex data and property data types that are not easily modified in Visual Basic's Property window. Although your NewText control is small, you will build a simple Property page to see how easy it is. To get started designing Property pages for your control, you will run the Property Page Wizard. This wizard offers a simple approach to Property pages and introduces you to the procedure.

To add a Property page, follow these steps:

1. Start the Property Page Wizard by clicking the appropriate option on the Add-Ins menu (you added this option to the Add-Ins menu while you were adding the ActiveX Interface Wizard). The introductory screen appears first. Enable the Skip this screen in the future checkbox and click Next to continue to the next step.

2. The next screen that appears is Select the Property Pages. If your user control exposes properties of type OLE_COLOR, Font, or Picture, default Property pages are available. Click Add, and a Property page called ppgGeneral appears. By clicking in each box, enable the checkboxes next to the default pages that you want to be available. Arrange the pages by clicking the up- and down-arrows to move the selected pages. The wizard then should look similar to Figure 16.12. Click Next.

FIGURE 16.12. Selecting the Property pages.

3. The wizard next presents the Add Properties screen, shown in Figure 16.13. Here you select the properties that you want to display on each page. The Available Properties box is filled with properties the wizard knows how to implement. You cannot modify the default pages for color and font. Drag each property in the Available Properties box to the ppgGeneral page tab. Then click Next.

FIGURE 16.13. The Add Properties screen.

4. The Finish screen appears. The Property Page Wizard has added one Property page file to your project. Click Finish to exit the wizard. The newly created Property page now is visible.

NOTE: When designing Property pages, you do not have to provide tabs or an OK, Cancel, or Apply button. These are provided for your Property Page dialog box. You must, however, provide the name of each tab for non-standard pages.
5. Select the Caption property in the page's Property window. Change the caption to General. Add the same option buttons that you added to the test application and reposition the other controls so that the Property page looks similar to Figure 16.14.

FIGURE 16.14.The General Property page.

Now that your Property page design elements are placed, you must integrate them with your control. The SelectedControls collection of the PropertyPage object enables you to interact with the control. When a Property page is opened during design-time, at least one control must have been selected. You will use the SelectedControls(0) element to reference the first control selected. The Changed property of the PropertyPage object signals the availability of the Apply button contained in the PropertyPage dialog box.

The key events of a Property page object are the SelectionChanged and ApplyChanges events. When a developer selects different controls, you should update the values in your Property pages. Place code in the SelectionChanged event to examine the selected controls collection. When changes are made on the Property pages, you must enable the Apply button by setting Changed to True. After the Apply button is clicked, the ApplyChanges event is fired. Place code here to update all the selected controls with the correct values from the Property page controls.

You need to make the following modifications to your General Property page:

1. Add the following code to enable the Apply button after one of your optCase buttons is clicked:
Private m_nTextCase As Integer

Private Sub optCase_Click(Index As Integer)
    m_nTextCase = Index
    Changed = True
End Sub
2. Add the following line to the SelectionChanged event. When the user selects a different NewText control, the properties of that control are displayed on the General tab.
optCase(SelectedControls(0).TextCase).Value = true
3. Add the next line to the ApplyChanges event. This ensures that after the user clicks the Apply button, the values in the Case option are updated.
optCases(SelectedControls(0).TextCase).Value = true

NOTE: This example only saves changes to the first control selected (SelectedControls(0)). You must loop through the selected controls collection if you want to apply the same property value to multiple controls.
4. Close the Designers and Code windows on the Property page. Access the test application by double-clicking frmTest in the Project window. Right-click a NewText control in the test form to show the context menu, and choose Properties
.
5. Test the functionality of the Property page you created. Figure 16.15 shows the completed Property pages in action.

FIGURE 16.15. NewText Property pages.

Using Compile Options

You can compile an ActiveX control separately or include it in a project. You can use public controls outside of Visual Basic in environments that support .ocx files. Private controls are stored in .ctl files and must be added to and compiled into the respective projects.

The Project Properties dialog box offers several compile options for creating a public component. Require License Key If you are compiling a component that you want to distribute to other developers or Web servers, choose the Require License Key option. When the project is built, Visual Basic creates a license file (.vbl). For a developer or Website to use your control, the .vbl file must be registered on that machine. The Setup Wizard helps you build the proper setup program to accomplish this registration. If you require the license key and someone uses your control without the authorized setup program, he will not be able to use your control for development. P-Code Versus Native Code Another option available when compiling your component is whether to compile using p-code (interpreted) or native code. For controls designed for Web page development, you probably will want to compile using p-code. This method usually produces a smaller .ocx than using native code. Compiling the NewText control with p-code, for example, creates a 26KB .ocx file. Compiling the same control using native code using the Optimize for Small Code option results in a 33KB .ocx file. Web developers usually prefer the speed of downloading instead of an unnoticeable degree of speed efficiency with native code. Which option you decide to use depends on your particular situation. Versioning The Component tab of the Project Properties dialog box also offers options for version compatibility. This enables you to control the compatibility of your component with earlier versions and applications that use those versions. The compatibility options for a control project follow:

To compile your NewText control, choose File | Make NewText.ocx. Click the Options button in the Make Project dialog box to display the Project Property pages. Here, you may increment the version number of the control or set it to increment automatically on each compile. Other options include setting the company name, project name, copyright, and trademark information that will be visible when a user is viewing your control from within Windows Explorer.

You now are ready to start building a real application with your control.

Looking at Internet Features

Most often, ActiveX controls are placed on forms to capture some information from the user or to offer some dazzling effect. The same is true on Internet or intranet Web pages. ActiveX controls created in VB 5 are lightweight and can be used to spruce up any static Web page. The client, however, must have a ActiveX-capable browser or an appropriate plug-in to view and interact with the ActiveX controls. When placed on a Web page, an ActiveX control should offer the same behavior as it does on a form.

Internet features are available when the controls are placed in an Internet-aware container, such as Microsoft's Internet Explorer.

Asynchronous Downloading

Your control can contain images or other large amounts of data. If so, you might choose to download this information in the background. To see an example of this process, look at most Web pages developed today. When a browser begins loading a page for view and encounters an HTML image tag, it spawns a background process to load the image and then continues to read the rest of the inline text.

Controls built in Visual Basic can download large property values (such as images) asynchronously. The control can begin a background loading process by calling its own AsyncRead method. The AsyncRead method has the following syntax:

AsyncRead Location, Type, Property

The Location argument is a string that determines the path or address of the data or picture. This can be any valid URL, such as this:

file://c:\Multimedia files\logo.gif

or this:

http://www.kscsinc.com/CardShop/images/activex.gif

The Type argument specifies one of three types of data that can be downloaded:

The Property argument is a string that specifies the name of the property that will receive this data. An example using the AsyncRead method follows:

Private Sub cmdGetPic_click()
    `--Download the picture from the URL specified
    `  by txtURL
    cmdCancel.Enabled = True
    AsyncRead txtURL.Text, vbAsyncPicture, "Picture"
End Sub

Downloading the control might call AsyncCancelRead at any time to stop the process, though. It takes a property name string as an argument and only stops the download for that particular property. The following code illustrates how to use a Cancel button to stop a transfer:

Private Sub cmdCancel_click()
    CancelAsyncRead "Picture"
End Sub

When each property download is complete, the AsyncReadComplete event is fired. In the event, an AsyncProperty object is specified. This object contains the following properties:

The following code fragment provides an example of how you might use a property after a download has been completed:

Private Sub UserControl_AsyncReadComplete(AsyncProp As VB.AsyncProperty)
On Error goto err_AsynComplete

    Select Case AsyncProp.PropertyName
       Case "Picture"
          Set Picture = AsyncProp.Value
    End Select

err_AsyncComplete:
   MsgBox Err.Number & ": " & Err.Description & " occurred in " & Ambient.DisplayName & ".AsyncReadComplete."
End Sub

NOTE: Be aware that some type of error might occur during transmission of the property data, thus stopping the download. If so, that error is raised when the Value property of the AsyncProperty object is accessed. Proper error-handling code is required to address this issue.

The Hyperlink Object

By using the Hyperlink object, a control can take over a hyperlink-aware container. The control can then force the container to jump to a specific URL or navigate backward or forward through its history list. The Hyperlink object contains these three methods:

Private Sub UserControl_Click()
    `-Go to the Virginia's weather page
    Hyperlink.NavigateTo "http://www.weather.com", "va.htm"
End Sub

If the container is not hyperlink-aware, the application registered on the client machine, such as a browser, is launched. Errors are raised if no entries are in the history list or the specified URL or document is not available.

Internet Component Download Features

In order for controls to be used with Web pages, they first must be downloaded to the client machine. It is the browser's responsibility to copy the required files and install the components only if they do not currently exist. Several Internet component download features follow:

You can find more detailed information on the Internet component download facilities in Chapter 17, "Distributing Active X Documents and Controls via the Web Using VB 5."

Introducing the DatePicker Control

Now that you see how easy it is to add functionality to a TextBox control, you probably have plenty of ideas about the types of controls you want to build. Because this new frontier for Visual Basic is so broad, this section provides another example of a type of control you can create. It is a composite control that contains features of a date selection box. You can use it simply to enter a date into its constituent textbox, or you can click the drop-down arrow and select from a scrollable calendar, as the test application in Figure 16.16 shows.

FIGURE 16.16 The DatePicker control example in action.

The DatePicker control is a slim 35KB (a download screamer). It was built using only a TextBox control and a handful of PictureBox controls. The calendar display was performed simply by using Print statements directly to one of the PictureBox controls. It is simplistic by design, yet functional. Examining the source code for the DatePicker control may help you learn more of the options now available to a Visual Basic ActiveX control designer.

Summary

This concludes the discussion of building ActiveX controls in Visual Basic 5. Although we have covered only a portion of the features and capabilities available in control development with VB 5, you should now have a basic understanding of the concepts involved. A more detailed discussion of ActiveX development could have filled an entire book this size.

The simple controls created in this chapter make use of wizard shortcuts provided by the developers at Microsoft. These wizards offer a fast way to build ActiveX components and at the same time provide the flexibility required to accomplish otherwise difficult tasks. If you can understand and learn from the process in which these wizards help you to build controls, you should be ready to create some more complex components on your own. Because control creation in Visual Basic is as simple as creating standard applications, it should soon become the method of choice for the multitude of programmers with VB skills.

There are an infinite number of possibilities available for the use of ActiveX controls in Internet/intranet environments. They may range from the interface objects that we have created for simple Web page design to the integrated client/server controls that communicate to stores of enterprise data. Whatever the function, the fact remains that VB programmers now have another trick up their sleeves and the Web of the future promises to be teeming with their active creations.