An object that raises events is called an event source. To handle the events raised by an event source, you can declare a variable of the object's class using the WithEvents keyword.
This topic continues the Widget object example begun in "Declaring and Raising Events." To handle the PercentDone event of a Widget, place the following code in the Declarations section of Form1:
Option Explicit
Private WithEvents mWidget As Widget
Private mblnCancel As Boolean
The WithEvents keyword specifies that the variable mWidget
will be used to handle an object's events. You specify the kind of object by supplying the name of the class from which the object will be created.
The variable mWidget
is declared in the Declarations section of Form1 because WithEvents variables must be module-level variables. This is true regardless of the type of module you place them in.
The variable mblnCancel
will be used to cancel the LongTask method.
You should be aware of the following limitations on the use of WithEvents variables:
As soon as you declare a variable WithEvents, the variable name appears in the left-hand drop down of the module's code window. When you select mWidget
, the Widget class's events will appear in the right-hand drop down, as shown in Figure 9.9.
Figure 9.9 An event associated with a WithEvents variable
Selecting an event will display the corresponding event procedure, with the prefix mWidget_
. All the event procedures associated with a WithEvents variable will have the variable name as a prefix. Add the following code to the mWidget_PercentDone event procedure.
Private Sub mWidget_PercentDone(ByVal Percent As _
Single, Cancel As Boolean)
lblPercentDone.Caption = CInt(100 * Percent) & "%"
DoEvents
If mblnCancel Then Cancel = True
End Sub
Whenever the PercentDone event is raised, the event procedure displays the percent complete in a Label control. The DoEvents statement allows the label to repaint, and also gives the user the opportunity to click the Cancel button. Add the following code for the Click event of the button whose caption is Cancel.
Private Sub Command2_Click()
mblnCancel = True
End Sub
If the user clicks the Cancel button while LongTask is running, the Command2_Click event will be executed as soon as the DoEvents statement allows event processing to occur. The module-level variable mblnCancel
is set to True, and the mWidget_PercentDone event then tests it and sets the ByRef Cancel argument to True.
Form1 is all set up to handle a Widget object's events. All that remains is to find a Widget somewhere.
When you declare a variable WithEvents at design time, there is no object associated with it. A WithEvents variable is just like any other object variable. You have to create an object and assign a reference to the object to the WithEvents variable. Add the following code to the Form_Load event procedure to create the Widget.
Private Sub Form_Load()
Set mWidget = New Widget
End Sub
When the code above is executed, Visual Basic creates a Widget and connects its events to the event procedures associated with mWidget
. From that point on, whenever the Widget raises its PercentDone event, the mWidget_PercentDone event procedure will be executed.
To call the LongTask method, add the following code to the Click event of the button whose caption is Start Task.
' Start Task button.
Private Sub Command1_Click()
mblnCancel = False
lblPercentDone.Caption = "0%"
lblPercentDone.Refresh
Call mWidget.LongTask(14.4, 0.66)
If Not mblnCancel Then lblPercentDone.Caption = 100
End Sub
Before the LongTask method is called, the label that displays the percent complete must be initialized, and the module-level Boolean flag for canceling the method must be set to False.
LongTask is called with a task duration of 14.4 seconds. The PercentDone event is to be raised once every two-thirds of a second. Each time the event is raised, the mWidget_PercentDone event procedure will be executed.
When LongTask is done, mblnCancel
is tested to see if LongTask ended normally, or if it stopped because mblnCancel
was set to True. The percent complete is updated only for the former case.
Press F5 to put the project in Run mode. Click the Start Task button. Each time the PercentDone event is raised, the label is updated with the percentage of the task that's complete. Click the Cancel button to stop the task. Notice that the appearance of the Cancel button doesn't change immediately when you click it. The Click event can't happen until the DoEvents statement allows event processing.
You may find it instructive to run the program with F8, and step through the code a line at a time. You can clearly see how execution enters LongTask, and then re-enters Form1 briefly each time the PercentDone event is raised.
What would happen if, while execution was back in Form1's code, the LongTask method was called again? Confusion, chaos, and eventually (if it happened every time the event was raised) a stack overflow.
You can cause the variable mWidget
to handle events for a different Widget object by assigning a reference to the new Widget to mWidget
. In fact, you can make the code in Command1 do this every time you click the button, by adding two lines of code:
Set mWidget = New Widget '<- New line.
Call mWidget.LongTask(14.4, 0.66)
Set mWidget = Nothing '<- New line.
The code above creates a new Widget each time the button is pressed. As soon as the LongTask method completes, the reference to the Widget is released by setting mWidget
to Nothing, and the Widget is destroyed.
A WithEvents variable can only contain one object reference at a time, so if you assign a different Widget object to mWidget
, the previous Widget object's events will no longer be handled. If mWidget
is the only object variable containing a reference to the old Widget, the object will be destroyed.
Note You can declare as many WithEvents variables as you need, but arrays of WithEvents variables are not supported.
As long as there is a Widget object assigned to the variable mWidget
, the event procedures associated with mWidget
will be called whenever the Widget raises an event. To terminate event handling, you can set mWidget
to Nothing, as shown in the following code fragment.
' Terminate event handling for mWidget.
Set mWidget = Nothing
When a WithEvents variable is set to Nothing, Visual Basic disconnects the object's events from the event procedures associated with the variable.
Important A WithEvents variable contains an object reference, just like any other object variable. This object reference counts toward keeping the object alive. When you are setting all references to an object to Nothing in order to destroy it, don't forget the variables you declared WithEvents.
For More Information The event procedures associated with WithEvents variables look a lot like event procedures for controls on forms. "Comparing WithEvents to Control Events on Forms" discusses the similarities and differences.