我可以从 Microsoft Access VBA 中的当前事件堆栈返回吗?

Posted

技术标签:

【中文标题】我可以从 Microsoft Access VBA 中的当前事件堆栈返回吗?【英文标题】:Can I Return From The Current Event Stack In Microsoft Access VBA? 【发布时间】:2017-02-27 17:56:24 【问题描述】:

我想知道在 Microsoft Access VBA 中是否存在一个预先构建的系统函数可以取消当前触发事件的整个内存堆栈,而不必退出堆栈跟踪中的每个过程?

我正在寻找类似以下的内容:

Public Sub Procedure2()
    CancelEvent 'This would return from procedure2, proedure1, and the specific event Sub
End Sub

Public Sub Procedure1()
    Procedure2
End Sub

Private Sub SomeControl_SomeEvent()
    Procedure1
End Sub

我已经尝试过DoCmd.CancelEvent,但是之后程序仍然继续执行,并且不取消整个过程堆栈(我可能一直在寻找错误的系统Sub)。我能想到实现这件事的唯一方法(没有讨厌的 If-Else 语句来检查如果我退出上面的堆栈过程是否必须继续下面的堆栈过程)是使用异常,并且具有以下内容:

Public Sub Procedure2()
    Err.Raise 1  'Just an example exception
End Sub

Public Sub Procedure1()
    Procedure2
End Sub

Private Sub SomeControl_SomeEvent()
    On Error Goto HandleError
    Goto StartSub
HandleError:
    Exit Sub
StartSub:
    Procedure1
End Sub

或者,是否可以仅在 VBA 中捕获特定异常,因为我可能必须仅在最低堆栈过程中捕获此异常?

【问题讨论】:

调用堆栈通常不是这样工作的。如果你收起一个堆栈,你需要解开它。如果Procedure2 中的问题实际上是一个错误条件,则在此处提出一个是适当的。如果它不是错误情况,您可能需要进行一些重构,以您不需要这样做的方式分离过程的职责。 它们是运行时错误,而不是异常。异常是完全不同的野兽,VBA 世界不知道。 您不会捕获 运行时错误,而是处理调用堆栈中更高的位置。在 VBA 中没有对调用堆栈的编程访问。就是说是的,确实,在某处引发的运行时错误会“冒泡”调用堆栈,直到它在某处被处理,或者作为一个例外,会破坏你的执行。看来您正在寻找error handling documentation。 确实,这很快就会变成一团糟——这与使用异常进行流控制没有什么不同! 您不需要GoTo 语句。您需要一个 On Error 语句 - On Error GoTo 只是将执行跳转到“catch”块的机制 - 错误处理子例程。 FWIW VBA 比 C# 早了至少十年,并且没有经历 Python 的重写。 【参考方案1】:

这必须作为您的错误处理策略来实施。

我会定义一个公共枚举来正式跟踪(和命名)自定义错误代码,从 vbObjectError + 一些值开始:

Public Enum CustomError
    CE_Cancelled = vbObjectError + 42
    CE_SomeOtherCustomError
    '...
End Enum

然后当你想“取消”一个事件并“向上走调用堆栈”时,你可以引发那个错误:

Public Sub RaiseOperationCancelledError(ByVal source As String)
    Err.Raise CE_Cancelled, source, "Operation was cancelled."
End Sub

RaiseOperationCancelledError 运行时究竟会发生什么,完全取决于此时调用堆栈中的 On Error 语句。

假设您在调用堆栈的顶部(实际上是 bottom)有一些控制事件:

Private Sub SomeControl_SomeEvent()
    On Error Goto CleanFail
    DoSomething
CleanExit:
    Exit Sub
CleanFail:
    If Err.Number = CE_Cancelled Then
        MsgBox Err.Description '"Operation was cancelled."
    Else
        Err.Raise Err.Number 'we don't know what happened; rethrow.
    End If
    Resume CleanExit
End Sub

如果DoSomething 有,IDK,比如文件 I/O,需要处理错误:

Sub DoSomething()
    On Error GoTo CleanFail
    Dim fileNumber As Long
    fileNumber = FreeFile
    'do stuff
    '...
CleanExit:
    Close fileNumber
    Exit Sub
CleanFail:
    If Err.Number = 53 Then
        ' handle "file not found" error
        Resume CleanExit
    Else If Err.Number = CE_Cancelled Then
        Close fileNumber ' we won't run CleanExit if we rethrow!
        Err.Raise Err.Number ' rethrow
    End If
End Sub

现在,这几乎是马虎了。那么有什么问题呢?

问题在于您将自定义运行时错误用于流控制,这不可避免地会变成任何语言的意大利面条代码,无论有无例外。

如果您正在做某事并且用户取消了它,那么您将拥有一个返回值的函数,该值可以准确地告诉您 - 例如。 用户取消了文件浏览器对话框,我们无法继续导出 - 您没有引发自定义错误!相反,您创建的函数的职责是返回用户选择的文件,使用某种机制告诉其调用者毕竟不会有导出目标 - 该函数可能会返回一个空字符串而不是有效的文件路径;然后调用者知道如果返回的字符串为空,它需要退出并返回给自己的调用者,自然地展开调用堆栈。

【讨论】:

再次感谢@Mat'sMug 的回复。现在说清楚了!

以上是关于我可以从 Microsoft Access VBA 中的当前事件堆栈返回吗?的主要内容,如果未能解决你的问题,请参考以下文章