Special Edition Using Microsoft® Visual Studio for Enterprise Development

Previous chapterNext chapterContents


- 5 -
Creating ActiveX Components with Visual Basic

by David Burgett

Learn about the three basic types of ActiveX controls. The easiest to create is the subclassed control, which adds new properties or functionality to an existing control.
Understand the difference between constituent controls and the ActiveX control itself. Clearly understanding this difference is imperative before you can effectively expose the appropriate properties and methods for your ActiveX control.
Learn about the aggregate ActiveX control, which consists of several existing constituent controls and greatly extends their potential. Create two controls by using the ActiveX Control Interface Wizard.
Understand how to create property pages for your ActiveX control to give it a consistent, professional look your users will quickly come to expect.
Create a user-drawn ActiveX control. With user-drawn controls, you create the user interface programmatically, giving you complete control at the expense of more complicated programming.

The most important enhancement of Visual Basic 5.0 over previous versions is its capability to create reusable ActiveX controls. ActiveX controls allow you to encapsulate business rules with functionality in a single package that you can distribute easily by conventional means or by automatic download from an I-net site. You can incorporate ActiveX components into any ActiveX-enabled browser (including Microsoft Internet Explorer), Office 97 applications, Visual C++, and, of course, Visual Basic. With the addition of simple ActiveX control creation to the tool set, Microsoft has poised Visual Basic as a necessary part of any large, I-net-aware development effort. Several versions of Visual Basic support ActiveX control creation. In addition to the usual retail versions, Microsoft has released a Control Creation Edition of Visual Basic 5.0 that can be downloaded without charge at www.microsoft.com/vbasic. Although this limited version can't make executables and lacks most of the advanced features of the full development environment, it does include all the standard Visual Basic controls and provides an inexpensive opportunity to explore Visual Basic's new capabilities. Of course, with this new functionality comes a new level of development and knowledge. To develop an effective control for use by applications and browsers, you must ensure that all necessary functionality is exposed to users. You must also ensure that all errors are handled properly, either silently by the control or by passing the error back to the calling application. Care must also be taken to ensure that the control, if visible, displays properly in many different environments and responds to resizing events, if applicable.

These new concerns are well worth the added development time. By using self-sufficient controls in your applications, you separate the optimization and enhancement of your program into small, easily managed sections. Suppose that you use a sorted listbox control written by another developer in your program. The original developer then improves the sort routine, reducing by 10 seconds the time necessary to sort a long list of items. With no changes to your application, you gain the same 10-second improvement simply by downloading and registering the new control.

You also can use ActiveX controls to enhance your existing Microsoft Excel and Word documents, your company Internet presence, or even your desktop with the recent release of Internet Explorer 4.0. To gain these benefits, you must first understand the nature and purpose of your proposed control and the options available for creating it. To prepare for the exercises in this chapter, you should review the history and definition of OLE and ActiveX technologies in Chapter 4, "Using Microsoft's Object Technologies." 

Introducing ActiveX Controls

The three basic ways to create an ActiveX control with Visual Basic are separated by your use of preexisting controls:

The easiest control to create is the simple subclassed control. Subclassed controls often serve only as a measure of convenience in reducing your workload, but don't underestimate the power a simple enhancement can have.

The most basic subclassed control is one that simply has new default properties set for it. For example, the default value of a checkbox in Visual Basic is 0 - Unchecked. You can easily create a control with all the functionality and appearance of a normal checkbox, but set the default value to 1 - Checked. This might sound like a great expense for little benefit, but it can actually be a great time saver if you use checkboxes regularly. After you're acquainted with creating ActiveX controls, you can create this example easily in just a few minutes. Suppose that you could cut your development time by just five seconds per checkbox. This very simple example would pay for itself in time and energy within a relatively small number of forms.

Building a Simple ActiveX Control: The Logo Control

In this section, you'll build an ActiveX control that's more functional than the checkbox example. You'll subclass the existing image control to create one that provides a consistent look and feel throughout applications, web pages, and documents and which saves you time by simplifying their creation. The new control, Logo, is used to display a company logo for splash screens, letterheads, or web pages (see Figure 5.1). After you complete the control, your corporate logo will be available in your toolbox right next to your other tools.

FIG. 5.1
The Logo control makes creating splash screens, letterheads, and web pages as easy as dragging and dropping.

Creating the Corporate Logo Control

To start creating your corporate logo, open a new ActiveX control project by following these steps:

1. Open Visual Basic or choose File, New if Visual Basic is already running.

2. Select ActiveX Control from the New Project dialog box (see Figure 5.2).

In addition to the new ActiveX control (OCX), you can create ActiveX EXEs, DLLs, and ActiveX Documents. EXEs and DLLs are helper files that make the control available to a container, but there is an important distinction. An EXE is run in a separate process, making one copy available to multiple containers that can share data. A DLL is run in the same process and the same container, making communication between the control and the container much faster at the expense of shared variables and additional memory.

FIG. 5.2
Visual Basic 5.0 adds many types of projects you can create by using its tool set.

An ActiveX Document is different from EXEs and DLLs in that rather than be a support file offering a control to a container, the ActiveX Document is similar to other documents, having the capability to save and copy its data to other containers.


TIP: For more information on the differences among these control types, see the Books Online articles "Creating ActiveX Components" and "Building Code Components."

The first difference you'll notice between your ActiveX control project and other Visual Basic projects is the form you'll use to design your control. This form, called a UserControl, isn't a true form in that it has no visible border, title bar, or control boxes (see Figure 5.3). The reasons for this will become clear as you develop the control.

FIG. 5.3
The UserControl form is very similar to a standard Visual Basic form, but without a title bar or border. These minor differences are very important in differentiating an ActiveX control from a standard EXE form.

Next, notice the new format of the Project Explorer (see Figure 5.4).

FIG. 5.4
The Project Explorer is now a tree hierarchy instead of a simple list. This new format helps you locate a particular element quickly, and it more clearly represents the relationships between the elements.

The new tree hierarchy of the Project Explorer will help you keep track of which controls, forms, and classes belong to a given project, which can be a challenge while trying to debug a control. Multiple projects are now supported, allowing you to maintain ActiveX controls and their test forms in separate projects. Each project is listed with the filename (if specified) in parentheses.

You should provide easily understood names for the UserControl and project. The name you supply for the UserControl is the one developers and end users will see. Also, it will become the default name within the Visual Basic development environment, with a number appended to it to create a unique control name.

To set the control properties, follow these steps:

1. Right-click the UserControl and choose Properties.

2. Change the Name property to Logo.

3. Choose Project, Project1 Properties.

4. Change Project Name to GASLogo (see Figure 5.5).

FIG. 5.5
The General page of the Project Properties dialog box allows you to set the name, description, and help file for the control. On the other pages, you can determine how the control will actually be created, including settings for version information and compatibility, optimization, and conditional compilation arguments.


TIP: You can't use the same value for the UserControl name and the Project name. Remember to set the UserControl name to something succinct and memorable that describes the control clearly, because this is the name that will appear to developers when the control is added to a form.

Adding Constituent Controls to the ActiveX Control

Because you'll enhance a control, the first step is to add the preexisting control to the new UserControl. Double-click the image control in the toolbox to create a small image control in the center of your UserControl for displaying the G. A. Sullivan corporate logo. For the Logo control to act in the desired manner, you must set the properties of the image control as follows:

1. Click the Image control and press F4 or choose Context, Properties.

2. Set the Name property to imgLogo.

3. Set Stretch to True.

4. Set Picture to the G. A. Sullivan corporate logo (or the image of your choice).

5. Set Left to 0.

6. Set Top to 0.


NOTE: The choice of an image control rather than a PictureBox control clearly determines the destiny of your new control. The image control lacks many of the methods and properties of the PictureBox control, including a Windows handle to allow manipulation of the control through the Windows API. In return for this lack of functionality, the Image control uses significantly fewer system resources, allowing it to repaint more quickly.


TIP: It doesn't matter where the image resides when you set the Picture property of the Image control. Visual Basic stores the data within the control itself, allowing you to distribute the control as a single file.

The Stretch property determines the state of the image within the control. If it's set to False, the image is displayed with its defined size, despite the size of the control. As a result, only part of the image might be displayed, or the entire image might be visible with a blank area around it. When set to True, the image size is altered to conform to the control. For the corporate Logo control, it will be important to be able to resize the logo, based on the container. If the control is used in a letterhead, for example, it probably needs to be smaller than if it's used within an application's splash screen.

