• ** There has been a recent site upgrade. Please clear your browser cache to avoid issues. **
  • New forum feature - post voting and best solution

    Check out this thread for the details: https://www.access-programmers.co.uk/forums/threads/new-forum-feature-post-voting-and-best-answer.314134/

    This new feature looks great to me! :)

  • We now have 3 forum themes

    Go for the default (light) theme, Shades of Grey or Shades of Blue. I just added the Blue one.

    The thread about it is here: https://www.access-programmers.co.uk/forums/threads/new-forum-theme-shades-of-blue.314136/

What You Need to Know About Access Events (1 Viewer)

MajP

You've got your good things, and you've got mine.
Local time
Today, 11:35
Joined
May 21, 2018
Messages
3,480
There is often a lot of confusion about Access Events and often answering questions posed here is difficult because of inaccurate terminology. People often say "Event" when it is unclear if they mean the actual event that takes place, the event procedure, or the event property. This thread will try to clarify the correct terminology and discuss the basic and advanced Event concepts. Please feel free to critique or recommend additional topics. I would like to clean this up eventually and make a sticky. I have included a db that demonstrates these concepts. This thread includes
- What is an Event and raising / handling events
- What are Event Properties
- What are Event Procedures / Event Handlers
- How a single function can handle multiple Events
- Handling events in external Classes (handling another form’s events)
- Handling other object’s events in custom classes
- Raising and handling Custom Events

A. Events raising and handling:
The best way to think of an event is an announcement (Raised) that something has happened and your code can then listen to this announcement and react to it (Handle). Events are Raised (announced) and code can Handle the announcement. Often people will say "I call this event" which is not correct when they mean they handle an event.

B. Event Properties:
In form or report design you see what are called Event Properties (examples afterupdate, onclick, onEnter). As you can see all event properties start with a preposition (On, before, after,...) which tell when the event takes place. These event properties do two things if filled in. One they tell the event to announce that it has occured. If left blank the event does not announce that it has occured. Two it describes what is listening for the event in order to handle it. There are three things that can handle an event: an event procedure, a macro, or a function. So the following can be put in an event property:

[Event Procedure] 'Informs that the event will be handled by an event procedure
[Embedded Macro] 'Informs that a macro will handle the event
=SomeFunctionName() 'Informs that a function will handle the event

C. Event Procedures / Event Handlers:
Event procedures are the VBA code that listens for an event to occur (Raise) and then reacts (Handles) to it. A more descriptive name is "event handler", because the event procedure code is not an "event" but a procedure that handles the event.

Code:
Private Sub cmbo1_Click()
   'This is an event procedure that handles the combobox click event
 End Sub

In something like vb.net the code construct makes this clearer. An event procedure will be followed by the names of the event/s it handles. So it would look something like this (pseudo code)
Code:
 Private Sub cmbo1_Click() handles cmbo1.onclick
   'This is an event procedure that handles the combobox click event
 End Sub

What is nice in .net and which you cannot do in vba is tell a procedure to handle multiple events. It would look something like this (pseudo code)

Code:
 Private Sub SomeProcedure() handles cmbo1.onclick, text1.afterupdate,
text2.afterupdate
   'This is an event procedure handles the combobox click event and textbox 1,2 after update
 End Sub

D. One Function to Handle Multiple Events:
As mentioned there is not a way to write a single event procedure to handle multiple events in vba, but you can do this using a function. You can make a function (it has to be a function not a sub even though it returns nothing). Then in each controls event properties, you do not put in "[Event Procedure]" you put in the name of the function like =SomeFunctionName(). This function now can handle multiple events.

Code:
Private Function SomeFunctionName()
  'Often you want to do something based on which control's event is handled
   dim ctrl as access.control
   set ctrl = me.activecontrol
   msgbox ctrl.name
end function

E. Handling other Object Events in a different Class / With Events
This is probably one of the most underused and valuabe techniques. You can handle in one class the events of another object. For example form 1 opens form 2 as a pop up. Suppose when you do something like change a value in form 2 you want to do something in form 1. Often people will hard code form 2 to update form 1. This is bad design because you are tightly coupling form 1 and form 2. If you now want form 3 to call form 2 and do the same thing then the code in form 2 gets complicated. Instead you can have form 1 and form 3 handle events that occur in form 2 (an external object). Assume form 1 opens form 2 and form 2 has a combobox. You want to handle in form 1 the afterupdate of the combobox that takes place in form 2. To do this:
1. In form 1 define a variable using WithEvents
2. Set the variable to the external combobox in form 2
3. Make sure the combobox in form 2 raises the event by setting the eventProperty to "[Event Procedure]" You can do it manually in form2, but I usually do this by code so I do not forget. Remember if you leave the Event property blank it will not announce the event. In form 2 the combobox afterupdate needs "[Event Procedure]" either by doing it manually or by code.
4. Handle the after update event for your variable. Notice you are not handling CmboProducts event, you are handling your variable "Cmbo" that is set to cmboProducts. What is nice, is after you define your Cmbo using withevents it will appear in intellisense and you can choose it like other form 1 objects.

