I have a UserForm
to which I added a property with the adequate Let and Get statements. I would like to have an event fire when the property changes. Event, RaiseEvent
and Routine have been stated, all in the UserForm
module. However, I can't find a way to assign the routine to the event.
For what I know, this is different from the usual Custom Events in Class Modules situation, because it is not declared in a Class Module whose Class I can instantiate in a regular module. All my searches provided examples to Custom Events in Class Modules or Built-In Events in UserForms, but no material on Custom Events in UserForms.
This actually made me wonder if it is at all possible to create Custom Events in UserForms and Private Subs to handle those Events. So is it possible to do it? And if so, what am I missing? How can I make Private Sub UFStatus_StatusChange()
handle the event?
Any help would be apreciated!
So far, the code goes, all in the UserForm module:
Public Event StatusChange (old_status As Long, current_status As Long)
Dim old_status As Long
Private current_status As Long
Public Property Let UFStatus (RHS As Long)
old_status = current_status
current_status = RHS
RaiseEvent StatusChange(old_status, current_status)
End Property
Private Sub UFStatus_StatusChange()
MsgBox("Status changed from " & old_status & "to " & current_status)
End Sub
A Custom Event is a URL page load event from your website that you'd like to track as a conversion. For example, an order confirmation page or a thank you page.
VBA workbook events are defined as an action performed by a user in Microsoft Excel that can trigger the execution of a specified macro. For example, when a user opens a workbook in Excel, the event “Workbook_Open” is triggered.
The Activate event occurs when the UserForm is displayed. This can happen using Show. It also occurs any time the UserForm is displayed. For example, if we switch to a different window and then switch back to the UserForm then the Activate event will be triggered.
Yes, but you need to understand how VBA/COM events work first.
Notice the dropdowns/comboboxes at the top of the VBE's code panes? The leftmost one is listing all available interfaces and event providers - the rightmost one is listing the available members and events exposed by whatever is selected in the leftmost dropdown.
So when you handle the Click
event of some OkButton
in the code-behind of UserForm1
, the handler stub might look like this:
Private Sub OkButton_Click()
End Sub
The signature is constructed in a very particular way, always in the same manner, regardless of whether you're implementing an interface or handling an event:
Private Sub [Provider]_[MemberName]([Args])
That underscore matters. Whatever you do, DO NOT name an event (or interface member) with an identifier that contains an underscore. In the case of an event, you'll hit a compile error:
Compile error: Invalid event name
In the case of an interface, you'll also get a compile error, with the VBE complaining that interface members aren't implemented.
This is why everything is PascalCase
and not Upper_Snake_Case
in VB. Stick to the convention, avoid underscores in public member names.
If you're not sure what interfaces are and why I'm mentioning them in a post about events, lookup the Implements
keyword, know that interfaces and events are very intimately related and work in a very similar fashion, and keep reading ;-)
Any class can define events. A UserForm
being a class, it can absolutely define events, yes. You define events exactly how you've done, using the Event
keyword:
Public Event SomethingHappened(ByVal SomeArg As Long)
The class that defines the event is an event provider - it is the only class that is allowed to raise the events it defines.
You raise events using the RaiseEvent
keyword, and provide the arguments:
Private Sub OnSomethingHappened()
RaiseEvent SomethingHappened(42)
End Sub
When and why you raise events is entirely up to your imagination.
Consider the Click
event of a CommandButton
on a UserForm
: the CommandButton
class probably has a method that listens for Win32 messages involving the mouse, and when it decides to handle a left-button click, it raises its Click
event, and something something and poof the OkButton_Click
procedure runs. Right?
The part MSForms is automagically doing for you, is that when you add a CommandButton
on the form, and name it OkButton
, well this OkButton
identifier essentially becomes a public field on the form, as if you had added a public, module-level variable:
Public OkButton As MSForms.CommandButton
Except, it actually looks like this:
Public WithEvents OkButton As MSForms.CommandButton
That WithEvents
keyword makes the OkButton
available in the left-side dropdown - OkButton
becomes an event provider, and its Click
event can be handled... in the form's code-behind.
The CommandButton
class doesn't know or care about a handler for its Click
event: the event provider is the OkButton
object, and the client is the UserForm1
class you're implementing the handlers in.
In other words, the event provider, and the client, are two completely separate classes.
The catch is that WithEvents
is only legal in a class module.
You can make your UserForm1
an event provider, but it cannot handle its own events.
Declare the events in your UserForm1
code-behind, and make sure you specify ByVal
parameters for parameters that are meant to be read-only at the handler site - use ByRef
when the handler can modify the value, e.g. for some Cancel As Boolean
parameter that you can read when the handler returns:
Public Event StatusChange(ByVal oldStatus As Long, ByVal newStatus As Long)
Now add a class module, call it MyPresenter
:
Option Explicit
Private WithEvents MyView As UserForm1
Private Sub Class_Initialize()
Set MyView = New UserForm1
End Sub
Private Sub Class_Terminate()
Set MyView = Nothing
End Sub
Public Sub ShowDialog()
MyView.Show
End Sub
Select MyView
from the leftmost dropdown; the rightmost dropdown should contain the StatusChange
event - and selecting it should create a handler stub:
Private Sub MyView_StatusChange(ByVal oldStatus As Long, ByVal newStatus As Long)
MsgBox "Status changed from " & oldStatus & " to " & newStatus & "!"
End Sub
Now, in the standard/procedural module where you were normally showing your form, instantiate that presenter class instead:
Public Sub Macro1()
With New MyPresenter
.ShowDialog
End With
End Sub
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With