You must remember the distinction between the UserControl you're creating and its constituent controls. The UserControl is the interface your end user will see and use. The constituent controls used to create the UserControl (the Image control in the Logo example) are visible only to you as you develop the control. Any properties of the constituent controls that need to be exposed to a developer who uses your control will need to be mapped to related properties of the UserControl, as demonstrated later in this chapter. The confusion between these two items will probably cause most of your debugging headaches as you learn to create ActiveX controls in Visual Basic.

With this in mind, you might now understand that simply setting the Stretch property of the Image control doesn't complete the resizing of the Logo control. Developers using this control will be able to resize only the UserControl, not the Image control within the UserControl; therefore, you need to attach the Resize event of the UserControl to the Image within code as follows:

1. Right-click the UserControl (anywhere outside the boundary of the Image control) and choose View Code from the shortcut menu.

2. Enter the following code:
imgLogo.height=UserControl.scaleheight
imgLogo.width=UserControl.scalewidth

This code allows the Logo to be resized with the UserControl in the design and runtime environments. The differences between these two environments and the methods for writing conditional code based on the environment are discussed later in the chapter.

Your first control is now ready to use. Before loading your web site or opening your company letterhead, you should first test the control from the Visual Basic design environment.

Testing the Logo Control

The control you're developing can exist only within a container, such as an executable form, a web browser, or a Microsoft Word document. To test your control, therefore, you must create a container for it by adding a new project to your project group with an executable. This new concept for Visual Basic users will become second nature when the difference between controls and containers is clear.


See Chapter 8, "Using ActiveX Client Components in an I-net Environment"

See Chapter 9, "Using ActiveX Components in a Client/Server Environment"


To add a test container to your project group, choose File, Add Project and then select Standard EXE from the New page of the Add Project dialog box. Your Project Explorer should now look like the one in Figure 5.6.

Because the new project is to be used only for testing, you don't need to supply names or change properties for it. To test the new control, you add it to the form just like any other control.


TIP: The Logo control will be the lowest control in the right-most column of the toolbox. You can rest the mouse pointer over it for a moment to confirm this.

FIG. 5.6
The project group, new to Visual Basic, holds multiple projects, allowing you to create and test ActiveX controls within a single develop-ment environment.


NOTE: If the UserControl development window is still open, the Logo control will be grayed out and inaccessible. Because Visual Basic must compile and run the control to display it on a form, you can't use the control development window and the form that uses the control at the same time. If the logo is grayed out, close the window in which you developed the Logo control.

Add a Logo control to the new form by double-clicking it in the toolbox. Notice immediately that the Logo is much larger than in the previous window. This increase in size is caused by the code you supplied in the control's Resize event. When you added the control to the form, the event was fired, and the code was executed, expanding the image to the size of the control (see Figure 5.7).

FIG. 5.7
The control is placed in a form container, and the image is automatically resized to fill the control. This automatic resizing is critical to the usability of the control, allowing it to be used in a wide variety of containers.

You can resize the Logo control to ensure that the image is the desired size. The Logo control can now be used like any other. You can manipulate the properties and code the events as usual.

For the control to be distributed, you must create an OCX file by choosing File, Make GASLogo.ocx.

You're now ready to use and distribute your Logo control to create splash screens, documents, and web pages (see Figure 5.8). The OCX file containing the control is the only file you must distribute.

FIG. 5.8
Insert the Logo control into a Microsoft Word document to create a professional-looking letterhead. Because the control retains its automatic sizing capability, you can use it in multiple documents by resizing it as necessary.


TIP: The .oca files accompanying your .ocx files are cache files created and maintained by the operating system. They're updated whenever necessary, so their date and file size might change frequently.

Creating an Aggregate Control: The Framed Text Box

The second type of ActiveX control you can build with Visual Basic is an aggregate control consisting of multiple existing controls needed for a particular task. The constituent controls used to build an aggregate control can be those included with Visual Basic, third-party controls, or your own ActiveX controls. When the aggregate is complete, the constituent controls work as a single control and appear as such to users.

You can create aggregate controls for specific applications, such as a Personal Information control with text boxes to hold a user's name, address, telephone number, and other personal information (see Figure 5.9). Creating such a control means you have to create the control only once for use in multiple applications or web pages. The business rules applied to entering information, such as allowing only numeric entries in the phone number field, are all part of the control, so they don't add to the complexity of the application that uses the control. Use of a single control wherever user information is required also provides consistency between your applications and familiarity for users.

A Personal Information control example is easy to implement because it consists only of text boxes and labels to define the text boxes. Next, you'll create a slightly more challenging aggregate control that incorporates several different types of controls, implements unique properties and methods, and has a custom property page.

FIG. 5.9
This sample form accepts personal user information through six default FramedText controls. You can resize the finished FramedText control to leave visible only the desired amount of text in a single line or across multiple lines.

Building Your Aggregate Control

To understand the creation of aggregate controls, you'll create a commonly used combination of a text box surrounded by a frame. You've probably used or created an application that uses this technique. A frame control is placed on a form, and a text box control is placed within the frame. The frame's caption is then set to show users the purpose of the text box. This can lead to very user-friendly forms easily understood by even the most novice users.

Creating visual consistency between the forms in your applications or across different applications is easy when using this type of framed text control on every data entry page. The problem with this technique is the added difficulty involved in creating and maintaining two controls for each data entry field. Each time you need to resize any entry field, you have to resize the frame control and the text box itself. Also, the text box--not the frame--holds the data you're interested in. The text box control is a child control of the frame, not the form, creating additional work if you use any routines that cycle through all the controls on a form.

You can greatly simplify this ritual of creating and maintaining the combination frame and text box control by creating a single FramedText ActiveX control. Begin by following these steps:

1. Open Visual Basic or choose File, New Project.

2. Choose New ActiveX Control.

3. Name the UserControl FramedText.

4. Name the project FramedTextProject.

You need to add a frame and TextBox control to the UserControl form:

1. Double-click the Frame control in the Visual Basic toolbox. Size isn't important.

2. Click the TextBox control in the Toolbox. Draw the text box within the frame control.

3. Set the following properties for the Frame control:

Name "frmFrame"
Left 0
Top 0
Caption "FramedText"

4. Set the properties for the TextBox control:

Name "txtText"
Left 120
Top 240
MultiLine True

5. To ensure that the constituent controls always fill the aggregate control, add the following code to the UserControl_Resize event:
frmFrame.Width = UserControl.ScaleWidth
frmFrame.Height = UserControl.ScaleHeight
txtText.Width = frmFrame.Width - 2 * txtText.Left
txtText.Height = frmFrame.Height - 1.5 * txtText.Top
6. Choose File, Save FramedText.ctl.

7. Close the UserControl design window.

Add an EXE as you did for the Logo control to test the FramedText control. Add the FramedText control to the form in your EXE. If you notice immediately the time you saved in drawing only a single control, you'll appreciate this time savings more each time you resize the control or set the properties. Resize the control on the form to verify that your code is working as expected.


NOTE: The values given for placement of the text box within the frame and multipliers are just guidelines. Feel free to experiment with these values to create the look you want.

Using the ActiveX Control Interface Wizard to Create Properties for Your Control

Now that you have a FramedText control on the form, notice that the new control lacks many of the properties you need--most important, text and caption properties. You first might think to treat the text box as a property of the control and access its Text property accordingly. Remember, however, that constituent controls are private members of the UserControl. As the developer using the control, you have access only to the UserControl and its exposed properties.

You've probably realized that there must be some way to create a new property for the UserControl and to connect that property to the Text property of the text box. Indeed, there is a way--the ActiveX Control Interface Wizard.

Starting the ActiveX Control Interface Wizard  To create the Text property and the others you need, use the ActiveX Control Interface Wizard included with all levels of Visual Basic. To begin, choose Add-Ins, ActiveX Control Interface Wizard.


TIP: If the ActiveX Control Interface Wizard doesn't appear on your Add-Ins menu, place it there by following these steps:

1. Choose Add-Ins, Add-In Manager.

2. Select the VB ActiveX Control Interface Wizard checkbox.

3. Click OK.