Code:
Private WithEvents Cmbo As ComboBox

Private Sub cmdOpen_Click()
  Set Cmbo = Nothing
  DoCmd.OpenForm "form2"
  'Set your variable to the combobox in form 2
  Set Cmbo = Forms!form2.cmboProducts
  'To ensure that the cmbo raises the afterupdate event need to add "[Event
Procedure]" in the event property in form 2
  Cmbo.AfterUpdate = "[Event Procedure]"
End Sub

Private Sub cmbo_AfterUpdate()
  'Now can trap an event from a control on another form
  Me.txtOutPut = Nz(Cmbo, "Nothing Selected")
End Sub

F. Handling other Object events in Custom Class
This is no different than what was described in E because a form or report's class is a class just like any custom class. So the technique is the same. To me this is the most powerful use of vba in Access. You can now build custom classes that extends the capability of a control or group of controls. You can handle multiple events from multiple controls and make them act basically as a user defined control. Here is the technique. This class only demonstrates the handling of events, this class has no real utility.

Class Code
Code:
Private WithEvents mCmbo As ComboBox
Public Property Get Combo() As ComboBox
  Set Combo = mCmbo
End Property

Public Property Set Combo(ByVal TheCombo As ComboBox)
  Set mCmbo = TheCombo
End Property

Public Sub Initialize(TheCombo As ComboBox)
  Set Me.Combo = TheCombo
  'Need to ensure you raise the events you want to capture
  Me.Combo.OnEnter = "[Event Procedure]"
  Me.Combo.BeforeUpdate = "[Event Procedure]"
  Me.Combo.AfterUpdate = "[Event Procedure]"
End Sub
'------------------------------------------------------ Trap Events --------------------------------------
Private Sub mCmbo_BeforeUpdate(Cancel As Integer)
  MsgBox "Before update Trapped in custom class " & Me.Combo.Value
End Sub

Private Sub mCmbo_Enter()
    MsgBox "Enter event Trapped in custom class " & Me.Combo.Value
End Sub
Private Sub mCmbo_AfterUpdate()
  MsgBox "After update Trapped in custom class. This demonstrates two different event handlers trapping the same event. " & Me.Combo.Value
End Sub

In the form you would initialize the custom class like this

Code:
Public CC As CustomClassHandler

Private Sub Form_Load()
  Set CC = New CustomClassHandler
  CC.Initialize Me.cmboProducts
End Sub

Private Sub cmboProducts_AfterUpdate()
  MsgBox "After update event handled in the form class " & Me.cmboProducts
End Sub

Even though the form does not have any beforeupdate or enter event handler the class will handle the events. Note there is an afterupdate event handler in the form's class and also the custom class. This actually demonstrates two different event handlers listening to the same event. This is important to prove the point that you do not call event handlers, they listen for the event.

G. Raising custom Events
In a class module you can raise your own custom events and then handle them. Since a form's or report's module is a class module you can do this here as well as in a custom class.
Public Event EventName(ReturnValue as datatype, ReturnValue2 as
datatype....)
So not only can you raise an event you can pass a value back. You have seen this before in the beforeupdate event of a form

Code:
  Private Sub SomeControl_BeforeUpdate(Cancel as as integer)
     ‘cancel is passed when the event is raised by reference.
 End sub

Here is the Code in Form 2 to raise an event. If the final product selected has the letter "A" in the name it raises the event HasA and passes to the event handler the product name.

Code:
Public Event HasA(Product As String) 'Raise event if selection has an 'a' in the name

Private Sub Form_Close()
  If InStr(Me.cmboProducts, "A") > 0 Then
    RaiseEvent HasA(Me.cmboProducts)
  End If
End Sub

The code in Form 1 to trap this custom event is

Code:
Private WithEvents cmbo As ComboBox
[B]Private WithEvents Frm As Form_form2[/B]

