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.
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.
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:
The argument event_name must, of course, be the same as the name of the event in the event declaration.
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:
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:
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.
Once you've entered the above code, you can test it by going through the following steps:
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
Figure 5.2 : Test program after the HappyHourStart event is triggered.
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:
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
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
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).
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:
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.
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.
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.
|InitProperties||The 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.|
|Initialize||An 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.|
|ReadProperties||An 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.|
|Resize||This occurs after the control appears and whenever its size is changed.|
|Paint||This occurs when the control needs to redraw itself.|
|WriteProperties||The 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).|
|Terminate||All 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.|
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.
|You instantate a control on an EXE project form.||Initialize|
|You open a form containing a previously-instantiated control.||Initialize|
|You alter the control's designer, then return to the EXE project form.||Initialize|
|You run the EXE program.||WriteProperties|
|You halt the EXE program.||Initialize|
|You delete the instance of the control from the form.||WriteProperties|
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."
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.