Chapter 3

Developing Your First Control


CONTENTS

This chapter steps you through a basic control project without going into too much detail about why things are the way they are. (We'll go back and cover each topic in detail later.) The objective is to familiarize you with how control development works in Visual Basic and to get you comfortable with the various terms and techniques you'll use on a regular basis.

The control you'll build in this chapter is designed to notify a company's employees of special events. The control could be deployed in either an executable application or a Web page. It is comprised of a graphical component and a textual component, combining the functionality of the standard Windows PictureBox and Label controls.

Getting Started

Start by creating a control project. To do this:

  1. Start Visual Basic Control Creation Edition.
  2. The New Project dialog box will appear (see Figure 3.1). Select ActiveX control.

Figure 3.1 : New Project dialog box.

You're now in the Visual Basic IDE window (see Figure 3.2). On the right side is the Project Explorer, an outline list of all the files in your project. (If you can't see the Project Explorer, choose the menu command View, Project Explorer or use the keystroke shortcut Ctrl+R.)

Figure 3.2 : Visual Basic IDE.

Setting Up the IDE

You'll want to make sure that a few other Visual Basic windows are open before you start working. The Toolbox enables you to manipulate existing controls, as well as the control you're working on. To make the Toolbox visible, use the menu command View, Toolbox.

The Immediate window enables you to type in Visual Basic code while your project is running. This can be a great help when you're testing and debugging your code. To display the Immediate window, use the menu command View, Immediate Window or use the keystroke shortcut Ctrl+G.

Finally, for the purposes of this walk-through, you'll want to change the Visual Basic development environment from the default MDI to SDI. To do this (if you haven't done it already):

  1. Choose the menu command Tools, Options.
  2. Select the Advanced tab.
  3. Activate the SDI Development Environment checkbox (see Figure 3.3).
  4. Click on OK.
  5. Restart Visual Basic. VB's interface will have changed; it's now equipped with close buttons for individual windows that you can drag around the screen.

Figure 3.3 : Advanced Options dialog box.

Managing the Project

The Project Explorer currently contains two files: the project file, called Project1, and a new control, currently called UserControl1. Start by changing the names of these objects to something more meaningful. To change the name of the project:

  1. In Project Explorer, click on Project1. The Properties window changes, displaying properties for Project1. At the top of the list is the Name property.
  2. Double-click on the name UserControl1 to select it. Type the new name of this project, HappyHourProject. Figure 3.4 shows all the changes that will occur to reflect the new name.

Figure 3.4 : Renamed project.

Now give the control a more meaningful (and festive) name. To do this:

  1. In the Project Explorer window, double-click on the control UserControl1.
  2. The Properties window will change to display properties for the control. In the control's Name property, change UserControl1 to HappyHour.

When you're done, the Project Explorer will look as in Figure 3.5.

Figure 3.5 : Renamed control.

Designing Your Control

On the screen you'll see a window containing a nondescript gray rectangle. This is known as the control designer. The control designer is the visual representation of the control, analogous to a form in a Visual Basic EXE project.

Every control you create using Visual Basic has a designer-even controls designed to be invisible at runtime have them.

The HappyHour control you'll create will be comprised of two standard Windows controls: a PictureBox control and a Label control. The Label control will act as a caption for the graphic that appears in the PictureBox control. To add these controls to the form designer:

  1. In the Toolbox, double-click on the PictureBox control, shown in Figure 3.6.
  2. A PictureBox control will appear in the middle of the control designer.
  3. Set the PictureBox's properties as shown in Table 3.1.

Figure 3.6 : PictureBox control in Toolbox.

Table 3.1 Picturebox Properties For Controls

PropertyValue
Left0
Top0
Height150
Width400

  1. Double-click on the Label control icon in the Toolbox to create a Label control on the control designer. Move the Label below the PictureBox and resize it so it takes up the bottom third of the designer.
  2. In the Properties window, change the properties of the Label as shown in Table 3.2.

Table 3.2 Label Properties