The first dialog box of the wizard details its benefits. As the dialog box explains, this wizard helps you create your ActiveX control by providing choices about which properties and methods will be available to the developer and writing the basic code necessary to support them. You can disable the introductory dialog box as shown in Figure 5.10. If you don't see this dialog box, click the Back button to display it.

FIG. 5.10
To skip the first dialog box of the ActiveX Control Interface Wizard in the future, select the
Skip This Screen in the Future checkbox.

The introduction includes two important notes concerning interface design and property pages. The note about property pages simply reminds you to use the VB Property Page Wizard to create them more easily. The note about interface design informs you that it's necessary to add all constituent controls to the UserControl before running the wizard. This requirement is based on the manner in which the ActiveX Control Interface Wizard creates the code for your control. If you add additional constituent controls to your UserControl after running the wizard, these controls might not behave well or might have incorrect property or method assignments. Choosing the constituent controls should be the first task of every ActiveX control design, so this constraint shouldn't hinder your development.

Click Next to move to the Select Interface Members dialog box of the wizard.

Choosing Among Default Properties, Methods, and Events  In the Select Interface Members dialog box, you determine which available properties, methods, and events you'll expose in your ActiveX control (see Figure 5.11). Two lists in the dialog box show the available names and the currently selected names.

FIG. 5.11
You can choose which standard properties, methods, and events to include in your control. Visual Basic selects those already present and gives you all the standard options, as well as those of your own creation.

The list on the right shows the properties, events, and one method defaulted to all visible ActiveX controls. As you examine the list, notice several useful properties Visual Basic provides for you, such as BackColor, Enabled, and Font. Visual Basic also includes several useful events, such as Click, KeyDown, and MouseMove. The single method provided for you is Refresh, which is useful for all visible controls.

In addition to these useful elements, Visual Basic suggests two properties that aren't useful to the FramedText control. The first of these properties is BackStyle, which can be set to opaque or transparent, based on the control in question. To decide whether to include BackStyle in the FramedText control, you must again understand the difference between the ActiveX control and its constituent controls. Your first thought might be to include the property so that users can place a FramedText control over other controls (such as a background image), allowing the buried controls to show through the frame. Because BackStyle isn't a standard property of the frame control, however, it's always opaque and will completely cover the UserControl, so users will find the BackStyle property useless and confusing.


TIP: It's possible to create a FramedText control with an applicable BackStyle property, involving little additional difficulty. As an additional exercise, consider redesigning the FramedText control to use four line controls and a label control in place of the frame.

The second property not useful for the FramedText control is BorderStyle. BorderStyle could be applied to the frame control, allowing developers to turn off the frame border and thus the FramedText control. Because you're designing the control to specifically have a frame, however, including the BorderStyle property would be counterproductive.

To remove the BackStyle and BorderStyle properties, highlight both properties and click the < button.

Adding New Properties and Methods  Now that you've pared the list down to the bare necessities offered by Visual Basic, it's time to enhance the functionality and usability of the FramedText control by adding additional properties and methods. As noted before, the properties most prominently lacking from the FramedText control are Text and Caption. Highlight the following items from the Available Names list and move them to the Selected Names list:

Click Next > to move to the Create Custom Interface Members dialog box, in which you can define unique properties, methods, and events for which Visual Basic will create the necessary base code (see Figure 5.12). Although you don't need to add any custom interface members for this simple FramedText example, most ActiveX controls require many custom members in order to be useful, as you'll see in the next example.

FIG. 5.12
In the Create Custom Interface Members dialog box, you can define your own properties, methods, and events. This dialog box includes any custom interface members previously created manually.

Click Next > to move to the Set Mapping dialog box of the wizard.

Mapping Interface Members  The Set Mapping dialog box is the most critical in the wizard because it allows you to map the properties, methods, and events of the UserControl to the constituent controls (see Figure 5.13). Although Visual Basic suggests default mappings, the functionality you want might require a different mapping. If you don't map the interface members to their appropriate controls correctly, your ActiveX control won't function correctly.

FIG. 13
In the Set Mapping dialog box of the wizard, you can map the properties, methods, and events defined in the previous two dialog boxes to their appro-priate constituent controls.

Consider the Text property of the FramedText control you're creating. This property should refer to the text box constituent control, not the user control itself. You'll map the Text property of the FramedText control to the Text property of the text box, connecting the two to ensure that a change in one is mirrored in the other. As a result, when the Text property of the FramedText control is changed programmatically or at runtime, Visual Basic automatically passes the specified value through to the constituent text control. Because this process is invisible to developers and end users, the Text property appears to be a simple property like any other.

To map the Text property, follow these steps:

1. Choose Text Property from the Public Name list.

2. Choose txtText from the Maps to...Control drop-down list. Visual Basic automatically inserts the Text property in the Maps to...Member listbox.

3. Map the rest of the properties, methods, and events to the specified UserControl or constituent controls as listed:

UserControl hWnd, InitProperties, PopupMenu, ReadProperties, Refresh, Resize, WriteProperties
frmFrame BackColor, Caption, Click, DblClick, MouseDown, MouseMove, MousePointer, MouseUp, ToolTipText, WhatsThisHelpID
txtText Change, Enabled, Font, ForeColor, KeyDown, KeyPress, KeyUp, PasswordChar, SelLength, SelStart, SelText


CAUTION: You must understand the limitations of the properties you're selecting. The MultiLine property, for example, is read-only. Although you can add a MultiLine property to the UserControl and map it to the text control, the property will remain read-only; trying to change it will cause an error.
4. Click Next to move to the Finished dialog box of the wizard.

Finishing the ActiveX Control Interface Wizard  In the Finished dialog box, you can choose to view a summary report. Ensure that the checkbox is selected and click Finish. The summary report gives you an overview of the work left to be done to make your control effective and stable (see Figure 5.14).

FIG. 5.14
The Wizard Summary text suggests tasks yet to be completed, as well as tips for creating fully functional controls. Pay close attention to Section C of the summary; it offers excellent advice for making your control look and act professional.

Some of the items listed are familiar ones, such as adding an EXE to your project to test the control. Others might be new to you, such as mapping properties to multiple constituent controls, which you'll want to do for properties such as BackColor, which you previously mapped to the frmFrame control. For example, you might decide to map BackColor to the txtText control, as well, to ensure the same back color for the frame and text box. To do this, add the following line to the public property Let BackColor:

txtText.BackColor() = New_BackColor


TIP: You can easily add another property to the FramedText control to allow users to specify separate colors for the frame and the text box. Simply add a new property (as demonstrated later in this chapter), call it TextBackColor, and map it to the text control.

Completing the Control with Manually Created Properties and Events

As a Visual Basic programmer, you've no doubt spent many hours setting properties for each control on your forms. If you are a more advanced programmer, you've probably created your own properties for forms and class modules. Now it's time for you to create properties for your own ActiveX controls.

In the preceding section, you used the ActiveX Control Interface Wizard to map the properties of the UserControl to the constituent controls. By examining the code Visual Basic creates for you, you can understand how a property is created (see Figure 5.15).

FIG. 5.15
The ActiveX Control Interface Wizard creates the code necessary for the Text property, including a call to the PropertyChanged procedure, notifying Visual Basic when the property has changed.


CAUTION: As the comments suggest, the MappingInfo line contains internal information for Visual Basic and shouldn't be altered or deleted.

The code created for the Text property has two routines: a Property Get and a Property Let. The word property differentiates these routines from normal methods and procedures. The third type of Property procedure that you need to know about is Property Set, which works similarly to Property Let except that it defines a reference to an object rather than the value of one of the standard variable types. Use Property Let as you would use Let, with standard variables (such as integer or string), and use Property Set as you would use Set, with objects (such as forms or controls).

The Property Get function is straightforward. Whenever the program needs to know the value of the UserControl Text property, this function returns the value of the txtText control.

The Property Let procedure is nearly as simple as you might expect. It accepts a string parameter that holds the new value for the UserControl Text property. The procedure updates the Text property of the txtText control each time the Text property of the UserControl is altered.


TIP: You can pass extra parameters to your own custom Property procedures as long as the parameter list in the Get and the Let/Set declarations agree. Simply define the parameters you want and add to the end of the list the parameter holding the value passed into a Let/Set procedure.

