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
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
Now when ever the record is changed we raise this event.
Private Sub Form_Current()
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
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.
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.
'FORM MENU CODE
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
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.
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
How this works is that all
active objects set a withevents reference to the instance of the proxy
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.
'USER or FORM CODE
Dim WithEvents gProxy As Proxy
Private Sub gProxy_Broadcast(Topic As Integer, Data As Variant)
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 them..it 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 firstname.lastname@example.org