Chapter 5

Handling and Raising Events


CONTENTS

When your control raises an event, it gives programmers an opportunity to do something interesting. Controls with rich event models represent the difference between a do-nothing file viewer and a fully-featured piece of component software. This chapter discusses how to create custom events in your ActiveX control.

Additionally, this chapter discusses the events triggered by the UserControl object itself.

Creating Custom Events

Unlike custom properties, which were available in VB 4.0 classes, custom events are a new feature in Visual Basic 5.0. Events are a way to permit users to write code to hook into things that your control does.

For example, consider the HappyHour control we started building in Chapter 3 This control, which is comprised of a PictureBox and a Caption control, is designed to reside in a Web page. In order to receive regularly updated graphical and textual information, the HappyHour control might be modified to reload picture or text information on a regular basis-every fifteen minutes, for example. In order to give users of your control the ability to run other code in response to this, you instruct your control to raise an event, called Updated, to be triggered every time the control has finished re-downloading new data.

The user could then hook into the Updated event to cause some other action to take place in her program; an audio beep, or perhaps a pop-up dialog box or other interface element to let the user know that the data has been updated.

Understanding the Syntax of Event and RaiseEvent

You create a custom event using an event declaration in the Declarations section of a code module. Similar to a variable declaration, an event declaration denotes the name of the event procedure and, optionally, any parameters that are passed to it.

After you've declared a custom event, you can trigger the event using the RaiseEvent statement. The syntax of an event declaration looks like this:

Public Event EventName([ByVal variable As datatype])

Once you've declared an event, you can refer to it in your code by using the RaiseEvent statement. The syntax of the RaiseEvent statement looks like this:

RaiseEvent event_name

The argument event_name must, of course, be the same as the name of the event in the event declaration.

Creating an Event

In this section you'll add an event to the HappyHour project. This project is an updated version of the HappyHour control discussed in Chapter 3 (In case you skipped over the walk-through in Chapter 3, the HappyHour control is designed to provide graphical and textual notification to members of a company that it is time for happy hour.)

The major change in this version of the HappyHour control is the addition of a Timer control and two new properties, HappyHourStart and HappyHourEnd. Since the addition of these new elements of the control don't introduce any significant new elements to the HappyHour control, I've included them for you in the updated version of the HappyHour control on your CD-ROM.

The HappyHourStart and HappyHourEnd properties store user-defined start and end times for HappyHour. For the purposes of our demonstration, we'll assume that happy hour begins at 5:00 PM and ends at 6:00 PM (although the way it's set up, the user can change that to any values she wants).

In order to implement this, the user sets the HappyHourStart property to 5:00 PM and the HappyHourEnd property to 6:00 PM.

The Timer control checks the system time once per second, comparing the current time against the user-set values for HappyHourStart and HappyHourEnd. If it's happy hour, the control fires the HappyHourStart event. The user can then use the HappyHourStart event to do anything she wants-make the computer play a sound or change the Picture and Caption properties of the control. To do this:

  1. Open the control designer for the HappyHour control. (This control project is on the CD-ROM that accompanies this book.) You should be able to see that in this version of the control, a Timer has been added to the control (Figure 5.1).
  2. Double-click on the control designer to open its code window.
  3. Using the Object combo box at the top of the code window, switch to the General section of the code. This should move your cursor to the top of the code window, if you're in Full Module view.
  4. Insert the HappyHourStart event declaration by writing the following code in the Declarations section of the code window:

Figure 5.1 : Revised version of the HappyHour control.

' Declarations

Public Event HappyHourStart()

Your event has now been declared and is eligible to be used in your code. Since this event will be raised as a condition of the current time, you'll write code to raise the event in the Timer event of the Timer control. To do this:

  1. Using the Object combo box, switch to the code for Timer1.
  2. The Timer event procedure appears. (This is the only event raised by the Timer control.) Add the code in the listing below.
Private Sub Timer1_Timer()
' Triggered once per second.
' Compares current time to
' happy hour time. If it's happy
' hour, raise the HappyHourStart event.

