VBA中的Sentry对象

时间:2013-12-21 10:33:33

标签: vba excel-vba excel

我在网络和我自己的项目中无处不在地看到具有以下模式的代码:

Sub Func()
     Application.EnableEvents = False
     ' some code
     Application.EnableEvents = True
End Sub

由于VBA对象的生命周期似乎是确定性的,我认为我可以像C++中那样用所谓的哨兵对象替换这个模式,这样就会出现异常退出的问题( err.raise)可以自动解决。

但是怎么样?我不知道,因为我是VBA的新手,甚至不知道何时通过引用传递对象。理想情况下,我希望代码看起来像这样:

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

2 个答案:

答案 0 :(得分:5)

Application.EnableEvents不是变量,而是属性。你不能在VB(A)中通过这样的引用传递属性,编译器将创建当前属性值的临时副本,并且你的岗哨将在副本上“关闭”。

要以这种方式管理对象属性,您可以这样做:
创建一个类,例如命名它SentryForPropertiesVariant并使用类似的代码:

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

然后使用它:

Dim s As SentryForPropertiesVariant
Set s = New SentryForPropertiesVariant

s.Init Application, "EnableEvents", False

您还可以在模块中使用辅助功能:

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

此时使用变得更简单:

Dim s As SentryForPropertiesVariant
Set s = CreateSentry(Application, "EnableEvents", False)

在这种情况下,您可能希望将Public Sub Init替换为Friend Sub Init

如果您计划将您的sentry类存储在共享加载项(.xla)中,则无论如何都必须具有此类辅助函数,因为无法通过驻留在其他工作簿中的代码创建加载项中定义的类,因此解决方案是在与创建实例的类相同的工作簿中定义一个函数,并将其返回给外部调用者。


最后,使用With(类似于C#的using)控制此类哨兵的生命周期很方便:

With CreateSentry(Application, "EnableEvents", False)
  'Here EnableEvents is False
End With

'Here it's True

但是,在执行此操作时,您应该牢记With类似于 using。如果你使用GoTo过早地跳出它,End With语句will not be executed,这意味着持有sentry实例的临时变量将存活到过程的结尾,并且属性不会在此之前恢复到原来的价值。

所以不要跳出那些街区。如果您必须,请在End With之前创建一个标签并跳转到该标签。

答案 1 :(得分:0)

类模块可以用于此目的,因为您可以指定在创建类模块实例时运行的代码,重要的是当对象上的引用计数降为零时。

具体来说,您可以将代码放入

Private Sub Class_Initialize()

Private Sub Class_Terminate()