PropertyValue
NamelblCaption
Caption(nothing)
WordWrapTrue
Top150
Height100
Left0
Width400

  1. Before you go any further, save the project. To do this, use the menu command File, Save Project. You'll be prompted to save the files (use the file names shown in Table 3.3).

Table 3.3 Control Filenames

FileDescription
HappyHour.ctlThe control designer
HappyHourProject.vbpThe project file

Later you'll add more files to the project and re-save them. The project file keeps references to all the files in your projects, so when you quit and restart Visual Basic, you won't have to re-load all the files in your project one at a time.

The visual design of this control is done for now. Close the HappyHour designer by clicking on its close button. The close buttons for the control designers-in both the MDI and SDI interfaces-are shown in Figures 3.7 and 3.8.

Figure 3.7 : Close button (SDI interface).

Figure 3.8 : Close button (MDI interface).

Testing the Control

Even though your control technically doesn't do anything at this point, you can still instantiate it on a form and view its visual interface.

The problem, though, is that ActiveX control projects can't be run like conventional Visual Basic programs can; they can only be run in the context of a container. A container can be a form in a Visual Basic executable, an HTML Web page displayed in Microsoft Internet Explorer, or another application that supports ActiveX controls.

In order to test your controls without having to mess around with a completely different development environment, Visual Basic gives you the ability to insert an EXE project into your control project. Forms in an EXE project are identical to Visual Basic forms; they just can't be compiled into an actual EXE file under VB. To insert an EXE project into your current project:

  1. Make sure that the control designer for the HappyHour control is closed. You won't be able to test the control unless it is closed.
  2. Select the menu command File, Add Project.
  3. The Add Project dialog box will appear. (This window appears startlingly similar to the New Project dialog box, so be sure you pull up the one that lets you add a project rather than create a new one.)
  4. Choose Standard EXE, then click on Open. In the Project Explorer, two new files are added to your project: Project1, a Visual Basic EXE project file, and Form1, a standard Visual Basic form. Form1 appears in the middle of the screen.

NOTE
You can't insert a control onto a form if that control's designer is open. When a control's designer is open, the control's icon in the Toolbox will appear disabled. To close the control designer, click on its close button.

  1. Insert a HappyHour control on the form by double-clicking on its icon in the Toolbox (illustrated in Figure 3.9). An instance of your control, called HappyHour1, is created.

Figure 3.9 : HappyHour control in the Toolbox.

Even though you haven't written any code yet, the HappyHour1 control already has a rudimentary set of properties and events. By looking at the Properties window, you can see that HappyHour1 has Height, Width, and Visible properties, as well as GotFocus and LostFocus events, among others. These properties and events are not really a part of your control, but rather passed through from the container in which your control resides. You can see these properties by looking in the Properties window, as illustrated in Figure 3.10.

Figure 3.10: Properties window of the HappyHour control.

I'll discuss more about the relationship between controls and containers in Chapter 7

Although you now have a basic control with a few properties and events, this isn't nearly enough. For one thing, if you resize HappyHour1 and make it smaller, the Label and PictureBox controls inside can easily get lost. And nothing makes a constituent control more angry, believe me.

For another thing, the important properties of the controls within HappyHour1 (such as the Caption property of the Label and the Picture property of the PictureBox control) can't be accessed.

We'll spend the rest of this chapter rectifying this situation. Start by closing Form1 by clicking on its close button, then returning to the control designer for HappyHour through the Project Explorer.

Exposing the HappyHour Control's Caption Property

The easiest way to implement a property in a control you create is to use an existing property of another control. Controls included in your controls are called constituent controls; the process of hijacking their properties and events is referred to as delegation. In this case you'll delegate the Caption property of the constituent Label control to provide a Caption property for the HappyHour control.

To give the HappyHour control a Caption property, do the following:

  1. In the Project Explorer, double-click on HappyHour.ctl to open it.
  2. Double-click on the HappyHour control's designer to bring up its code window.
  3. Choose the menu command Tools, Add Procedure. The Add Procedure dialog box will appear.
  4. In the Name box, type Caption.
  5. In the Type panel, select the Property option. The Add Procedure dialog box should look like Figure 3.11.
  6. Click on OK. The code window will create two new procedures, a Property Get and a Property Let, as illustrated in Figure 3.12.