The function of an extra line in the Property Let text procedure might not be immediately clear. The call to the PropertyChanged procedure tells Visual Basic that the property has changed, so VB can update its internal recordkeeping and store the new value. The design-time and runtime versions of the controls are actually different instances of the running program. If Visual Basic doesn't save the new property value internally, the runtime instance of the control won't reflect property changes made at design time.

Visual Basic handles these property value changes through an object called PropertyBag. The ActiveX Control Interface Wizard creates all necessary calls to PropertyBag for you in the ReadProperties and WriteProperties methods of the UserControl. PropertyBag has two methods--ReadProperty and WriteProperty--to move values from the internal storage to the actual control.

The ReadProperty method has a required DataName and an optional DefaultValue parameter. DataName refers to the property being read; DefaultValue is used if no entry is in PropertyBag. Similarly, WriteProperty takes required DataName and Value parameters and an optional DefaultValue parameter. The Value parameter specifies the value to place in PropertyBag in the section defined by the DataName parameter. DefaultValue works a bit differently in WriteProperty, in that WriteProperty writes information to PropertyBag only if the Value specified is different from the DefaultValue.

For example, the ReadProperty method might be called for the FramedText control with a DataName of "Text". This returns the value of the Text property in PropertyBag, or the DefaultValue if the Text property is empty. Similarly, the WriteProperty method, when passed a DateName of "Text", saves the value passed in the Value or DefaultValue in the Text property of PropertyBag.


TIP: By comparing the Value and DefaultValue and saving only when necessary, Visual Basic conserves file space. Thus, you should use DefaultValue whenever possible.

Raising events in your control is just as easy as mapping properties. By calling the RaiseEvent method, you can force Visual Basic to respond to any event you choose. For example, in the ActiveX Control Interface Wizard, you specified that the Click event should be mapped to the frmFrame_Click event. Thus, when users click the frame in the runtime environment, the code in the developer's FramedText_Click event is executed.

The problem with this is that the Click event for the FramedText control is fired only when users click the frame portion of the control; it doesn't fire when users click the text box. This disparity demonstrates the individual nature of the constituent controls, rather than the desired unity.

Fortunately, correcting this problem is simple. From the txtText_Click event, call RaiseEvent Click to force Visual Basic to fire the appropriate Click event. You could call the frmFrame_Click event instead, but this would add an unnecessary extra level of procedural calls. If you need to include additional internal code in response to the Click event, you want to call the frame's Click event directly.

To complete the FramedText control, create RaiseEvent calls in the txtText control for the following events: DblClick, MouseDown, MouseMove, and MouseUp.


NOTE: For the Mouse events, be sure to copy the RaiseEvent call from the frmFrame control to ensure that you include the correct parameters.

The FramedText control is now ready to use like any other Visual Basic control. It's manipulated in the design environment and referred to programmatically like all other controls. Test the executable to ensure that it works as it should. You can now use it in place of the standard text box whenever you want to draw special attention to an entry field.

Creating a More Complex Aggregate Control: The TimeSheet Control

To see an even more complex aggregate control, create a TimeSheet control to provide end users with a means of entering billable hours via a company intranet (see Figure 5.16).


See "Creating a Java Applet or Application," Chapter 7

FIG. 5.16
The TimeSheet control can be used in a standalone application, as seen here, or on a company intranet page. By using a single TimeSheet control in all potential browsers, you can ensure that all data entry conforms to the same set of business rules.

End users will be able to choose a Project Code and Task Code from a list of acceptable options, enter hour amounts for those projects and tasks, and enter any comments about the tasks. The control itself needs to make the acceptable Project and Task codes available, verify and correct all data entered, and provide automatic totaling for all values entered. The control also should accept a project description for every code and display this description in a ToolTip when users pause over the specified row of time values.


NOTE: Visual Basic's Control Creation Edition doesn't include the MSFlexGrid control necessary for the TimeSheet example. Although you could use a control array of text boxes or a third-party grid control instead, neither is necessary to follow the example.

This single control can easily be made available to all employees, in house and on site, with business rules built into the control, thus helping ensure that no erroneous time sheets are submitted. This example shows how ActiveX controls can improve efficiency and accuracy while expanding accessibility. This TimeSheet control will become an integral part of the G. A. Sullivan Billing Project created throughout this book and included at http://www.gasullivan.com/vs97book/.

Building the TimeSheet Control

To begin your control, open Visual Basic and a new ActiveX control as before. Edit the project properties to set the Project Name to GAS TimeSheet. Set the Name property of the UserControl to TimeSheet.


NOTE: Remember to make these names clear and suggestive, as developers will see the names when they try to add a reference to your ActiveX control.

To design the control, first decide its functionality and choose which preexisting controls to use. The TimeSheet control should look and function like any typical time sheet. The design will be a common grid, with columns of dates under which times can be entered into the appropriate rows. The rows represent the projects and tasks consultants can work on in any given week.

To display the time sheet information in rows and columns, use the Microsoft Flexible Grid control included with Visual Basic. The MSFlexGrid isn't a part of the standard toolbox items, so you'll have to tell Visual Basic where to find it by adding a reference to MSFlxGrd.OCX. (The file extension shows that the MSFlexGrid is itself an ActiveX control.) This example shows the extensibility of ActiveX controls--you're using an ActiveX control as the basis for a new ActiveX control.

Follow these steps to add a reference to the MSFlexGrid control:

1. Choose Project, Components from the menu.

2. Select Microsoft FlexGrid Control 5.0.


NOTE: If Microsoft FlexGrid Control 5.0 isn't an option on the Components list, you'll have to find the file yourself. Click Browse and locate the MSFlxGrd.OCX file, which should be in the \Windows\System directory.
3. The MSFlexGrid control is now available in your toolbox. Double-click its icon to place the default MSFlexGrid on your UserControl (see Figure 5.17).

FIG. 5.17
The MSFlexGrid is the main component of the TimeSheet control. The example here has three fixed rows at the top to serve as headers.

4. By default, the MSFlexGrid has two columns and two rows, with one of each fixed. Your time sheet will have 11 columns--seven for days and one each for project code, task code, comments, and total hours. Base the total row number in the time sheet on how many different projects and tasks any particular employee works on in a given week. As an initial estimate, you can choose 48 as the maximum. You should also set the other necessary setup properties for the MSFlexGrid now, as follows:

Property Setting
Name grdTimeSheet
Cols 11
Rows 48
FixedCols 0
FixedRows 3
Left 0
Top 0

Resize the UserControl to the largest convenient size. Resize the MSFlexGrid to fill the UserControl.

5. Like the GASLogo control created earlier in this chapter, you want the TimeSheet control to resize to fill the entire UserControl. Add the following code to the UserControl_Resize event:
grdTimeSheet.height=UserControl.scaleheight
grdTimeSheet.width=UserControl.scalewidth

Using Constituent Controls to Enhance the TimeSheet Control's Functionality

Now that the basic grid is in place for your TimeSheet control, you need to add the constituent controls that will add functionality to the TimeSheet control. One major drawback of the MSFlexGrid is that users can't type directly into the grid. Because the grid is designed to be bound to a database, there's no way to type directly into the control as into a text box control.

That said, you might guess that the next control to add to the TimeSheet control is a text box control for data entry. When users type an entry into the TimeSheet, the actual text manipulation takes place in the text control, with the final value being placed into the grid.

When examining the MSFlexGrid, you probably noticed that it supports KeyDown, KeyPress, and KeyUp events, which can be used for text manipulation. Because so much code is necessary to support all the text-manipulation functions a text box inherently handles, it's worthwhile to tackle the difficulty of incorporating the text box into the control. You'll still have to write code to handle different KeyPress events in the grid control for determining whether to display the text box or another control. By using the text box, you give users full editing functions, including cut, copy, and paste.