' bail out if it's already happy hour
    If mHappyHour Then
        Exit Sub
    End If

' bail out if happy hour hasn't
' been defined yet
    If mHappyHourBegin = "" Or mHappyHourEnd = "" Then
        Exit Sub
    End If

' check to see if it's happy hour now
    If Time > CDate(mHappyHourBegin) And Time < CDate(mHappyHourEnd)_
Then
        mHappyHour = True
        RaiseEvent HappyHourStart
    Else
        mHappyHour = False
    End If

End Sub

The variable mHappyHour is an internal flag that indicates to the Timer event whether it's currently happy hour or not. This flag exists because it wouldn't make sense for the HappyHour control to raise the HappyHourStart event once per second during happy hour; instead, if the Timer event sees that it is happy hour already, it aborts without raising the HappyHourStart event.

Testing The HappyHourStart Event

Once you've entered the above code, you can test it by going through the following steps:

  1. Set your computer's system clock to 5:00 PM. You can do this by using the Date/Time settings in Control Panel.
  2. Close the HappyHour control designer, if it is open.
  3. Open HHTestForm.frm, the EXE project test form for the HappyHour project.
  4. Double-click on HappyHour1. The HappyHourStart event should appear in the code window. Enter the following code:
Private Sub HappyHour1_HappyHourStart()
    HappyHour1.Caption = "It is now happy hour!"
    Set HappyHour1.Picture = LoadPicture("d:\Code\Chapter 05\_ 
HappyHour2\After\happy.bmp")
End Sub
  1. Launch the program by clicking on the Run button on the toolbar, or by pressing the function key F5.
  2. As soon as the program runs, the HappyHour control should immediately trigger the HappyHourStart event and change the picture and caption. The running program should look like Figure 5.2.

Figure 5.2 : Test program after the HappyHourStart event is triggered.

Creating an Event That Includes a Parameter

Sometimes it's useful for events raised by a control to pass additional information to event procedures. Controls do this in the form of event parameters. Most events do not have parameters, but it's useful to include them when necessary.

For example, consider the MouseDown event of most interface controls. It is not enough that Visual Basic simply indicates that the user has pressed the mouse key. Your program must also know where and how the mouse was clicked.

This is why the MouseDown event receives the parameters of Button, Shift, X, and Y. You should be familiar with these types of events already, but to illustrate this more clearly, the code below gives an example of the first line of a MouseDown event procedure for a PictureBox control.

Private Sub Picture1_MouseDown(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)

The Button parameter tells the event which mouse button was pressed. The Shift parameter tells whether a key on the keyboard (either Shift, Ctrl, Alt, or some combination of these) was pressed. The X and Y parameters tell the event where on the PictureBox control the user pressed the mouse.

When events raised by your control pass arguments in this way, the RaiseEvent procedure specifies what the parameters are. To create an event that passes parameters in your example project, begin by declaring a HappyHourChanged event:

Public Event HappyHourChanged (bHappyHourStatus As Boolean)

Next, modify the Timer event of Timer1 to raise the new HappyHourChanged event, rather than the HappyHourStart event, using this code:

Private Sub Timer1_Timer()
' Triggered once per second.
' Compares current time to
' happy hour time. if it's happy
' hour, raise the appropriate event.

' happy hour hasn't been set yet
If mHappyHourBegin = "" Or mHappyHourEnd = "" Then
    Exit Sub
End If

' check to see if it's happy hour now
    If Time > CDate(mHappyHourBegin) And Time < CDate(mHappyHourEnd)_
Then
        
        If mHappyHour = False Then
            RaiseEvent HappyHourChanged(True)
            mHappyHour = True
        Else
            ' it's already happy hour
        End If
    Else
        If mHappyHour = True Then
            RaiseEvent HappyHourChanged(False)
            mHappyHour = False
        End If
    End If

End Sub

The Timer event still evaluates whether it's happy hour, but now instead of merely triggering the HappyHourStart event (which would lock the employees of our company into a perpetual state of happiness), it calls the HappyHourChanged event, passing the event the Boolean parameter of True or False depending on whether it's happy hour or not.

You can now take advantage of that Boolean parameter in the HappyHourChanged event. To do this:

  1. Close the control designer and open the HHTestForm project on the CD-ROM that accompanies this book.
  2. Double-click on HappyHour1 to open its code window.
  3. Delete the event procedure HappyHourStart.
  4. In the HappyHourChanged event procedure, enter the following code:
Private Sub HappyHour1_HappyHourChanged(HappyStatus As Boolean)

Select Case HappyStatus
    Case True
    HappyHour1.Caption = "It is now happy hour!"
    ' Change the following filename to match your system's configuration
    Set HappyHour1.Picture = LoadPicture("d:\Code\Chapter 05\HappyHour2\After\_
happy.bmp")
    
    Case False
    HappyHour1.Caption = "It is not happy hour yet. Get back to work."
    ' Change the following filename to match your system's configuration
    Set HappyHour1.Picture = LoadPicture("d:\Code\Chapter 05\HappyHour2\After\_
work.bmp")
    
End Select

End Sub

    (You'll want to change the filename "d:\work\work.bmp" to reflect the location of the file work.bmp on your system.)

  1. Using the Date/Time icon in the Control Panel, set your system clock to 5:59 PM.
  2. Run the program. You should be able to see the control change to the happy hour state as soon as the program runs. When your system clock changes to 6:00 PM, the control should change back to its work state.

Raising Events of Constituent Controls

The preceding example demonstrates how to modify the Timer event of the constituent Timer control for a specific purpose. But if you are interested in simply passing a constituent control's event through to your UserControl, it's easy to do. Simply raise the constituent control's event to the UserControl level by using the RaiseEvent statement.

Events that are passed through to constituent controls are said to be forwarded. Here is an example of how a forwarded event might work in the HappyHour control. In this case, we're forwarding the Click event of PictureBox control:

Public Event Click()    ' This goes in the Declarations section

Private Sub Picture1_Click()
    RaiseEvent Click
End Sub

Note that as written, this code will only raise the Click event if the user clicks on the PictureBox portion of the HappyHour control; it will not raise the event if the user clicks on the Label portion. To cause the Click event to be raised when the user clicks on any portion of the control, add a RaiseEvent to the Label's Click event as well (thereby forwarding the Click event of the Label control):

Private Sub lblCaption_Click()
    RaiseEvent Click
End Sub

Providing Standard Events

There is a set of events that users expect in practically every control. These events are:

Providing this core set of recommended events will go a long way toward making your control's programmable interface more intuitive. And, of course, providing more than this basic set of events will make your control more flexible for the programmers that use it.

Of course, you're not required to provide any of these events if they don't make sense in the context of your control. For example, a control that is meant to be clicked (such as a CommandButton) doesn't need to have a DblClick event (indeed, it would be difficult to implement a DblClick event in such a control, since its Click event would be triggered the first time a user clicked it).

Specifying a Default Event

You can specify that a particular event in your control's event model is the default event for that control. The default event is the first event that appears in a code window.

For example, consider the PictureBox control. When you instantiate a PictureBox on a form at design time and double-click on it, the code window opens to the picture box's Click event. This happens because Click is the default event for a PictureBox control. To designate a default event for your control:

  1. Select the menu commands Tools, Procedure Attributes.
  2. The Procedure Attributes dialog box appears. In the Name combo box, select the event you want to serve as your control's default event.
  3. Click on the Advanced button.
  4. The Procedure Attributes dialog expands. Activate the User Interface Default box.
  5. Click on OK.

It's never mandatory to provide a default event for your control, but it makes things a little easier on your users. Most commercial controls designate a default event, so your users will expect you to provide one, too. If you don't provide an event, the first event to be displayed in the code window is the event that comes first in alphabetical order.

Understanding Container-Provided Events

The Extender object of your control's container can automatically provide a number of events for your control. However, bear in mind that not all containers are the same. In Visual Basic, the container provides the following events:

Because they are provided by the container, you don't have to write any code to enable the user to hook into these events; they're there inherently.

However, you need to remember that you can't count on these events being provided by the container. If you raise an event that you expect to be provided by the container's Extender object, use error-trapping just in case your control is placed into a container that doesn't raise the event you expected.

See Chapter 7 "Interacting with the Container," for more details on the container. See Chapter 15, "Debugging and Error Trapping," for more information on error-trapping.

Events of the UserControl Object

In order to be able to provide a number of standard features of an ActiveX control, it's important to understand the events that it triggers during its lifetime. This is different than the events that your control raises; the events of the UserControl object are analogous to events of the Visual Basic form such as Load, Unload, and Activate.

The events of the UserControl object are summarized in Table 5.1.

Table 5.1 Events of the UserControl Object

EventOccurs When
InitPropertiesThe user places the control on the container for the first time. This event is only triggered once in the lifetime of the control. It is used to set the initial values for a control's properties.
InitializeAn application creates an instance of a UserControl. The Extender and Ambient objects are not available to this event. This is the first event triggered by a control; it is triggered numerous times in the control's lifetime.
ReadPropertiesAn old instance of a control is re-instantiated. This is where you read design-time properties from the PropertyBag and reassign them to your control.
ResizeThis occurs after the control appears and whenever its size is changed.
PaintThis occurs when the control needs to redraw itself.
WritePropertiesThe design-time properties of the control need to be saved using the PropertyBag object. This event is only triggered at design time (because run-time properties of the control aren't saved via the PropertyBag).
TerminateAll references to a UserControl are set to Nothing or when the last reference to the object falls out of scope. This occurs as the control is about to be destroyed.

TIP
The Load and Unload events you're accustomed to working with in Visual Basic forms aren't present in control designers. The analogous events of the UserControl are the ReadProperties and WriteProperties events.

Although the table makes the chain of events look deceptively simple, bear in mind that these events get triggered numerous times-often in seemingly counter-intuitive ways-during the development and deployment of your control. Part of the reason why the events are triggered in ways you might not expect is because the UserControl is destroyed and re-created by Visual Basic behind the scenes while you move through the development-testing-debugging-refinement cycle of control creation.

For example, if you create a UserControl, then instantiate it on an EXE project form, go back to the control designer in order to make changes to it, then return to the EXE project form, the control will have been destroyed and re-created by Visual Basic. This is to insure that the changes to the control are reflected in the instantiation of the control on your EXE project form.

Controls that are placed on a form at design time are destroyed, then re-created, when the user runs the Visual Basic EXE project. Because this process is performed seamlessly behind the scenes by Visual Basic, it might seem strange that so many Terminate events occur in your UserControl.

Table 5.2 provides a step-by-step narrative that should give you a better idea of how and when these events are triggered.

The example project HappyHour2 on your CD-ROM has Debug.Print statements in all the important UserControl events, so you can see the events explained in Table 5.2 in action.

Table 5.2 Events in the Lifetime of a Typical UserControl Object

ActionEvents Triggered
You instantate a control on an EXE project form. Initialize
InitProperties
You open a form containing a previously-instantiated control. Initialize
ReadProperties
You alter the control's designer, then return to the EXE project form. Initialize
ReadProperties
You run the EXE program.WriteProperties
Terminate
Initialize
ReadProperties
You halt the EXE program.Initialize
ReadProperties
You delete the instance of the control from the form. WriteProperties
Terminate

NOTE
Because there is no such thing as "design-time" on a Web page, controls that reside on Web pages don't go through the same life cycle as controls that reside in Visual Basic applications. Controls that live in Web pages are always treated as if they are newly instantiated each time they appear; consequently, they trigger Initialize, InitProperties, Resize, and Paint events. For more information on how your controls behave in Web pages, see Chapter 13, "Deploying Your Control on the Web."

Summary

This chapter explored how to raise events in your control. We covered both event declaration and implementing a real-world event in a control.

We also covered the event model of the UserControl object itself. In addition, we went over how you can use such events to manage properties and other run-time and design-time attributes of your control.

In the next chapter, you'll learn how to provide custom methods in your controls.