Combining RaiseEvents and WithEvents

By John Colby

Demo

 

WithEvents is a very powerful technique for allowing us to encapsulate all of the generic code for a given control or controls including the event stubs themselves. However, there is a “gotcha” with all of this control:  when an event fires, Access sinks that event as follows:

 

·         In the form module provided an event stub exists in the module.

·         After that, the event is sunk in any and all classes that Dim the control using WithEvents and which have an event sink in the class.

 

Consequently, several different class instances can sink the same event. The order that they run is determined by the instantiation order, last instantiated first run (for A2K). 

 

But what happens if processing needs to occur in a form after the class sinks an event and runs its code?  For instance, perhaps the class processes data and the form then needs to immediately use that processed data. How do you return control to the form? 

 

The answer is RaiseEvent. Any class module can declare an event that it will raise, and which can be sunk (handled) in any other class that dimensions a pointer to the class “WithEvents”. This allows us to sink the AfterUpdate of a text box or other control.When we finish processing the AfterUpdate in the class, we can RaiseEvent AfterUpdate. The form that instantiated the class to deal with the text box then has an event stub, which sinks the class’ AfterUpdate event. After the text box’s AfterUpdate fires:

 

 

1.       The form may have an event sink that sinks the text box’s AfterUpdate. It gets first shot at the event.

 

2.       The text box’s class has an event sink that sinks the text box’s AfterUpdate. It gets second shot at the event.

 

3.       The class Raises the AfterUpdate event.

 

The form may have an event sink that sinks the class’ AfterUpdate event. This is not really the original event, but it is raised as a result of the original event and thus appears to be that event. You can even pass one or more variables back to the form.

 

CAUTION: Visual Basic is not very thread-oriented (executes code serially) and you can lock up your program if a responder sinking an event goes into an endless loop. Also think about response time when you do take advantage of this kind of behavior. Basically, the class that raises an event doesn’t get control back until all sinks finish processing.

The code

Now, here’s a few examples:

 

Dim WithEvents mdclsCtlTextBox As dclsCtlTextBox

 

Private Sub Form_Open(Cancel As Integer)

    Set mdclsCtlTextBox = New dclsCtlTextBox

    mdclsCtlTextBox.Init Nothing, Me!Text2

End Sub

 

Private Sub Text2_AfterUpdate()

    MsgBox Text2.Value & " : First handler: After update in form"

End Sub

 

Private Sub mdclsCtlTextBox_AfterUpdate(strPassedBack As String)

    MsgBox "Third handler: After update of class in form : " _

      & strPassedBack

End Sub[slh1] 

 

The form opens and dimensions a class for the text box “WithEvents”. As as result, the class has an event it can raise and this form expects to sink that event. By the way, you cannot use WithEvents and New in the same Dim statement. Also, you cannot dimension a class variable WithEvents unless the class publishes an event. Attempting either of these scenarios will cause compile errors.

 

Thus the Open event sets a pointer to a new instance of the class, then initializes the class passing in a pointer to the text box.Notice that the text box has a local AfterUpdate event sink which, in this instance, does nothing more than put up a message box telling you we got here. Also notice that the form has an event sink for the classes’ AfterUpdate event. The class is going to sink the text box’s AfterUpdate event, and then Raise its own AfterUpdate event.

 

In the class module’s header we declare an event as follows:

 

Public Event AfterUpdate(strPassedBack As String)

 

Then we add an event handler for the control:

 

Private Sub mtxt_AfterUpdate()

    MsgBox mtxt.Value & " : Second handler: After update in Class"

    RaiseEvent AfterUpdate("Something passed back")

    MsgBox "control returns back to the class"   

End Sub

 

After the form finishes handling the event, the class gets control and does whatever it wants to–in this case simply putting up a message box telling you it has control. It then (after you close the message box) raises its own event AfterUpdate. Since the form chose to sink that event, control returns back to the form in mdclsCtlTextBox_AfterUpdate where yet another message box is put up telling you that control has returned to the form.

 

Finally, after you close that message box, the class displays one final message box telling you that control has returned to the class.

 

Remember that I said the system locks up if you seize control in an event sink and don’t return it?  To demonstrate this fact the demo starts four forms that use my message class to send messages to each other. They are all responding to their own timer event.  The demo then opens the text box class demo. When you type anything into the text box on the last form opened, the AfterUpdate event chain starts running. Since message boxes open, the entire system locks up until you close the message boxes.

 

This demo graphically shows that you need to be careful in your event sinks to process and move on. In the end it may not matter. If you know exactly what goes on in the whole system you may know that it doesn’t matter if things lock up. But if you have a form with a comm. port asynchronously grabbing data and passing it along via events, you may very well start to cause unintended side effects (buffer overruns and so on).

More ideas

One nice thing about events is that if no one sinks the event, it just isn’t processed. Thus you can have an event that a class raises, and if the form needs to sink the event it can. If it doesn’t, no error occurs.

 

I built a message class around this concept. This message class is used by the four forms in the demo which talk to each other.

 

The message class is instantiated globally. Any class that wants to send messages (or receive messages) dimensions a message class and sets its variable to point to the global class instance. Any class that has done so can now send or receive messages using this single message class instance. In essence, there is a “channel” that anyone can listen to.

 

In fact, the message class has two different channels that may be used. There is a simple message method that just sends a Variant data type message. It is up to the receiver to process every message and look for messages that it understands. There is another message method that sends a From, To, Subject, and Message string. Thus the receiver can sink this event and use a case statement that only processes messages to it, or all messages from a specific sender, or perhaps both.

 

I actually use this methodology in a serial communications port module. I have a form that has the MSComms control embedded in it. The form initializes a serial port  when the form opens. The form then sinks an event from the MSComms control so that when data arrives in the serial port, the form is notified and an event is sunk on the form. This comms form pre-processes bar codes. The form manipulates the data, formats it so that the rest of the program can understand it, and then sends a message using the message class we are discussing.

 

The nice part of this is that if the program that needs the bar code is running, it receives the message (a bar code) and processes it. If that form is not running, the message is simply ignored. Thus, I have encapsulated the communication port handler and pre-processing in one form, and the bar code processing in another form. Either can be loaded without the other. I can program and debug the port handler independently of the form that uses the data and vice versa.

 

If both are loaded and a bar code is sensed and pre-processed, and a message sent, then the form that actually interprets the bar code and uses it, accepts the message and handles it. I can use the same communication port form to feed bar codes to different receiving forms, or I can have two communication port forms receiving bar codes from different serial ports and sending them to the same or different forms.

 

I could (in another scenario) have two serial port forms receiving entirely different data–perhaps bar codes on one  port and a gauge on another, both receiving data and sending messages on the message channel, and the messages  picked up by whatever form knows how to process the messages. In fact, any class can sink events, so it doesn’t even have to be a form processing the message. It could simply be a class instance sitting there monitoring the messages and doing something with them.

Summary

As can be seen, the ability of a class to raise an event that another class can sink opens up new possibilities and solves a class of problems that is tough to deal with otherwise, especially if you use WithEvents. Class events are not different from control events. They are raised by something (control, form or class) and sunk by a class somewhere else. If no one sinks the event, nothing happens. The class that sinks the event decides what to do (what code to run) when the event fires.

 

WithEvents or event sinks are one half of a matching pair. By learning about RaiseEvents we can start to generate events ourselves, adding another tool to our arsenal.

 

Please contact the author at jcolby@myrealbox.com to ask questions or report errors.

John Colby©2002
May be distributed as long as the copyright remains.
John Colby Bio

 


 [slh1]John, I added the _ character because code is wrapping. Please confirm addition.