Data Broadcasting and
Communication Pipelines using Events.
aka 'Observer Pattern'
http://en.wikipedia.org/wiki/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 |
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.
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.
'FORM MENU CODE Private Sub Form_Load()
Set frmcarrier = Me.Parent
Set mnuControl = CommandBars("WizPanelPopUp").Controls("Press Me")
End SubPrivate 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
http://en.wikipedia.org/wiki/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
'PROXY CODE 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
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.
'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 papwalker@ozemail.com.au