Reputation: 395
I see codes with the following pattern ubiquitously on the web and in my own projects:
Sub Func()
Application.EnableEvents = False
' some code
Application.EnableEvents = True
End Sub
Since the lifetime of VBA
objects seems to be deterministic, I thought I could replace this pattern with the so-called sentry objects as we do in C++
, so that the problem of unusual exit (err.raise) could be solved automatically.
But how? I have little idea because I'm new to VBA
and not even fully aware of when a object is passed by reference. Ideally, I would like to have the code look something like this:
Sub Func()
dim Sentry
Set Sentry = CreateSentry(Application.EnableEvents,False)
' From now on we should not need to care if the variable was actually
' True or False beforehand, what kind of error handling is used in this function, etc.
End Sub
Upvotes: 3
Views: 424
Reputation: 78180
Application.EnableEvents
is not a variable, it is a property. You cannot pass a property by reference like this in VB(A), the compiler will create a temporary copy of the current property value, and your sentry will be "closed" over the copy.
To manage object properties in this way you could do this:
Create a class, name it e.g. SentryForPropertiesVariant
and use similar code:
Option Explicit
Private m_Obj As Object
Private m_PropertyName As String
Private m_OldValue As Variant
Public Sub Init(ByVal obj As Object, ByVal PropertyName As String, ByVal NewValue As Variant)
Set m_Obj = obj
m_PropertyName = PropertyName
m_OldValue = CallByName(obj, m_PropertyName, VbGet)
CallByName m_Obj, m_PropertyName, VbLet, NewValue
End Sub
Private Sub Class_Terminate()
If Not m_Obj Is Nothing Then
CallByName m_Obj, m_PropertyName, VbLet, m_OldValue
End If
End Sub
Then use it:
Dim s As SentryForPropertiesVariant
Set s = New SentryForPropertiesVariant
s.Init Application, "EnableEvents", False
You can also have a helper function in a module:
Public Function CreateSentry(ByVal obj As Object, ByVal PropertyName As String, ByVal NewValue As Variant) As SentryForPropertiesVariant
Set CreateSentry = New SentryForPropertiesVariant
CreateSentry.Init obj, PropertyName, NewValue
End Function
at which point using becomes simpler:
Dim s As SentryForPropertiesVariant
Set s = CreateSentry(Application, "EnableEvents", False)
and in which case you probably want to replace Public Sub Init
with Friend Sub Init
.
If you plan to store your sentry class in a shared add-in (.xla), you will have to have such helper function anyway, because classes defined in add-ins cannot be created from code residing in other workbooks, so the solution is to also define a function in the same workbook as the class that would create the instance and return it to the external caller.
Finally, it is convenient to control the lifetime of such sentries with With
(resembles C#'s using
):
With CreateSentry(Application, "EnableEvents", False)
'Here EnableEvents is False
End With
'Here it's True
However when doing so, you should keep in mind the With
only resembles using
. If you jump out of it prematurely with GoTo
, the End With
statement will not be executed, which means the temporary variable holding the sentry instance will live to the end of the procedure, and the property will not revert to its original value until then.
So don't jump out of those blocks. If you absolutely have to, create a label right before End With
and jump to that.
Upvotes: 8
Reputation: 234815
Class Modules can be used for this as you can specify code to run when a class module instance is created, and, importantly when the reference count on an object drops to zero.
Specifically, you can put code in
Private Sub Class_Initialize()
and
Private Sub Class_Terminate()
Upvotes: 0