Follow these steps to create a text box with the appropriate properties:

  1. 1. Add a TextBox control to the TimeSheet control. (Size and placement aren't important.)

  2. 2. Set the Name property of the TextBox control to txtTime.

  3. 3. Set BorderStyle to 0 - None.

  4. 4. Set Visible to False.

Ideally, users of your aggregate ActiveX control should never see its constituent controls as separate controls; the aggregate controls should always appear and work as a single control. For the TimeSheet control, the text box should be invisible to users--they should have a sense that they're editing their text directly in the TimeSheet control itself. For this reason, set the BorderStyle of the text box to none, forcing it to be invisible (see Figure 5.18).

FIG. 5.18
The invisible text box allows you to enter text into the grid seamlessly. By ensuring that the user can't differentiate between the constituent controls, you create a professional-looking, complete control.

To make the txtTime control fully invisible to the user, you need to place it in the appropriate area of the TimeSheet and size it to match the cell being edited. To enable this, place the following code in the KeyPress event of grdTimeSheet:

txtTime.Top = GrdTimeSheet1.CellTop + GrdTimeSheet1.Top
txtTime.Left = GrdTimeSheet1.CellLeft + GrdTimeSheet1.Left
txtTime.Width = GrdTimeSheet1.CellWidth
txtTime.Height = GrdTimeSheet1.CellHeight


NOTE: The assignment statements for the Top and Left properties will work without adding an associated grdTimeSheet property because the top and left of the grid control have been set to 0. This code will be necessary later in the chapter when you move the grid control to allow room for other controls.

Whenever users press a key, txtTime appears inside the cell invisibly and accepts their input. Before this process is useful, however, you need to add four more lines of code immediately below the preceding code:

txtTime.text = chr(KeyAscii)
txtTime.setstart=len(txtTime.text)
txtTime.setfocus
txtTime.visible=true

The first line passes the character selected by users to the Text property of txtTime, offering the appearance that users are typing directly into the grid. The TimeSheet control won't be very user-friendly if users have to press a dummy key before they can start entering data. The second and third lines set the focus to txtTime and place the cursor at the end of the word, so users can continue typing immediately. Again, this helps make the transition between grid and text box seamless, enhancing the illusion that the TimeSheet is a single control.

To test your TimeSheet control, add an EXE to your project, place a TimeSheet control on the default Form1, and run the application. Use the mouse or arrow keys to select a cell and then begin typing. The text you type will seem to appear directly in the grid itself, lining up within the current cell. Notice that if you type too many characters to fit in the cell, the text automatically scrolls, just as anyone would expect. This is another benefit of using the TextBox control.

Now that users can enter data into the TextBox control, you must ensure that the data can be transferred from the text box to the grid after it's entered. Users can signify completion with one of two keys: Enter to accept the data or Esc to cancel any changes and stop data entry.

In the KeyPress event, enter the following code:

Select Case KeyAscii
Case Is = 13:
grdTimeSheet.Text = txtTime.Text
txtTime.Visible = False
      Case Is = 27:            txtTime.Visible = False
End Select

Try the code again in the executable. You can now enter text into any non-fixed cell on the grid.

Lifetime of a UserControl and the Associated Events

Now that users can enter text into the grid, you need to provide column headers so that they know which data belongs in each cell. These headers should be created within the TimeSheet itself and be visible in the design-time environment. Before you can write the code to accomplish this, you should understand the five most important events in the lifetime of an ActiveX control.

An ActiveX control begins its life cycle when the first instance of it is created within a system. This includes creation within a running application, a web page, or a design-time environment. When an instance of the control is created, the UserControl_Initialize event is fired, giving programmers a chance to set any setup variables necessary.


NOTE: The Initialize event is equivalent to the Form_Load event found in regular forms.

Immediately after the Initialize event, the InitProperties or ReadProperties event fires as the control populates its properties. The difference between the two events is chronological. The first time a control is created, the InitProperties event fires, both setting up the properties and filling them. When that instance of the control is re-created, the ReadProperties event fires, simply filling in the appropriate values.

For example, when you place a TimeSheet control on your executable's form, first the Initialize event fires, and then InitProperties. When you run the application, the control actually ends and restarts. Because this is the second creation of this particular instance of the control, the ReadProperties event fires after the Initialize event.

Two events are associated with destroying the control: WriteProperties and Terminate. WriteProperties occurs only when the control needs to save its current data back to the control container. The most common occurrence of this is when a control is deleted from the form in the design-time environment.

The Terminate event fires when any instance of the control stops. This includes ending an application, closing a web page, deleting a control within the design-time environment, or switching between runtime and design time. (The distinction will become more important later in the chapter when you use the Ambient property of the UserControl.) The Terminate event is used to process any clean-up code before the control is destroyed, such as closing the database for data-bound controls.

Now that you're familiar with the events fired at creation and destruction of your ActiveX control, you can use that knowledge to create column headers for the TimeSheet control. The column headers for a G. A. Sullivan time sheet are as follows:

When you first added the MSFlexGrid control to the TimeSheet control, you set the FixedRows property to 3. In the grid control, fixed rows appear at the top of the grid, are light gray in color, and never scroll off the page. These rows are to be filled with the column headers.

The lowest of the three fixed rows will contain the main headers and the day totals. The top and middle rows will hold the day of the week and the date, respectively. Add the code in Listing 5.1 to the UserControl_Initialize event. These values represent the static grid headings; the dynamic grid headings are determined at runtime.

Listing 5.1  Values for Static Grid Headings

Dim iCounter as integer
With grdTimeSheet
    .TextMatrix(2, 0) = "Project Code"
    .TextMatrix(2, 1) = "Task Code"
    .TextMatrix(2, 2) = "Comments"
    .TextMatrix(0, 3) = "Sunday"
    .TextMatrix(0, 4) = "Monday"
    .TextMatrix(0, 5) = "Tuesday"
    .TextMatrix(0, 6) = "Wednesday"
    .TextMatrix(0, 7) = "Thursday"
    .TextMatrix(0, 8) = "Friday"
    .TextMatrix(0, 9) = "Saturday"
    .TextMatrix(1, 10) = "Totals"
End With

Close the UserControl design window and examine the form in the executable. The column headers now appear in the design-time environment and at runtime as well (see Figure 5.19).

FIG. 5.19
With all column headers in place, the TimeSheet control now resembles a typical time sheet. Setting the default date headers to the current week gives the TimeSheet control an automated look and feel.

Considering Private Versus Public Methods

To fill in the rest of the column headers, you need to write two subroutines that can be called at initialization and any time during the control's life. When writing ActiveX controls, you need to plan carefully which portions of the control should be exposed to future developers and which should be guarded to ensure that they aren't abused. Remember that future developers not only include anyone who uses your control, but also, because controls can be subclassed, anyone who uses any child of your control.

When you define a function or subroutine in an ActiveX control, you must tell the Visual Basic compiler whether it's private or public. If you declare it as public, it will be compiled as a normal method of the control and can be called in the form of Control.MethodName just like the clear method of the ListBox control. If you declare the subroutine as private, it's available only to other routines within the same module. If you try to call a private subroutine from outside the control, you get a Method or Data Member Not Found compiler error.

To display the day totals in the third row, create a private subroutine that you can call anytime a new number of hours is entered into the TimeSheet. Because this is an internal bookkeeping subroutine, making it available to the control's users isn't necessary. Users should never have to call a routine explicitly to update the totals; the control will handle it implicitly.

Add the code in Listing 5.2 to your TimeSheet control.

Listing 5.2  The RecalcTotals Procedure

Private Sub RecalcTotals()
   Dim x As Integer
   Dim y As Integer
   For x = 3 To 9
      GrdTimeSheet1.TextMatrix(2, x) = "0"
   Next
   For x = 2 To GrdTimeSheet1.Rows - 1
      GrdTimeSheet1.TextMatrix(x, 10) = ""
   Next
   For x = 3 To GrdTimeSheet1.Rows - 1
      For y = 3 To 9
         If Val(GrdTimeSheet1.TextMatrix(x, y)) > 0 Then
            GrdTimeSheet1.TextMatrix(2, y) = Val(GrdTimeSheet1.TextMatrix(2, y))  +  Val(GrdTimeSheet1.TextMatrix(x, y))
            GrdTimeSheet1.TextMatrix(x, 10) = Val(GrdTimeSheet1.TextMatrix (x, 10)) +  Val(GrdTimeSheet1.TextMatrix(x, y))
         End If
      Next
   Next
   GrdTimeSheet1.TextMatrix(2, 10) = Val(GrdTimeSheet1.TextMatrix(2, 3)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 4)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 5)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 6)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 7)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 8)) + _
      Val(GrdTimeSheet1.TextMatrix(2, 9))
End Sub