Private Sub cmdOpen_Click()
  Set cmbo = Nothing
  DoCmd.OpenForm "form2"
  Set cmbo = Forms!form2.cmboProducts
  Set Frm = Forms!form2
  'To ensure that the cmbo raises the afterupdate event need to add "[Event Procedure]" in the event property
  cmbo.AfterUpdate = "[Event Procedure]"
End Sub
Private Sub cmbo_AfterUpdate()
  'Now can trap an event from a control on another form
  Me.txtOutPut = Nz(cmbo, "Nothing Selected")
End Sub

[B]Private Sub Frm_HasA(Product As String)
  MsgBox "Final Selection Has An A in the product name"
End Sub[/B]

Any questions or suggestions please provide.
 

Attachments

  • Events.accdb
    524 KB · Views: 18
Last edited:

The_Doc_Man

Immoderate Moderator, Former MVP, Retired SysAdmin
Staff member
Local time
Today, 10:35
Joined
Feb 28, 2001
Messages
17,930
MajP:

After reading your truly excellent set of explanations and examples, there are a couple of points I would add, and I hope you don't object.

First, although it is theoretically possible for you to call one event's handler from another event manually (i.e. Call Button1_Click), events don't normally interrupt each other or run as subroutines of each other. You can make that manual call because event routines are declared with Private Sub Control_EventType() declarations. I.e. they are subroutines. Since the default for event routines is a "Private Sub" declaration, you can only make that call from within the same class module as the targeted event handler.

That non-interruptible concept means that if you have an event X that causes a condition that would trigger (raise) another event Y, the handler for Y normally cannot start until the handler for X executes an Exit Sub or reaches the End Sub of the handler code. One exception is when you call "DoEvents" which can allow events in other contexts to occur.

Second, there is yet another "exception" to event handlers not interrupting other event handlers. An EXCEPTION HANDLER is a handler for a specific class of event but it typically is treated differently than ordinary events (even though both use the "Handler" nomenclature.)

An exception (also commonly called a trap) is an event that was detected and signaled in a different way than most other events, sometimes by hardware and sometimes by non-Access facilities (such as Windows or an Application Object).

For instance, if you are using SINGLE or DOUBLE numbers and encounter a floating-point overflow trap, that is something that can be managed by a trap-handler. Division by zero is another typical math-oriented trap. System power alerts can cause traps as well.

Just as an event cannot interrupt another event, so a trap normally cannot interrupt another trap. (Which makes a trappable error in a trap handler the worst and most insidious loop you have ever seen.) Like events which have an event queue, traps have a trap queue. They also linearize.

You can tell the difference between an event handler and a trap handler in another way when you are looking at them... Event handlers EXIT but trap handlers RESUME. Don't ever get them confused because if you do, that is a program crash waiting to happen, probably with an illegal memory reference.

There is another fine point that makes trap handlers and event handlers different. With the single exception of a Form_Error event, most other events do not activate the Err object - because they aren't errors.

I bring this all up because of the confusion often experienced by new Access users with the common "handler" nomenclature. The truth is, that confusion isn't exclusive to Windows. UNIX also has the concept. OpenVMS (a mainframe-class O/S) also had the concept. It is a computing concept of considerable importance.

Another important point has to do with event handlers and their events. MajP's point "A" is incredibly important to understand. Access is the main program at all times in your App and is driving the process that your App has specified. AS A COURTESY, when certain things happen in Access, if you declared a handler then Access will call your handler. But unless the event in question has the ability to be canceled, the named event will occur REGARDLESS of whether you had a handler for it.

For example, even if you have no Form_Load event, Access will load a form that was successfully opened - because there is no Cancel option for Form_Load. Once opened, a form WILL load regardless of the event handler. Typically, the event handler is called when the event has already occurred. The exceptions are events with the word "Before" in their name. BeforeUpdate, BeforeInsert, and BeforeDelete come to mind.
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 11:35
Joined
May 21, 2018
Messages
3,480
I do not object and appreciate feedback. However, I have to admit I had a hard time making heads or tails of your response. Maybe just me, but I found it confusing and kind of all over the map. Not sure of the salient points, but I will see what I can address

1. Calling Event Procedures from current form/report
You said that it is "Theoretical" to call an Event Procedure / Event Handler. I would say the word "theoretical" is a little misleading, it sounds like something that can be done but has not been achieved or something extremely complicated to do. Calling an Event Procedure from other code is a simple to do, but not IMO the best code design. However it is worthwhile to understand that this can be done because it shows that these Event Procedures are like other procedures, and it can be called like any other pocedure. Assume I have an on click Event Procedure.