Figure 3.11: The Add Procedure window.

Figure 3.12: Code window with Property Get and Property Let.

Coding the Property Get

To cause the label's Caption property to be read whenever the HappyHour control's Caption property is accessed, modify the Property Get procedure like this:

Public Property Get Caption() As String
    Caption = lblCaption.Caption
End Property

Note that you must change the first line of the procedure to be defined As String instead of As Variant, since captions should be treated as strings.

TIP
Remember, you can toggle between Full Module view and Procedure view by using the buttons in the lower left corner of the code window.

Property Get versus Property Let
The concepts of Property Get and Property Let were first introduced in Visual Basic 4.0. For some reason, when this feature was first introduced, I had trouble remembering that Property Get was the code that was run when the property was read, and Property Let was the code that was run when the property was altered. If you have this problem, too, and you're an old hand with Basic, it might help you to remember that Let is a Basic keyword from the old days of Basic programming. The code was the way you set the variable x to the value 5. So it is with properties, thereby proving that everything old is new again.
To know when to use Property Let vs Property Set, it helps to remember that assigning a value to an object variable always requires a Set statement; similarly, properties that represent objects (such as the Picture property of a PictureBox control) require a Property Set instead of a Property Let.
Let x = 5

Coding the Property Let

Property Let is the procedure that runs when the value of a property is set or changed. To cause the label's Caption property to pass through to the HappyHour control's Caption property, alter the Property Let procedure so it looks like this:

Public Property Let Caption(ByVal NewCaption As String)
    lblCaption.Caption = NewCaption
    PropertyChanged "Caption"
End Property

Note that the data type of the Property Let procedure was changed from Variant to String, to reflect the fact that captions are strings.