Add a call to RecalcTotals to the end of the UserControl_Initialize event, and your TimeSheet control will have hour totals across the third fixed row. Next, add another call to the subroutine in the txtTime_KeyPress event at the end of the code for KeyAscii=13. Test the application; it should now accept and total numeric input in the grid.

Adding the Rest of the Constituent Controls to the TimeSheet Control

Most of the basic functionality has now been implemented for the time sheet. The columns are labeled and totaled appropriately, and users can enter values into the grid. The only major functions still missing from the TimeSheet control are the capability to choose project and task codes for a row and the capability to enter comments.

You might have noticed that at this point you can actually enter numeric data in the Comments column by using the txtTime control; however, using this control for comment entry limits the comments column by the same business rules applied to the time column, which, when the control is complete, will accept only numeric data. It's possible--and not difficult in code--to determine which column is now accepting the data and apply the business rules accordingly; unfortunately, that reduces the readability of the code and complicates debugging. A better solution is to add another text control to accept alphanumeric data for the Comments column.

Set the properties for txtComments identical to those of txtTime, with the exception of the MultiLine property, which should be set to True to allow users to enter multiple lines of text in a text box only one line high. The event procedures should be similar as well; you can simply copy most of the code to the new control. For the multiline capability of the text control to be useful, you have to make the control more than one line of text high; therefore, you should adjust the size of the txtComments control to be twice the height of the cell being clicked in the grdTimeSheet_Click event. Although the text will be squeezed back into a single line when it's placed into the grdTimeSheet control, it will be easier to enter long text when more is visible.


CAUTION: Make sure that all references to txtTime are changed to txtComments, and if you add any additional code that references a specific cell of the grid control, be sure to edit the values accordingly. The Visual Basic compiler can't catch these types of errors, and debugging them can be quite difficult.

To complete the TimeSheet control, you need to add to it two combo boxes to hold the list of acceptable project and task codes. Combining lists into one listbox control creates the list as it's required and saves a small amount of memory; however, it also diminishes the control's performance speed by forcing it to re-create the list at runtime.

Accordingly, add two combo box controls to the TimeSheet control and name them lstProjectCodes and lstTaskCodes. You need to add to the TimeSheet control two public methods, SetProjectCodes and SetTaskCodes, which will accept an array of strings containing the appropriate data. This array of strings will be used to fill the Project Descriptions combo box, which means end users can choose only from valid projects. The same technique is used for the Task Descriptions combo box. Because the control expects to have a list of project descriptions, the array passed containing the project codes is actually a two-dimensional array that also holds the descriptions (see Figure 5.20).

FIG. 5.20
The first listbox displays the appropriate project codes for this time card, ensuring that only a valid project code is used. You should always define appropriate business rules for a control before doing any actual coding.


TIP: If you've never passed an array to a Visual Basic procedure, don't worry--it's easier than it might seem. Simply declare the parameter as you would for any variable-length array in the procedure declaration, as follows:

Public Sub SetProjectCodes(codes() As String)

To determine the number of elements passed into the procedure, use the Ubound() function and work with the array as you would any locally defined one.


Carefully Considering Exposed Properties to Make a Control Complete and Useful

The default properties of the combo box control should work fine for the TimeSheet as well. The only property that stands out as one that could be changed is Sorted. The TimeSheet control would probably seem more user-friendly if the Project and Task codes were displayed in alphabetical order, so this might be your initial impulse.

Before you do this, however, you should consider your end users' needs. Perhaps the project codes for a given company are customarily in a particular order but not necessarily alphabetical. If users are accustomed to a specific list order, altering that order might actually make the control more difficult to use.

The ideal solution is to allow users to decide whether to sort the combo boxes; however, the Sorted property of combo boxes is read-only at runtime, making such a property very difficult to implement.


TIP: If you want to explore the option of letting users decide, consider either maintaining two combo boxes for each list or using the Windows API to design the combo box from scratch in code. To use the API, start with CreateWindowEx, any good book on the Win32 API, and a lot of elbow grease, and you should be on your way.

Some properties are easy to create and useful to the control. Because you've seen how the ActiveX Control Interface Wizard can create the properties for the control, you'll create a property manually to help you understand the internal mechanics involved. You'll create a DayMax property for the TimeSheet control to establish one of the business rules applied to the data entered into the TimeSheet. The DayMax property gives developers a way to control the maximum number of hours that can be entered for any given day. This way, the maximum number of working hours can be set to 8 or some other valid value between 1 and 24, helping to decrease data entry mistakes.

A UserControl property is nothing more than a publicly defined variable, a designated value that the outside world can see. You might decide, therefore, to define a public module-level integer variable for the TimeSheet control and name it DayMax. This way, any program using the TimeSheet control can set or read TimeSheet.Daymax as any valid integer value. Although this method would work, it offers the control no protection from being set to a negative value or a nonsensical value, such as 10,000. What you need is a way to call a procedure anytime the value changes. Visual Basic properties give you just that, and more.


CAUTION: Be sure to remove any declarations you've made for DayMax before implementing it as a property. If you attempt to define a property and a module variable with the same name, the compiler will stop with an Ambiguous Name detected error.

Recall the syntax for property declaration:

Public Property Get PropertyName() as PropertyType
Public Property Let/Set PropertyName(PropertyValue as PropertyType)

Define the procedures for the DayMax property. Now that you know how to define the property to the world, you need to create an internal integer variable that holds the value of the public variable. Define m_DayMax as a module-level public integer. The m_ stands for member to help you remember that the variable is an internal member of the control. Create the property by entering the code in Listing 5.3.

Listing 5.3  The Code Behind a Control Property After Sufficient Error Checking Is Included

Public Property Get DayMax() As Integer
   DayMax = m_DayMax
End Property
Public Property Let DayMax(ByVal New_DayMax As Integer)
   If New_DayMax >= 1 And New_DayMax <= 24 Then
      m_DayMax = New_DayMax
   End If
End Property

Now close the code window and open the executable form. Select the TimeSheet control and examine its properties. The DayMax property appears in the Property Browser like any Visual Basic-defined property and is set to the default value for integers of zero (see Figure 5.21).

FIG. 5.21
The DayMax property appears in the standard Visual Basic property window with a brief description in the Help pane. Note that clicking the ... button to the right of the DayMax line opens the property page containing the DayMax property.


TIP: To make the DayMax useful, you must set its default value to a valid value, such as 24. This is accomplished in the UserControl_InitProperties event, setting m_DayMax to 24 and allowing the property to read this value when it's created.

Using the ActiveX Control Interface Wizard's Set Attributes Dialog Box

Finish adding the constituent controls to the TimeSheet control by adding a lblEmployeeName label and an associated label captioned Employee: just above the MSFlexGrid control.

Now that all constituent controls have been placed on the TimeSheet control and you've added the DayMax property manually, start the ActiveX Control Interface Wizard to complete the following list of custom interface members:


ON THE WEB:The entire code for the TimeSheet control is available at http://www.gasullivan.com/vs97book/.

Use the ActiveX Control Interface Wizard to map the EmployeeName property directly to the associated label. No further work on this property is necessary for now. The EmployeeName property would be extremely useful in a more advanced version of the TimeSheet control that updates a centralized database or automated payroll.

The Increment property is a single value used to round data values entered to the nearest increment specified. For example, if you want to allow your employees to enter time in 15-minute segments, set the Increment property to .25. Now all values entered will be rounded to the nearest quarter hour automatically.

Because the Increment property is non-visual, don't map it to any of the constituent controls. Having a non-mapped interface member will bring up the wizard's Set Attributes dialog box (see Figure 5.22). This dialog box defines settings for any non-mapped properties. You can define the return type and the arguments to simplify method declaration or the data type, default value, and read/write privileges to simplify property declaration.

FIG. 5.22
The Set Attributes dialog box of the ActiveX Control Interface Wizard allows you to set read/write access, default values, data types, and help descriptions. Appropriate default property values speed development time and make a control user-friendly.


TIP: You can also use the Set Attributes dialog box to define a description for each unmapped property, method, and event. This description will be available to developers via the Visual Basic Object Browser and the property window, as seen in the last section of this chapter. If you plan to distribute your control for development use, you should always define a clear, helpful description--other developers will appreciate it.

Notice that the ActiveX Control Interface Wizard includes DayMax in the list of properties. This demonstrates the capability to run the wizard multiple times, even after property or method definitions change. As noted before, it's important to have all constituent controls in place before running the wizard, but code changes won't have an adverse effect.