Code:
Private Sub cmdOne_Click()
  msgbox "Hello World"
End Sub

This is where bad terminology often comes in. People will ask something like "Can I make the click event occur from another event". As written the answer is no and a meaningless question. What they usually mean to say is, "Can I call the on click Event Procedure from another procedure" So the answer is yes to the latter because an Event Procedure is just a procedure like other procedures with the unique ability to respond when an event is raised.

so to call the CmdOne_Click Event Procedure from another Event Procedure:

Code:
Private Sub Form_Close()
  cmdOne_Click
End Sub

This is easy to do but IMO not the cleanest design. To allow multiple events to call the same procedure the cleaner design is simply to make a common procedure and have the Event Procedures call the common event.

Common Event that can be called from other procedures:
Code:
Private Sub HelloWorld
  Msgbox "Hello World"
end Sub

It can then be called by multiple events

Code:
Private Sub cmdOne_Click()
  HelloWorld
End Sub

Private Sub Form_Close()
  HelloWorld
End Sub

2. Calling Form/Report Event Procedures from External
Calling an Event Procedure from an external form, report, or module is again very possible and easy to do. Here again not the best design IMO. Like any class procedure that gets called from outside the class you have to make the Event Procedure "Public". This is done by simply changing the procedure access identifier from "private" to "public". Assume this code is in Form1.

Code:
Public Sub cmdOne_Click()
  msgbox "Hello World"
End Sub

You could call form1 CmdOne_Click event procedure from form2 like

Code:
dim frm as Form_Form1
'Instantiate an instance of form1
docmd.openform "Form1"
set frm as forms!form1
'call the public procedure
frm.cmdOne_click

If you really wanted to do this, again the cleaner design would be to simply have the common procedure in form1. Again it would have to be Public.

Code:
Public Sub HelloWorld
  Msgbox "Hello World"
end Sub

When I say calling an event procedure is a bad design it is not like writing bad inefficient or error prone code. In truth it is not fundamentally or functionally different than calling any other procedure because as stated Event Procedures are procedures like any other procedure. I just think it makes following the code less clear and not as well encapsulated.

3. Event Handlers and Error Handlers
You mentioned that a lot of novice Access developers confuse event handlers and error handlers. I find that extremely suprising and have never seen a single example of this on my 20 years of active participation on Access forums. Few vba users even use the term Event Handler. However, the remaining discussions on exceptions I did not feel was relevant to this discussion since the focus was on events.
 
Last edited:

The_Doc_Man

Immoderate Moderator, Former MVP, Retired SysAdmin
Staff member
Local time
Today, 10:35
Joined
Feb 28, 2001
Messages
17,930
As it was not my intention to confuse anyone, I guess I missed my mark since I confused you. Sorry 'bout that.

Clarification #1: We agree (but view through the filter of our different backgrounds) that you CAN call an event routine since it is just a subroutine. However, it can never occur that event X's routine will be interrupted by event Y's routine - because events are said to "linearize" and fall into a sequence. Event Y's routine can be CALLED from event X and thus be a part of program flow. But if event Y actually occurs and event X wouldn't call it, event Y's routine MUST wait until event X's routine is complete. I.e. there is no "natural" sequence in which event X's routine would wait for event Y's routine IF event X occurred first.

Clarification #2, regarding nomenclature of handlers: Some will refer to "event routines" or "event procedures" and others will refer to "event handlers." However, if you come from certain specific backgrounds, you will hear "handler" more often - e.g. in writing device drivers or real-time interrupt-driven code, which is MY background.

We agree that if you had something in event Y's handler that you wanted event X to be able to use, you would do far better to make that something be its own sub in a general module. Then X and Y event handlers would be able to use it easily. Directly calling a handler routine from another handler routine is generally better managed by making the common code become a separate procedure.

My point about scoping is actually corroborated by what you said. In order to activate a specific event procedure declared in another form, you have to override the Access default that makes all event procedures Private. You certainly CAN declare the routine to be Public, but to my way of thinking (and apparently yours as well), doing that is inferior to splitting out the code into its own public routine in a general module. It leads to confusing code and is to be shunned as a form of "spaghetti code."

You and I don't really disagree, MajP - we just have radically different backgrounds that lead to different ways of seeing things. And I strongly support your efforts here!
 

Users who are viewing this thread

Top Bottom