PropertyChanged looks like a statement, but it's actually a method of your control's container. The method sends a message to the container, informing it that a property of your control has been changed. This flags the container to trigger a WriteProperties event, which saves any design-time changes to this property that you make. (We'll deal with the WriteProperties event again later on.)

Testing the New Caption Property

To test that the HappyHour control's new Caption property works properly, you can go back to the instance of your HappyHour control that exists on Form1. To do this:

  1. Close the code window by clicking on its close button.
  2. Close HappyHour's designer by clicking on its close button.
  3. Using the Project Explorer, open Form1 by double-clicking on it.
  4. There should still be an instance of your HappyHour control on Form1. Click on it to select it, if it isn't selected already.

Look in the Properties window. You should be able to see that there is now a Caption property available for HappyHour1. Try changing the property by typing something else in the Caption property. Your control should look something like Figure 3.13.

Figure 3.13: Caption property for HappyHour1.

We're finished exposing the HappyHour control's Caption property. We'll expose the Picture property of the PictureBox control through the HappyHour control later. For now, there's another problem to solve: resizing.

Resizing the Control

When HappyHour1 is resized, the two controls contained in it aren't resized along with it. This can cause the Label or the PictureBox to become lost if the control is made too small. And we can't have that. To rectify this, we can insert code that will cause the components of the HappyHour control to resize themselves. To do this:

  1. Close Form1, if it is open, and reopen the control designer for HappyHour from the Project Explorer.
  2. Double-click on the control designer to open a code window.
  3. In the Object combo box (at the top left of the code window), select UserControl, if it isn't selected already. This exposes the events for the control itself. The UserControl object is to a control what the Form object is to a conventional Visual Basic application.
  4. In the Event combo box(at the top right of the code window), select Resize.
  5. In the UserControl's Resize event, insert the following code:
Private Sub UserControl_Resize()
    'To make this more concise as well as faster executing,
    'You can also use a Move method instead of setting
    'Width and Height separately. I did it this way for clarity.

    Picture1.Width = UserControl.ScaleWidth
    Picture1.Height = (UserControl.ScaleHeight / 3) * 2
    lblCaption.Width = UserControl.ScaleWidth
    lblCaption.Height = UserControl.ScaleHeight / 3
    lblCaption.Top = Picture1.ScaleHeight + 60
End Sub

This code will ensure that the width and height of the HappyHour control will always be slightly less than the overall width of the control.

NOTE
In the preceding code example, you use the ScaleWidth and ScaleHeight properties of the UserControl because you don't know whether the user will reset the ScaleMode property to a measurement value other than the standard Visual Basic unit of measurement (the twip, equal to 1/1440th of an inch). Your control doesn't have a ScaleMode property yet, but writing code like this protects you in case you ever decide to give it one.

You can see how this code works if you close the control designer, return to Form1, and stretch the control HappyHour1. You should be able to see that the PictureBox and the Label stretch proportionately.

Supplying a Default Caption Using the Extender Object

Controls that have a Caption property should supply a default value for their captions. The default caption for a CommandButton is Command1, the default caption for a Label is Label1, and so forth. You can supply the same functionality in your control by reading a property of the Extender object in your UserControl's InitProperties event.

Extender properties are supplied by the container in which your constituent control resides. So if you set the default Caption property of the HappyHour control to be equal to Extender.Name, you can achieve the effect that users expect. To do this:

  1. Close Form1, if it is open, and reopen HappyHour.ctl.
  2. Double-click on HappyHour's designer to open its code window.
  3. In the Procedures combo box of the UserControl code window, choose InitProperties.
  4. Type the following code:
Private Sub UserControl_InitProperties()
    Caption = Extender.Name
End Sub

  1. Close the control designer and return to Form1.
  2. Now you'll have to re-create the HappyHour control to see the default you just created. To do this, click on HappyHour1, then press Delete.
  3. Create another instance of HappyHour1 on your form by double-clicking on its icon in the Toolbox. You should be able to see that the default Caption property for the new instance of HappyHour1 has been set properly, as illustrated in Figure 3.14.

Figure 3.14: HappyHour control with default caption set.

Storing Design-Time Property Changes

You've probably noticed that the changes you've been making to the HappyHour1's Caption property have vanished into the ether each time you flip between the control designer and Form1-specifically, the Caption property has been wiped out each time you make a change to the control designer.

You can make design-time property settings persistent by writing code in the WriteProperties event of the UserControl. The WriteProperties event is triggered each time the user's design-time property settings need to be saved.

Property settings are made persistent by the use of the PropertyBag object. You have the ability to put data into the PropertyBag and take data out of the PropertyBag. The WriteProperty method of the PropertyBag object stores a property in the PropertyBag, while the ReadProperty method does the opposite. Either way, Visual Basic is left holding the PropertyBag, which is a good thing.

To make your control save its design-time Caption property, do the following:

  1. Close Form1, if it is open, and open the HappyHour control designer.
  2. Double-click on the control designer to expose its code window.
  3. In the Procedure combo box at the upper right side of the code window, select WriteProperties.
  4. Into the code window, insert the following code:
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "Caption", Caption, Extender.Name
End Sub

    This code uses the WriteProperty method of the PropertyBag object to save the value of the Caption property of your control. There's more information on how the PropertyBag works in Chapter 4, "Control Properties."

Conversely, just as you must write code to save a design-time property change to the PropertyBag, you must also write code to retrieve previously set properties from the PropertyBag in your control's ReadProperties event. To do this, add the following code to your code window:

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    Caption = PropBag.ReadProperty("Caption", Extender.Name)
End Sub

NOTE
The value for Extender.Name is a parameter to the WriteProperty and ReadProperty methods so those methods can know whether the design-time properties have been changed by the user. If the properties haven't been changed (that is, they're equal to Extender.Name) then there's no need to write a specific entry to the PropertyBag. This makes the process of managing saved property settings more efficient.

Your control should now have the ability to retain the properties set for it at design time. To test this:

  1. Close the code window and the form designer.
  2. Open Form1.
  3. Set the Caption property of the HappyHour control to the text of your choice.
  4. Close Form1.
  5. Re-open Form1. You should be able to see that the change you made in the Caption property has been retained.

Exposing Properties Using the ActiveX Control Interface Wizard

Now that you know how things work under the hood, try exposing a property of a constituent control the easy way, using the ActiveX Control Interface Wizard. This tool takes care of much of the busywork involved in control creation, particularly when your control is primarily comprised of one or more constituent controls.