NOTE: If you run the wizard multiple times on a single control, you'll probably encounter Visual Basic's odd way of dealing with multiple declarations. Rather than replace an old declaration with the newly defined one, Visual Basic simply comments out the old code. While this is excellent for recovering from mistakes made within the wizard, it makes for messy code. Removing these extra comments will make your code more readable.

Set a description and the appropriate values for each custom interface member and then finish the wizard. Copy the code for the three new methods and test it in the executable. You should be able to change the employee name, view automatic rounding of all hour data entered, and retrieve information about the data in the grid.

Using the AmbientProperties Object

As explained previously, one major difference between ActiveX controls and normal executables is that you create ActiveX controls to run in two distinct states: design time and runtime. The control developers manipulate in the design environment (for example, Visual Basic or C++) is a running application, the same application that executes in the runtime environment. As the control developer, you must have a way to determine which state the control is being run in so that it can react appropriately. The AmbientProperties object holds this information and much more (see Figure 5.23).

FIG. 5.23
By using the Ambient.UserMode property, you can fill the grid with the control name at design time only. Use discretion with the UserMode property; overuse or misguided use will only confuse your users.

Every container that can support an ActiveX control provides an AmbientProperties object (called simply Ambient) with which you can determine this. Visual Basic defines 16 read-only properties within the object, allowing you access to important container values such as BackColor, DisplayName, Font, and UserMode.

UserMode is a Boolean value that determines whether the control is being executed for end users or developers. If end users are using the application containing the control, UserMode is True; otherwise, it's False. This property allows you to design controls with different design-time and runtime interfaces, such as the Common Dialog, Timer, and Wizard controls.

To demonstrate use of the UserMode property, add code to the TimeSheet control to display the name of the control in each non-fixed cell of the TimeSheet at design time only. Add the code in Listing 5.4 to the UserControl_Show event. The Ambient object's most useful property for creating ActiveX controls is the UserMode property, which allows you to determine whether the controls are being used in design- or runtime mode.

Listing 5.4  Adding a Creative Finishing Touch with the Ambient Property

If Ambient.UserMode = False Then
   Dim x As Integer, y As Integer
   For x = 3 To MSFlexGrid1.Rows - 1
      For y = 0 To 10
         MSFlexGrid1.TextMatrix(x, y) = Ambient.DisplayName
      Next
   Next
End If
BackColor = Ambient.BackColor
MSFlexGrid1.ForeColor = Ambient.ForeColor

Now examine the control placed on the executable form, noticing that each cell contains the name TimeSheet1, the default name for the TimeSheet control. The final two lines blend the control into its container by setting the control's color scheme to match that of the container. Thus, if you set the BackColor of the executable form to another color, close the form, and then redisplay it, the frame around the grid portion of the TimeSheet control reflects the color change as well. This technique works very well if the TimeSheet is used with no border (BorderStyle = 0). Run the executable to ensure that the DisplayName is filled in only at design time.

You probably noticed the problem with this code when you had to close the executable form and reopen it to display the name changes. The code to fill the grid with Ambient.DisplayName executes only when the control is drawn on the form. Thus, the DisplayName in the grid doesn't update when you change the control's properties. To account for this, the UserControl has an AmbientChanged event, which fires whenever one of the ambient properties of the control container is changed. The list of ambient properties includes, in addition to those used previously, properties that let you determine that your control is the default for the form, the palette being used to paint the control, and even the local language being used by Windows.

To use the AmbientChanged event, copy the code from the UserControl.Show event and paste it into the UserControl.AmbientChanged event. Delete the three final lines from the Show event to ensure that the colors used to paint the TimeSheet control will change only when the related colors of the container change. Leave the code to display the Ambient.DisplayName at design time in both events to ensure that the grid is filled when the form is displayed and updated when the control name is changed.


TIP: To further optimize your control, use the PropertyName parameter passed into the AmbientChanged event. You can build a Select Case statement to execute only the code necessitated by the property that changed, rather than execute all code each time.


NOTE: The AmbientProperties object is container-specific. In other words, different containers might have different AmbientProperties objects and, thus, different ambient properties. For example, BackColor is a standard property of the form's AmbientProperties object, but not necessarily of a custom container. Your completed control should therefore include code to handle any situation where the control is placed on a nonstandard container that doesn't support the properties you're trying to read.

Now that you have all your properties created and set appropriately, you might be wondering how developers will keep track of them all. Another new feature of Visual Basic 5.0 is its capability to group properties together into logical groups on property pages.

Creating Property Pages

In using Visual Basic, you've probably used a control that contains a custom property page. In fact, you've used one to create the TimeSheet control, the MSFlexGrid. In the ActiveX control designer, right-click MSFlexGrid1 and choose Properties. Rather than bring up the usual Visual Basic Properties window, the MSFlexGrid displays its own custom property page (see Figure 5.24).

FIG. 5.24
The MSFlexGrid's custom property page groups the control's properties into five convenient pages.

The benefit of using a custom property page is ease of use and understanding for developers. The custom property page lets you group properties onto tabbed pages, making their purpose more readily understood. The property page for the MSFlexGrid divides its properties into five tabs devoted to General, Style, Font, Color, and Picture properties. On the General page, developers can see at a glance the number of fixed and standard rows and columns without having to scan up and down a typical Properties window. Likewise, with all font-related properties grouped on a single page, developers can see them all quickly and even view a sample based on the current settings.

Creating Property Pages with the Wizard  To add a property page to the TimeSheet control, you use the Property Page Wizard to construct the basic page. Choose Add-Ins, Property Page Wizard from the menu.


TIP: If the Property Page Wizard doesn't appear on your Add-Ins menu, you can place it there by following these steps:

1. Choose Add-Ins, Add-In Manager from the menu.

2. Select the checkbox next to VB Property Page Wizard.

3. Click OK.


The first dialog box of the wizard is an introduction, which can be turned off by selecting the checkbox. The second dialog box of the wizard, Select the Property Pages, shows you a list of the property pages Visual Basic suggests you include. Three default property pages are included with Visual Basic: StandardFont, StandardColor, and StandardPicture. These same pages are included in the custom property page for the MSFlexGrid.

Visual Basic has already suggested that you use the StandardFont and StandardColor pages by adding them to the list of available pages. Select both and click Add to add a new page. Name the page General when prompted and move to the next screen. Choose General at the bottom of the list and click the up-arrow button twice to move it to the top of the list (see Figure 5.25).

FIG. 5.25
Visual Basic suggests StandardFont and StandardColor property pages, to which you'll add a General property page as shown here. Additional pages are only a click away.


TIP: In addition to the Font, Color, and Picture pages, Visual Basic includes any property pages (.pag files) you have in your project. By adding existing pages to your project, you can reuse property pages for many different controls, with only minor modification.

Ensure that all three pages are selected and advance to the next dialog box, Add Properties. In this dialog box, you can decide which properties belong together on a single page. On the left is the list of Available Properties, and on the right are tabbed listboxes for each selected property page. With the General tab selected, choose >> to move the three available properties into the General listbox (see Figure 5.26).

FIG. 5.26
The three available properties for the TimeSheet control should be moved onto the General property page. In more complicated controls, it's important to place similar properties together to help users understand their purpose.


NOTE: The StandardFont and StandardColor pages are maintained directly by Visual Basic; thus, you can't add properties to these pages.

Choose Next > to move to the final page of the Property Page Wizard. Choose to view the summary report and select Finish. If you've just created the General property page, Visual Basic issues a confirmation that it was created. Respond to the confirmation and examine the summary report closely--it contains a plethora of tips on how to test, bulletproof, and accessorize your property pages.

Close the summary report and open the executable form. Right-click the TimeSheet control and notice a new entry titled Properties. Select this entry to view the custom property page for the TimeSheet control (see Figure 5.27).

FIG. 5.27
The custom property page for the TimeSheet control is complete in just a few minutes with the Property Page Wizard. This small amount of work can pay large rewards in ease of use and readability for users.

Test the property page to ensure that the changes are applied back to the control. Close the property page and return to the ActiveX control designer. Notice that the General property page now resides in your project window. Double-click the page to view it. Property pages are designed like all ActiveX controls. You can use the property page design window to modify the page any way you like. For example, to personalize the page to your company, you could add a background image of your company logo. You can also locate the controls where you want them on the page.


