Data Broadcasting and Communication Pipelines using Events.
aka 'Observer Pattern'

One common scenario in MS Access programming is for one form to see anothers data, or for a form to be notified of something from another. Since forms are class instances like any other we can refer to them as objects. So... Object B needs info from object A.
There are some ways to do this.

1) Object B looks at the instance of object A (MyForm) and reads a public member.

x = Forms!FormA!SomeControl
x = Forms!FormA.SomePublicVariable
x = Forms!FormA.SomePublicPropertGet

2) Object A sets a Global Variable which B polls

3) Object A sets a public on object B

Bear in mind that the data required may have a context in time. Late data may be useless or even be considered wrong. A latency issue. The data migh be transitory so a polling approach might not work. Since we are doing event driven programming the use of polling is less than ideal. In this case option three above is the best of a bad lot.

Lets look at an example: Tracking Forms
We have a master Form and ...n... Slave Forms. The slave forms need to know the current record on the master as it changes. Normally you might use subforms but in this instance we can't or don't want to. So the slave forms need to be advised when the Master form changes record. One of achieving this is to use a public event on the master.

Option Compare Database
Option Explicit
Option Base 1 Public Event RecordChanged (Key As Integer)

Now when ever the record is changed we raise this event.

Private Sub Form_Current()
Key = Me.ID
RaiseEvent RecordChanged(Key)
End Sub

One advantage here is that the Master form doesn't know or care who gets the event. It doesn't need to expose public members. When the record changes it fires the event. The Slave forms simply set a WithEvents Reference to the master and they will receive the event.

Option Compare Database
Option Explicit
Dim WithEvents frm As Form_Form1

Private Sub Form_Load()
         Set frm = Forms!Form1 
End Sub 

Private Sub frm_RecordChanged(Key As Integer)
         'Do something
End Sub

The resulting Slave form code might look something like above. What you should keep in mind is that any number of Slave forms can set a reference and they will all receive the event. The master form Broadcasts the data using the RaiseEvent and any objects with a reference will receive it. A similar situation to this is when one changes the locale settings in control panel. After changing the settings a system wide broadcast is emitted and all running processes are advised to re-check the settings and update date or money controls etc. Since the master knows nothing about the slave forms you can mix and match, change names, re-factor slave forms - anything without breaking your code.

Another Practical Example: Popup Menu re-use.
There is another scenario where using events can be almost mandatory. When using command bars / menu bars in Access. Traditionally one used to wire in a procedure name into the 'On Action' property of the commandbar control. However one can only use a procedure from a standard module. This is acceptable for a lot of cases but it limits the re-use of the menu on a per form basis. If one wants to use a popup to do something with a variety of forms, and the context depends on the form then we're in trouble. We have to use code in the standard module procedure to determine which form is open, and hardwire with if or case statements to execute a method of the form.
There is however a better way. Set a withevents reference to the commandbar control. As mentioned above, the referenced object doesn't care anything about the referencer. COM will look after the details. Now the menu can be used for any form and when clicked it calls the click event procedure in the form module.
However there is a consideration, if you have two forms open that reference this menu control, they both get the event fired. So what I check to see that the form is the Screen.ActiveForm / Screen.ActiveControl.Parent.
Of course you may want all forms to get notification from a particular menu (broadcast) - in this case there is less code.

Option Explicit
Option Compare Database
Dim WithEvents frmcarrier As [Form_Main Wizard Carrier]
Dim WithEvents mnuControl As CommandBarButton
Private Sub Form_Load()
    Set frmcarrier = Me.Parent
    Set mnuControl = CommandBars("WizPanelPopUp").Controls("Press Me")
End Sub
Private Sub mnuControl_Click(ByVal Ctrl As Office.CommandBarButton, CancelDefault As Boolean)
    If Screen.ActiveControl.Parent Is Me Then MsgBox "Menu clicked panel 1"
End Sub

Event and Data Proxy.
aka Proxy Pattern

Another approach is to use a proxy. This allows communication between objects where they know nothing about each other, the only thing they do know about is the proxy. It's a bit like Mr. Smith asking a hotel clerk to use the PA to get the representative from Merke to contact Mr. Smith. Mr Smith need have no idea who, where or even if the Merke rep is in the hotel.
The first thing required is the Proxy. This can be a class module with at least one public Property and Event.
See Below

Option Compare Database
Option Explicit
Public Event Broadcast(Topic As Integer, Data As Variant)

Public Event QueryData(Topic as integer, Data as Variant)
Public Property Set DoBroadcast(Topic As Integer, Data As Variant)
           RaiseEvent Broadcast(Topic, Data)
End Property

Public Function QueryData(Topic As Integer) as Variant
Dim vData As Variant
RaiseEvent QueryData(Topic, vData)
If Not IsObject(vData) Then
QueryData = vData
Set QueryData = vData
End If
End Function

How this works is that all active objects set a withevents reference to the instance of the proxy class.
When an object (Form etc) wants to notify others regarding a state it calls the DoBroadcast method of the class. All objects will receive an event.

The typical code for objects is laid out below.
In the event procedure the objects examine the topic to see if they need take notice.

Option Compare Database Option Explicit
Dim WithEvents gProxy As Proxy
Private Sub gProxy_Broadcast(Topic As Integer, Data As Variant)
If Topic = 2 then Call MySubRoutine
End Sub

Private Sub Test()
Dim MyTopic as Integer
MyTopic = 334
If gProxy.QueryData(MyTopic) = True then call DoSomeRoutine End Sub

Of course system works both ways. A Form wanting to know something from another form might call the QueryData method of the proxy (above) with a Topic that another form will understand. All forms will get the event and the form understanding the topic will set the Data argument in the event procedure to the suitable value. The Proxy inspects the return value from the RaiseEvent as uses it as the return value for the function.
This allows forms or objects to talk to one another with out knowing anything about each other. They need only know about Topic numbers and the proxy.

Where I actually use this sort of thing commercially is with complex wizard style input forms. The wizard comprises a main form (the Carrier) and several subforms and a navigation control. The subforms know nothing about each other. They only know about the parent. The parent acts as the proxy passing info back and forth between the subforms. So to extend the wizard and add, remove or rename subforms presents no problem as they never reference each other directly.
Using this style you are basically using techniques used throughout COM. If you want to use ADO Recordsets you set a reference to ADODB. ADODB doesn't know anything about your application. It uses events to notify you of what's what and if you don't use doesn't care.

The point being that if you need to call a procedure on another form, have that form set a reference and raise an event. The same goes for when you need to read a text box, raise the event with a topic and read the return value. It seems more complicated to use this approach but this way you can change the type, name or any feature of the form or control and not break your code.

For a rough sample of using events in this way see this A2000 demo.


For further information on this article contact
peter walker