You can use the ActiveX Control Interface Wizard to delegate a Picture property for your control to the constituent PictureBox control. To do this:

  1. From the Add-Ins menu, select ActiveX Control Interface Wizard. The Wizard launches, as shown in Figure 3.15.
    Figure 3.15: The ActiveX Control Interface Wizard.

  2. Click on Next.
  3. The Select Interface Members screen appears. In this panel, you choose the interface elements (properties, methods, and events) you want your control to have.
  4. On the left side of this screen is a list, labeled Available names, of interface elements that can be delegated to constituent controls. Scroll down the list until you find the Picture property.
  5. Double-click on the Picture property to add it to the Selected names list, as shown in Figure 3.16.
    Figure 3.16: The Select Interface Members window.

  6. Click on Next.
  7. The Create Custom Interface Members window appears (see Figure 3.17).
    Figure 3.17: The Create Custom Interface Members window.

  8. You won't be adding any custom elements to the interface for now, so click on Next once again.
  9. The Set Mapping screen appears.
  10. Here you will delegate the Picture property of your control to the Picture property of the constituent PictureBox control. To begin, click on Picture property in the Public Name list.
  11. In the Control combo box in the Maps To panel, select Picture1. The Member combo understands that you're trying to map your control to the Picture property of Picture1, so it displays the Picture property.
  12. The Set Mapping screen should look like Figure 3.18.
    Figure 3.18: The Set Mapping screen.

  13. Click on Next.
  14. The Set Attributes window appears, as shown in Figure 3.19.
    Figure 3.19: The Set Attributes window.

  15. Your control doesn't have any unmapped members, so click on Next.
  16. The Finished! window appears. Make sure the View Summary Report checkbox is selected, as shown in Figure 3.20. Then click on Finish.

Figure 3.20: Finished!.

The wizard now goes to work, writing the code that will support the property mapping you asked for. When it is done writing code, the wizard displays a summary reminding you of what you need to do to complete your control project (see Figure 3.21). Note this information, then click on Close.

Figure 3.21: Interface Wizard Summary.

Open the HappyHour control and double-click on its designer to view its code. You should be able to see that the following code has been added to your project:

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty 'Caption&', Caption, Extender.Name
    Call PropBag.WriteProperty("Picture", Picture, Nothing)
End Sub

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    Caption = PropBag.ReadProperty("Caption", Extender.Name)
    Set Picture = PropBag.ReadProperty("Picture", Nothing)
End Sub

The WriteProperty and ReadProperty methods inserted by the wizard are essentially identical to the code you wrote for the Caption property; the wizard just uses an alternate syntax.

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

Public Property Set Picture(ByVal New_Picture As Picture)
    Set Picture1.Picture = New_Picture
    PropertyChanged "Picture"
End Property

The only really new thing here is the Property Set procedure. Property Set (rather than Property Let) is required here because the Picture property is an object, rather than a conventional value.

Testing the New Code

This code enables you to include a picture in your control at design time. To test this, you'll need some graphics file on your hard disk.

  1. Close the code window and the designer for the HappyHour control.
  2. Open Form1.
  3. Select the control HappyHour1, then look at the Properties window. You should be able to see that HappyHour1 now has a Picture property.
  4. Click on None next to the Picture property, then click on Browse.
  5. A file open dialog box appears. Locate any graphic file (such as Windows Bitmap, .GIF, .JPG) on your disk. If you're stuck, try my personal favorite, the file cars.bmp in your Windows folder.

NOTE
Support for GIF and JPEG images is a new feature in Visual Basic 5.0.

  1. Click on the file, then click on Open. The graphic is assigned to your control's Picture property. See Figure 3.22 for the result.

Figure 3.22: The finished product.

Summary

In this chapter you stepped through the creation of a very simple control. You added properties using two methods: manually, by writing code, and automatically, using the ActiveX Control Interface Wizard.

The next few chapters will discuss control properties, events, and methods in more detail. This discussion will enable you to create a richer, more full-featured control.