通过 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,切换到其他打开的程序,并(未冻结时)更新状态标签。
我试过:
- 将 DoEvents 附加到
SetStatusLabel
方法而不是散布 - 虽然表单仍然经常冻结,但它会持续更新状态标签(同时仍然不允许取消按钮)
- 使用 winAPI
Sleep
代替并补充 DoEvents
,延迟分别为 1、5、10、50 和 250 毫秒 - 表格只是停止更新于都没有 doevents,而且两者都冻结得更多。
- 使用
Do While
循环 运行 DoEvents
持续一秒钟(冻结)
- 重写 QueryClose 以取消表单。这个帮助很大。出于某种原因,关闭 [x] 按钮可以比用户窗体按钮更一致地被点击 far - 仍然没有我想要的那么一致。问题?在发布期间,Excel 停止响应,因此,如果您单击关闭按钮两次,现代 windows 将结束该过程...无需清理。
- 使用Application.OnTime 定期调用 DoEvents。总体情况似乎没有改善
- Alt-Tab 键。不完全是。出于某种原因,虽然 alt-tab 有时只会使用户窗体更难冻结,但有时它会停止冻结和更新。
这是一个我愿意进行重大重构工作的问题,包括将长过程的想法分解为单独的方法,最初执行设置,并在 class 终止时进行清理。我正在寻找 可以提供一致结果的东西 。 - 我会接受从 excel 版本到 excel 设置的任何内容,再到重构以赢得 API 调用。
感谢您对此的任何见解。
事实证明,将一些有用的改进与一个新的改进简单地结合在一起,就会产生很大的不同。
- QueryClose 看个人喜好。保留它以捕获更多终止,保留它以确保用户使用新解决方案
- 坚持在您认为合乎逻辑的地方添加
doEvents
(不仅仅是在状态栏更新时 - 例如 Application.Calculate
调用前后)
- 尽可能优化长运行过程,避免excel次调用
而且,最重要的是
- 集成的取消键功能(默认情况下
CTRL+Break
)比用户窗体按钮 和 窗体关闭按钮 没有 意外结束 excel 任务的几率。
成品的打磨过程如下
首先,设置一个debugMode
,或相反的handleErrors
,模块级变量来控制是否实现中断取消和错误处理。 (错误处理会使你的代码更难调试,所以你会喜欢这个开关)
如果您的进程正在处理错误,您可以将 Application.EnableCancelKey
设置为 xlErrorHandler
和 On 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
任意多次,而不会导致崩溃或弹出窗口。
彻底取消一个漫长的 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,切换到其他打开的程序,并(未冻结时)更新状态标签。
我试过:
- 将 DoEvents 附加到
SetStatusLabel
方法而不是散布 - 虽然表单仍然经常冻结,但它会持续更新状态标签(同时仍然不允许取消按钮) - 使用 winAPI
Sleep
代替并补充DoEvents
,延迟分别为 1、5、10、50 和 250 毫秒 - 表格只是停止更新于都没有 doevents,而且两者都冻结得更多。 - 使用
Do While
循环 运行DoEvents
持续一秒钟(冻结) - 重写 QueryClose 以取消表单。这个帮助很大。出于某种原因,关闭 [x] 按钮可以比用户窗体按钮更一致地被点击 far - 仍然没有我想要的那么一致。问题?在发布期间,Excel 停止响应,因此,如果您单击关闭按钮两次,现代 windows 将结束该过程...无需清理。
- 使用Application.OnTime 定期调用 DoEvents。总体情况似乎没有改善
- Alt-Tab 键。不完全是。出于某种原因,虽然 alt-tab 有时只会使用户窗体更难冻结,但有时它会停止冻结和更新。
这是一个我愿意进行重大重构工作的问题,包括将长过程的想法分解为单独的方法,最初执行设置,并在 class 终止时进行清理。我正在寻找 可以提供一致结果的东西 。 - 我会接受从 excel 版本到 excel 设置的任何内容,再到重构以赢得 API 调用。
感谢您对此的任何见解。
事实证明,将一些有用的改进与一个新的改进简单地结合在一起,就会产生很大的不同。
- QueryClose 看个人喜好。保留它以捕获更多终止,保留它以确保用户使用新解决方案
- 坚持在您认为合乎逻辑的地方添加
doEvents
(不仅仅是在状态栏更新时 - 例如Application.Calculate
调用前后) - 尽可能优化长运行过程,避免excel次调用
而且,最重要的是
- 集成的取消键功能(默认情况下
CTRL+Break
)比用户窗体按钮 和 窗体关闭按钮 没有 意外结束 excel 任务的几率。
成品的打磨过程如下
首先,设置一个debugMode
,或相反的handleErrors
,模块级变量来控制是否实现中断取消和错误处理。 (错误处理会使你的代码更难调试,所以你会喜欢这个开关)
如果您的进程正在处理错误,您可以将 Application.EnableCancelKey
设置为 xlErrorHandler
和 On 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
任意多次,而不会导致崩溃或弹出窗口。