Notification (or Events)

There are many times when objects can perform some action in response to a property being set or a method being called. This has traditionally been difficult to implement, since an object has usually needed to notify the calling program that the action has taken place.

In the previous section, we discussed the invoice object and how it can detect when a video is added or removed from the list of rentals. Either of these conditions should cause the subtotal, tax and total amounts to be recalculated. The challenge is that the UI needs to be notified that these values have changed so that the display can be updated.

Traditional Solutions

Traditionally, this would have been handled either by having the UI refresh the values when it added or removed a video, or by calling a special Calculate method to force the calculation.

If the UI updates the values when adding or removing a video, we've effectively coded some business logic into the UI. After all, the UI would have to know all the conditions under which the values might change, so that it could know when to refresh the display.

Using an explicit Calculate method is also far from ideal. If we have such a method, then we're relying on the UI code to call it, rather than recalculating our values automatically. If the UI doesn't call the method when appropriate, we could end up in a situation where the subtotal, tax, and total properties on the object would be incorrect. This solution can not only lead to bugs, but it also requires that the UI programmer must have more knowledge about how our object works than we might otherwise prefer (such as a more appropriate time to call the Calculate method).

Callbacks

A more complex solution to the problem would be to have the invoice object call a method of another object and have that object update the UI. This solution solves most of our problems, but it's definitely harder to implement for the UI developer.

To implement this, the UI itself would have to create an object to receive the message sent by the invoice object. Of course, the invoice object would need to know about the UI's object so that it could call it; we'd therefore need to add a method to the invoice object so that the UI could pass a reference to its object!

Once the invoice object had a reference to the UI's object (let's call it Displayer), it could call some predefined method on the Displayer object whenever a message needed to be sent to the UI. This is shown in this diagram:

Let's look through some code that could handle this.

Displayer Object

First off, our UI developer will need to provide an object to update the display. Create an ActiveX DLL project, create a new class module called Displayer, and add this sample code:

Option Explicit

Public Sub UpdateValues()
  ' in this routine we'd update our display
  ' with the properties from the Invoice
  ' object
End Sub

This example assumes that we've implemented our business objects in an ActiveX DLL, and that the UI is in a different EXE or DLL. We'll keep our Displayer class in a separate ActiveX DLL for now, which will allow us to create a simple UI test harness in a Standard EXE. (Alternatively, if we had created our UI in a ActiveX DLL, then the Displayer class could have been included within the main UI project.)

The Displayer class must also have its Instancing property set to 5 - MultiUse so that we can make it available to the Invoice object.


The upshot of all this is that if we did want our Displayer class to be within the main UI program, then our UI project would need to be an ActiveX server - whereas it may have only needed to be a regular program prior to the introduction of the callback mechanism.

Invoice Objects

Within the main DLL project for our business objects, the Invoice class also needs some special code to support the callback. It needs a method that will allow the UI to give it a reference to the Displayer object.

For our current example, add a sample DLL for our business objects, using the File-Add Project menu option, then add a class module, using the Project-Add Class Module menu option, and call that class module Invoice. Now add the following sample code to Invoice:

Option Explicit

Private objCallback As Object

Public Sub SetCallback(Display As Object)
  Set objCallback = Display
End Sub

Performing the Callback

Then, in the routine where the Invoice object does the calculations, we need to call the UpdateValues method of the Displayer object:

Public Sub Calculate()
  ' here we'd do the calculations
  
  objCallback.UpdateValues
End Sub

Releasing Object References

Finally, we need to make sure that the Invoice object releases its reference to the Displayer object before the Invoice goes away. If we don't do this, we run the risk of having objects hanging around in memory after we've tried to close down the program:

Private Sub Class_Terminate()
  Set objCallback = Nothing
End Sub

The UI Code

Now that all that has been set up, we can write some UI code that creates the Invoice object. Since we're going to use a Standard EXE to hold our sample UI and test our callback mechanism, add a Standard EXE project using the File-Add Project menu option, add a standard module to that project, using the Project-Add Module menu option, and enter this example code:

