> ## Content Index
> Fetch the complete content index at: https://nolongerset.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Raising Custom Events in VBA
- URL: https://nolongerset.com/raising-custom-events-in-vba/
- Published: 2021-04-28T03:44:00.000Z
- Updated: 2026-05-08T13:05:01.000Z
- Description: This quick tutorial will have you writing custom events in VBA in no time.
- Author: Mike Wolfe
- Tags: Advanced, Events, #Import 2026-05-20 02:59

In [yesterday's article](https://nolongerset.com/event-driven-programming-in-vba/), I used the childhood game of funnel ball to talk about how event-driven programming relates to real life. As promised at the end of that article, I'll now go through the process of representing those real-life events in VBA.

## The Real-life Events of Funnel Ball

For context, here's what a funnel ball pole looks like:

![](https://storage.ghost.io/c/eb/d7/ebd732c1-5f03-4f07-b386-5d08557e15c9/content/images/2021/04/FunnelBall-1.jpg)

Throw the ball in the top, then it comes out a colored hole numbered 2, 4, 6, or 8

As a quick review from yesterday's article, these are the events we will be modeling:

- **Ball Enters Funnel**: this is what we'll call it when the ball enters the funnel
- **Ball Exits Funnel**: this is what we'll call it when the ball leaves the funnel

Additionally, when the **Ball Exits Funnel**, we want to know the **Hole Number** (2, 4, 6, or 8) of the hole from which it exits.

## Announcing: The `oFunnelBallPole` Class

Whenever I have a class module that represents a real-life object, I like to prefix the class module name with a lower-case "o" (for object). So, for the class module that represents the funnel ball pole itself, we'll call that oFunnelBallPole.

Here is the code for that class:

```vba
'--== oFunnelBallPole class module ==--

Option Explicit

Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

'Custom events must be declared at the top of a class module
Public Event BallEntersFunnel()
Public Event BallExitsFunnel(HoleNumber As Byte)

'This method models the real-life act of throwing a ball
'   into the funnel at the top of a funnel ball pole
Public Sub ThrowBallInFunnel()

    'First, we raise an event to announce that
    '   the ball has entered the funnel
    RaiseEvent BallEntersFunnel
    
    'Then, the ball rattles around for 1 to 5 seconds
    Sleep RandomInt(1, 5) * 1000
    
    Dim HoleNum As Byte
    HoleNum = RandomInt(1, 4) * 2
    
    'Finally, we raise an event to announce that
    '   the ball is exiting the funnel along with
    '   the number of the hole from which it exits
    RaiseEvent BallExitsFunnel(HoleNum)
    
End Sub

'Returns a random long integer between the Min and Max values, inclusive
Private Function RandomInt(Min As Long, Max As Long) As Long
    RandomInt = (Rnd() * (Max - Min)) + Min
End Function

```

I tried to keep this class as simple as possible, to really highlight the event code.

### `Public Event` lines

At the top of the class module, we declare our custom events using the [Event keyword](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/ff9d44a9-7a89-474e-9546-a1b169d38a26). The `Public` keyword is optional. All events are public whether you use the Public keyword or not; Private Events are not allowed (as they would serve no purpose).

### `RaiseEvent` lines

Once we declare public events at the top of our class module, we can raise those events using the [RaiseEvent statement](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/3795fff1-ce8a-40f7-8d2c-b1e2c1a251c4) within the functions (`Function`), methods (`Sub`), and properties ( `Property Get`/ `Let`/ `Set`) of our class. The VBA compiler will complain if you try to call `RaiseEvent` without first declaring the event publicly at the top of the class module.

Think of `RaiseEvent` as an announcement to the world that something is happening. 

This is where event-driven programming can start to feel confusing. With procedural programming, we can generally look at the next line of code to know what's going to happen next. With event-driven programming, that's no longer the case. 

When our class calls RaiseEvent, that could initiate code to run in a different part of our program. Maybe even in multiple parts of the program. Or maybe nowhere at all.

Remember, `RaiseEvent` is nothing more than an announcement. Whether anyone is listening is an entirely separate matter.

When it comes to event-driven programming, if a tree falls in the woods and no one hears it, then the answer is clear: it does not make a sound.

## Listening: The `clsFunnelBallGame` Class

Raising events is only half of the puzzle. Events serve no purpose unless you listen for them elsewhere. And the only way to listen to events in VBA is inside a class module.

But how do we actually *listen to events* in VBA? Using the [WithEvents keyword](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/f41f8ec5-7a2d-4797-8ba9-0e3b52113b9e). Here's the sample class:

```vba
'--== clsFunnelBallGame class module ==-- 

Option Explicit

Public WithEvents Pole As oFunnelBallPole

Private Sub Class_Initialize()
    Set Pole = New oFunnelBallPole
End Sub

Private Sub Pole_BallEntersFunnel()
    Debug.Print "At "; Format(Now, "h:mm:ss"); " ball enters funnel"
End Sub

Private Sub Pole_BallExitsFunnel(HoleNumber As Byte)
    Debug.Print "At "; Format(Now, "h:mm:ss"); " ball exits hole"; HoleNumber
End Sub
```

In the declaration section at the top of the class module, we use the `WithEvents` keyword to tell VBA to be ready for us to handle the events raised by the **oFunnelBallPole** class.

The event handler methods are constructed using "magic identifier" syntax. What I mean by that, is that there is no keyword–such as `Handles` or `Listens`–that declares the private subroutine *Pole\_BallEntersFunnel()* to be a handler for the **oFunnelBallPole** class's *BallEntersFunnel* event. Rather, VBA uses the WithEvents declaration at the top of the class to look for private subroutines of the form `Pole_{*oFunnelBallPole declared event name*}`.

[Technically speaking](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/f41f8ec5-7a2d-4797-8ba9-0e3b52113b9e), VBA uses the WithEvents variable name (i.e., `Pole` in this example) with an appended underscore character as an "*event handler name prefix*" within the class module.

This is why "\[t\]he name of an event MUST NOT contain any underscore characters ([Unicode](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/213ca0c8-6b82-4899-80a3-3c76eb534829#gt%5Fc305d0ab-8b94-461a-bd76-13b40cb8c4d8)u+005F)." ([VBA language specification](https://docs.microsoft.com/en-us/openspecs/microsoft%5Fgeneral%5Fpurpose%5Fprogramming%5Flanguages/ms-vbal/ff9d44a9-7a89-474e-9546-a1b169d38a26))

## Testing

Let's write a quick test procedure to demonstrate how all of this works:

```vba
Sub TestFunnelBall()
    Dim Game As clsFunnelBallGame
    Set Game = New clsFunnelBallGame
    
    Dim i As Integer
    For i = 1 To 3
        Debug.Print vbNewLine; "Toss #"; i
        Debug.Print String(30, "-")
        Game.Pole.ThrowBallInFunnel
    Next i
End Sub
```

Here's what it looks like if we run it in the immediate window:

![](https://storage.ghost.io/c/eb/d7/ebd732c1-5f03-4f07-b386-5d08557e15c9/content/images/2021/04/image-113.png)

## Multiple Listeners: The `clsPhotography` Class

I said earlier that each time we call `RaiseEvent`, we can never be sure how much other code we might be kicking off. This is another one of the differences between purely procedural code and event-driven code. With event-driven code, we can have multiple listeners handling the same event.

For example, let's imagine that we have a photographer taking pictures of the funnel ball game. As soon as that ball exits the funnel, he wants to snap a photo of the exact hole whence the ball emerges. Let's write such a class:

```vba
'--== clsPhotography class module ==--

Option Explicit

Private WithEvents GamePole As oFunnelBallPole

Sub PhotographFunnelBallPole(FunnelBallPole As oFunnelBallPole)
    Set GamePole = FunnelBallPole
End Sub

Private Sub GamePole_BallExitsFunnel(HoleNum As Byte)
    Debug.Print "At "; Format(Now, "h:mm:ss"); " take picture of hole:"; HoleNum
End Sub
```

Now, let's update our test procedure to add the new listener:

```vba
Sub TestFunnelBall()
    Dim Game As clsFunnelBallGame
    Set Game = New clsFunnelBallGame
    
    Dim Photography As New clsPhotography
    Photography.PhotographFunnelBallPole Game.Pole
    
    Dim i As Integer
    For i = 1 To 3
        Debug.Print vbNewLine; "Toss #"; i
        Debug.Print String(30, "-")
        Game.Pole.ThrowBallInFunnel
    Next i
End Sub
```

Finally, let's test our funnel ball game again, this time with an extra listener. 

![](https://storage.ghost.io/c/eb/d7/ebd732c1-5f03-4f07-b386-5d08557e15c9/content/images/2021/04/image-114.png)

If you step through this code as it runs, you will see that the following line in the **oFunnelBallPole** class gets called only one time per toss...

```vba
RaiseEvent BallExitsFunnel(HoleNum)
```

...but the next two methods called are...

```vba
Private Sub Pole_BallExitsFunnel(HoleNumber As Byte)
```

...in the **clsFunnelBallGame** class module and...

```vba
Private Sub GamePole_BallExitsFunnel(HoleNum As Byte)
```

...in the **clsPhotography** class module.

This is a powerful feature. Use it wisely.

## Quick Recap

It takes two to tango when it comes to *custom* event-driven programming in VBA:

- an **Announcer** class that declares a module-level `Public Event` and then calls `RaiseEvent` within the class body (methods/functions/properties)
- a **Listener** class that handles events via `WithEvents`

However, you only need an Announcer class if you want to raise your own custom events. A far more common scenario is to create one or more Listener classes that handle *built-in* events in Access, like KeyDown, MouseUp, etc.

That sounds like a good topic for another day.