通过 long-运行 过程(DoEvents 或其他)持续接收用户输入

Consistently receiving user input through a long-running procedure (DoEvents or otherwise)

彻底取消一个漫长的 API-缠身程序是地狱般的,我正试图找出最好的方法来驾驭这种地狱般的状态。

我正在使用 excel 2016(手动计算且没有屏幕更新)- 我可能需要一些时间来尝试 运行 2010 年的程序,看看是否有任何问题在未来几天(我知道速度放缓)。

随着时间的推移,我的程序 LongProcedure 失去了成功使用其取消功能的能力(可能是由于复杂性增加)。它最初是不一致的,需要大量垃圾点击才能取消,现在完全失败了

设置如下:
首先,LongProcedure 在 class 模块 LongClass 中,并检查了 public 属性 是否提前取消,允许它清理。

Public Sub LongProcedure()
    ' [Set up some things] '
    For Each ' [Item In Some Large Collection (Est. 300 Items)] '
        ' [Some Code (ETA 5 Seconds) Sprinkled with 3-4 DoEvents] '
        ' [Export workbook (ETA 10 Seconds)] '
        If (cancelLongProcedure) Then Exit For
    Next
    ' [Clean up some things] '
    GeneratorForm.Reset ' Let the UserForm know we're finished
End Sub

其次,我有一个从宏中显示的用户窗体,它实例化了过程 class 和 运行 过程。它包含一个 运行 按钮、一个状态标签和一个取消按钮。

Private MyLong As LongClass

Public Sub ButtonRunLongProcedure_Click()
    Set myLong = New LongClass
    myLong.LongProcedure()
End Sub

所以这个问题总体上是双重的。
ExportAsFixedFormat 调用会打开一个 "Publishing..." 进度条,它会冻结 excel 大约十秒钟 - 很好。在我所有的努力中,我还没有找到一种方法来处理发生这种情况时的用户输入。
最重要的是,DoEvents 调用似乎不再执行任何操作以允许单击取消按钮。该进程不一致地冻结 excel,切换到其他打开的程序,并(未冻结时)更新状态标签。

我试过:

这是一个我愿意进行重大重构工作的问题,包括将长过程的想法分解为单独的方法,最初执行设置,并在 class 终止时进行清理。我正在寻找 可以提供一致结果的东西 。 - 我会接受从 excel 版本到 excel 设置的任何内容,再到重构以赢得 API 调用。

感谢您对此的任何见解。

事实证明,将一些有用的改进与一个新的改进简单地结合在一起,就会产生很大的不同。

  • QueryClose 看个人喜好。保留它以捕获更多终止,保留它以确保用户使用新解决方案
  • 坚持在您认为合乎逻辑的地方添加 doEvents(不仅仅是在状态栏更新时 - 例如 Application.Calculate 调用前后)
  • 尽可能优化长运行过程,避免excel次调用

而且,最重要的是

  • 集成的取消键功能(默认情况下 CTRL+Break)比用户窗体按钮 窗体关闭按钮 没有 意外结束 excel 任务的几率。

成品的打磨过程如下

首先,设置一个debugMode,或相反的handleErrors,模块级变量来控制是否实现中断取消和错误处理。 (错误处理会使你的代码更难调试,所以你会喜欢这个开关)

如果您的进程正在处理错误,您可以将 Application.EnableCancelKey 设置为 xlErrorHandlerOn Error GoTo [ErrorHandlingLabel]。错误处理标签应该直接在清理之前,并立即将 EnableCancelKey 设置为 xlDisabled 以避免错误。在继续执行清理步骤之前,您的处理程序应检查存储的 Err.Number 并采取相应行动。

确保如果您在脚本中遵循任何其他复杂的 vba(例如在带有 UDF 的 sheet 上使用 Application.Calculate),您事先设置 On Error GoTo 0 , 和 On Error GoTo [ErrorHandlingLabel] 之后,以避免捕获 cellbound 错误。

不幸的是,缺点是要使 UX 始终可读,您必须将取消键保留在 xlDisabled 上,直到表单关闭。

在代码中:

Public Sub LongProcedure()
    If handleErrors Then
        On Error GoTo ErrorHandler
        Application.EnableCancelKey = xlErrorHandler
    End If
    ' [Set up some things] '
    For Each ' [Item In Some Large Collection (Est. 300 Items)] '
        ' [Some Code (ETA 5 Seconds) Sprinkled with 3-4 DoEvents] '
        ' [Export workbook (ETA 10 Seconds)] '
    Next
ErrorHandler:
    If handleErrors Then
        Application.EnableCancelKey = xlDisabled
        If (Err.Number <> 0 And Err.Number <> 18) Then
            MsgBox Err.Description, vbOKOnly, "Error " & CStr(Err.Number)
        End If
        Err.Clear
        On Error GoTo 0
    End If
    ' [Clean up some things] '
    GeneratorForm.Reset ' Let the UserForm know we're finished
End Sub

并在用户窗体中

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If MyLong.handleErrors Then Application.EnableCancelKey = xlInterrupt
End Sub

请注意,此方法可能会产生一些错误,因为执行会直接 到指定的标签。您的清理代码需要从一开始就实例化所需的变量。

总的来说,一旦这些问题得到解决,此设置可确保用户可以点击 CTRL+Break 任意多次,而不会导致崩溃或弹出窗口。