Private objInvoice As Invoice
Private objDisplayer As Displayer
Set objDisplayer = New Displayer
Set objInvoice = New Invoice

objInvoice.SetCallback objDisplayer
objInvoice.Calculate

Notice that, as well as creating an Invoice object, we create a Displayer object, and give the Invoice object a reference to it.

You'll need to make a reference to the Invoice object from the UI project. Select the UI project in the Project window, and use the Project-References menu option to select the project containing our Invoice object.

Since we've created a separate EXE for the UI test harness, we also need to add references to the projects containing the Displayer object and the Invoice object. Select the UI EXE project in the Project window, and use the Project-References menu option to select the appropriate references to the other projects.

If you run the sample program as it stands, you won't see much happen: but if you step through the program execution, you'll see the callback mechanism alive and well. The UI code successfully establishes the Displayer callback method to itself before it calls the Invoice object's Calculate method. Within the Calculate method, the callback is then made to the Displayer class's UpdateValues routine.

This solves our problem, in that it allows an object to notify our program when something has happened. In our example, the Invoice object notifies the UI object that Calculate method has been called. In some ways, this is very attractive, since the interaction between the two objects is very well defined, and the UI can give the Invoice object a reference to any object that implements an UpdateValues method. Since this technique uses regular method calls between the two objects, we can write our code to pass any parameters that our program might need.

On the other hand, this solution is somewhat complex: it forces the UI developer to add a new class to the project, or make the UI program an ActiveX server. It also tightly couples the business object to the UI implementation, since it requires that the UI provide an object with a specific method.

Raising Events

Visual Basic 5.0 introduced the ability to raise events from our objects. This means that we can write code in our class module that will raise an event in the calling code, just as a button control can raise a Click event in a form.

This is done by introducing three new keywords to the Visual Basic 5.0 language. In our class module, we need to use the Event and RaiseEvent keywords, respectively, to declare and raise events. In the calling code (Form or Class module), we need to declare the object using the new WithEvents keyword.

Sticking with the Invoice object example, we could have the Invoice object raise an event when the subtotal, tax and total amounts have changed. Then the UI programmer would just need to write code behind the event to update the display. This is a very nice model, since all Visual Basic programmers are familiar with writing code to respond to events - so the learning curve for the UI programmer is very small.

Invoice Objects

Inside our Invoice object, we need to have a line to declare our event:

Public Event UpdateValues()

Then in the routine where we calculate the amounts, we can add a line to raise the event:

Private Sub Calculate()

  ' here we'd do the calculations

  

  RaiseEvent UpdateValues

End Sub

All references to our objCallback object are now unnecessary, and we no longer need our SetCallback routine, since we've managed to create the same mechanism using events.

The UI Code

The UI programmer just needs to make a change to the declaration of the Invoice object to include the WithEvents keyword:

Private WithEvents objInvoice As Invoice

At this point, the objInvoice object will appear in the Object dropdown in the upper-left of the code window, along with any other controls or objects available to the module. If we choose objInvoice, then the Procedure dropdown in the upper right will list all the events that are declared in the Invoice class - just UpdateValues in this case. This means we can write a regular event procedure:

Private Sub objInvoice_UpdateValues()
  ' here we'd put the code to update the
  ' values on the display
End Sub

All references to our objDisplayer object are now unnecessary; and, since we no longer need our Displayer class module, the UI no longer needs to be an ActiveX server.

This solution is not very different from implementing a callback, but it does have some significant advantages. The UI programmer doesn't need to implement a special class. In fact, the UI programmer can draw on existing knowledge and techniques to write code behind the object's events - just as if they were events from a control on a form. Furthermore, the Invoice object author doesn't need to worry about being passed some external object, or any specifics about the calling program at all. They may appreciate that.

The bottom line is that the RaiseEvents command works independently of whether anyone actually receives the event that's raised. This means that we've effectively separated the UI programmer's tasks from the object developer's tasks.

© 1997 by Wrox Press. All rights reserved.