NOTE: You'll probably be inclined to resize the General property page to conserve space, but this isn't necessary. When the custom property page is displayed, Visual Basic will create a uniform interface by forcing all forms to the size of the largest form.

Creating Property Pages from Scratch  At some point, you'll probably find it easier to create your own property pages from scratch than to use the wizard. Start by choosing Project, Add Property Page to create the page. When the page is created, save the form and examine the properties for the ActiveX control. The PropertyPages property maintains a list of all included property pages. Check the box next to your new page to include it in the list. To make a designed-from-scratch page functional, you would have to write the code the Property Page Wizard creates for you. Examine the code in the General property page. You'll notice the PropertyPage object has two new events in addition to the usual events for forms:

The standard Properties window in Visual Basic allows you to select multiple controls, displaying only common properties and values for which the value is the same for each selected control. Examination of the basic code created by the Property Page Wizard shows that your new property page doesn't support this. The created code in SelectionChanged fills the property page with the values from only the first of the selected controls.

The same limitation exists for the ApplyChanges event; although the Visual Basic property window can set a property for multiple selected controls, the default code created by the Property Page Wizard applies the changes to only the first selected control. To alter the code to apply the changes to all controls, create a loop and alter the property value for each control in the SelectedControls array.


TIP: For additional information, see the "Creating Property Pages for ActiveX Controls" and "Property Page Wizard" sections in Books Online.

The TimeSheet control is now complete. This control is a bit different from the previous two controls--the Logo control and the TextBox control--in that it's not a control you're likely to keep in your toolbox permanently. The TimeSheet control has been created to serve a specific purpose on the company intranet page, but don't overlook the other possibilities for this control. It could easily be integrated into a Word document with VBA code to create automated reports and graphs or modified to hold other kinds of spreadsheet-related data, such as expense reports, inventory records, or financial data.


See "Components of a Dynamic Web-Based Application," Chapter 11

Creating User-Drawn Controls

Now that you've written both simple subclassed and aggregate controls, you need to learn about the third type of ActiveX control--user-drawn. User-drawn controls are the most complex, because you're required to code not only the functionality, but also the interface. Common user-drawn controls include representations of common objects (such as business cards or a deck of playing cards) as well as images created at runtime to demonstrate the drawing process.

To see the creation of user-drawn controls, you'll create a StarBurst control that will draw a simple 16-point star filling the control (see Figure 5.28). The creation of the control is straightforward, with a single routine drawing each line in rapid succession to create the star. You easily can modify this routine to change colors after drawing each line, to pause for a specified amount of time between lines, or to draw continuously with random colors until some event occurs.

FIG. 5.28
The StarBurst control redraws itself with any ForeColor and BackColor you specify. Also, you might add DrawMode and TimerInterval properties to create a constantly changing design.

Follow these steps to create the StarBurst control:

1. Create a new ActiveX control and name it StarBurst.

2. Name the project StarBurstProject.

3. Add the code in Listing 5.5 to the UserControl.Resize event. This simple code becomes very flexible through its use of the UserControl's width and height properties.

4. Adding the code to the Resize event ensures that the StarBurst will always fill the entire control. To ensure that the control is always redrawn correctly, the Paint event needs to include a call to the Resize event.

5. Change the ControlContainer property of the control to True to allow the control to contain other controls.

6. Test the control by adding an EXE and placing a StarBurst control on the form. It will draw the starburst and maintain its aspect ratio as you resize the control.

Listing 5.5  A Simple User-Drawn Control

Dim ix As Integer, iy As Integer
UserControl.Cls
ix = UserControl.Width
iy = UserControl.Height
Line (ix / 4, iy)-(ix / 2, 0)
Line -(ix / 4 * 3, iy)
Line -(0, 0)
Line -(ix, iy / 4 * 3)
Line -(0, iy / 2)
Line -(ix, iy / 4)
Line -(0, iy)
Line -(ix / 4 * 3, 0)
Line -(ix / 2, iy)
Line -(ix / 4, 0)
Line -(ix, iy) 
Line -(0, iy / 4)
Line -(ix, iy / 2)
Line -(0, iy / 4 * 3)
Line -(ix, 0)
Line -(ix / 4, iy) 

Try adding another control to the form, within the StarBurst container. Move the StarBurst control to ensure that the container control is actually contained.


TIP: If you double-click a control in the toolbox, Visual Basic adds the control to the form by default. To add a control inside the StarBurst container, single-click the control in the toolbox and draw an outline of the control within the container.

Run the ActiveX Control Interface Wizard and remove all properties, methods, and events except BackColor, BorderStyle, Click, Enabled, ForeColor, and Refresh. You don't need any custom interface members, and the mapping for all six items is to the UserControl.

Because the BackColor and ForeColor are already mapped to the UserControl and the Line function draws in the ForeColor of the form, most of the work has been done for you. You only need to add a call to the Resize event in both Property Let procedures to ensure that the control is redrawn immediately. Enabled, Click, and Refresh need no additional code.

If you look at the properties you've placed on the executable form for the StarBurst control, notice that you can't choose from the standard BorderStyle types. To make the StarBurst control conform to other Visual Basic controls with a BorderStyle property, you must create an enumerated type to assign to the property.


NOTE: An enumerated type is simply a way to keep two sets of related constants mapped to one another. Suppose that the developer would like to see None and FixedSingle as BorderStyle options, and Visual Basic expects to see a numeric value. Creating an enumerated type allows both sides to see what they want and agree on what that means.

In the declarations section of the control, add the following enumeration code:

Enum BorderStyleEnum
    None = 0
    FixedSingle = 1
End Enum

This code defines the values you'll allow for BorderStyle. To force BorderStyle to allow only these two values, you must change the type definition in the Property Let and Get procedures. They should be the same as shown in Listing 5.6.

Listing 5.6  Adding a Professional Look to Your Control's Properties

`WARNING! DO NOT REMOVE OR MODIFY THE FOLLOWING COMMENTED LINES!
`MappingInfo=UserControl,UserControl,-1,BorderStyle
Public Property Get BorderStyle() As BorderStyleEnum
    BorderStyle = UserControl.BorderStyle
End Property
Public Property Let BorderStyle(ByVal New_BorderStyle As BorderStyleEnum)
    UserControl.BorderStyle() = New_BorderStyle
    PropertyChanged "BorderStyle"
End Property

If you now examine the BorderStyle property of the control on the executable form, you should see the familiar choices of None and FixedSingle (see Figure 5.29).

FIG. 5.29
The enumerated types for BorderStyle appear in the Visual Basic Properties window as a drop-down list. Creating control properties that act just like Visual Basic's controls takes only a few lines of code.


TIP: Be sure to define all color properties for your control as OLE_COLOR, not Long, if you want users to be able to choose the color from the palette rather than enter a long integer.

The StarBurst control now has fully operational properties that act and appear just like the properties for any standard Visual Basic control.

From Here...

By using Visual Basic to create ActiveX controls, you can easily create a single interface for standalone applications as well as web browsers and documents. This single-control approach greatly reduces development time and encourages consistency across multiple formats and applications.

The growth of the Internet in the last few years has been phenomenal and shows no signs of slowing. Add to this the increasing popularity of corporate intranets and the integration of Internet Explorer 4.0 into the operating system, and it becomes clear the web browser is quickly taking its place as a necessary piece of software on every computer. With online software purchases on the rise, the ability to issue updates and bug fixes only for the necessary controls (instead of the entire application) becomes important. ActiveX controls give you this capability by automatically downloading and updating themselves when appropriate. Usually, end users need to do no more than confirm the download, making application maintenance a breeze across an intranet and simplifying downloads over the Internet.

In addition to these benefits, the capability to add ActiveX controls directly into Microsoft Word and Excel documents changes the entire face of document handling. No longer will documents be static; with ActiveX controls embedded in them, documents can be more responsive, more informative, and more user-friendly--all with very little additional development work.

With the advent of ActiveX control creation and many other advanced features available in version 5.0, Visual Basic takes its place as a premier development language suited to projects of any size and complexity.

Refer to the following chapters for more information relevant to ActiveX controls:


Previous chapterNext chapterContents


© Copyright, Macmillan Computer Publishing. All rights reserved.