在excel-spreadsheet中,用户定义的函数用于计算基本结果作为电子表格矩阵(复合元素的横截面值)。
Public Function XSValues(inputRange as Range) as variant
[...]
' returns an array and used as matrix-formula (Ctrl-Shift-Enter)
End Function
这些结果一方面在电子表格中使用。另一方面,基于来自这些电子表格结果的一些值,使用VBA过程来执行相对复杂且耗时的计算(结构模型的静态分析)。此过程由按钮触发。
Public Sub Recalculate()
[...]
myValue = range("SomeXLResult").Value
' calculation
' notification updates
' drawing
' places different results into cells
End Sub
现在,我的问题是,当Sub Recalculate
被触发时,电子表格计算已过时。
我发现在Excel 2016中,电子表格计算被分成多个线程。并且经验丰富的用户交互有时比电子表格计算更快。
因此,我得到了折旧值,以便在VBA程序中进一步处理。 我的问题是:如何保证电子表格范围内的更新值?
答案 0 :(得分:1)
如果您的答案中解释的解决方案对您有用,那么很好。我只是想知道你是否知道应用程序的d a
事件(https://msdn.microsoft.com/en-us/vba/excel-vba/articles/application-aftercalculate-event-excel):
只要计算完成且没有,就会发生此事件 出色的查询。必须满足这两个条件 在事件发生之前。即使没有,也可以举起活动 工作簿中的工作表数据,例如计算完成时的工作表数据 整个工作簿,没有运行查询。
加载项开发人员使用AfterCalculate事件来了解所有内容 任何查询和/或工作簿中的数据已完全更新 可能正在进行的计算。
此事件发生在所有工作表之后。计算,图表。计算 ,AfterRefresh和SheetChange事件。这是最后发生的事件 在所有刷新处理和所有计算处理完成之后, 它发生在应用程序之后。 CalculationState设置为xlDone。
这对您来说可能更容易实现。访问应用程序对象事件的技巧是在类模块中声明它Y
。例如,我调用了类 clsAppEvents :
AfterCalculate
在您的模块中,您只需拥有调用和事件处理代码:
WithEvents
仅供参考,调试结果如下:
按钮点击:25/10/2017 4:49:20 p.m。
Calc结束于:25/10/2017 4:49:22 p.m。
Sub来电:25/10/2017 4:49:22 p.m。
答案 1 :(得分:0)
最后,以下解决方案满足了我的需求:
按下重新计算按钮时,vba会检查当前的Excel计算状态。如果计算完成,则直接启动用于计算的VBA-程序Recalculate
。如果计算模式正在等待或计算,则只有本地工作表变量p_RecalcButtonClicked
设置为true。完成Excel计算后,每个工作表会在计算之后触发Worksheet_Calculate
事件。因此,我们可以指示Excel Recalculate
。
作为一项安全措施,我使用函数Recalculate
在子waitForRecalculation
开头之前保留了上述评论中相关两个问题中描述的解决方案。为了避免不活动,我引入了一个计时器来告诉用户,计算是否在给定的时间内无法完成。
这是主要工作表的代码:
' ##### Worksheet-Code
'''
' Private Worksheet-Variable to determine,
' if the button was pressed prior to worksheet calculated-event
'
Private p_RecalcButtonClicked As Boolean
'''
' Procedure to handle Button Clicked
' (either using a shape with a macro assigned or
' an Active-X-Button with this procedure as event handler: best is to use {Button}_MouseUp as {Button}_clicked is fired occasionally by excel itself)
'
Public Sub ButtonClicked()
'
' depending on the calculation state ...
'
Select Case Application.CalculationState
Case xlDone
'
' ... all done, fine ...
' ... directly call the calculation procedure sub Recalculate
'
p_RecalcButtonClicked = False
Recalculate
Case xlPending
'
' ... pending ...
' ... set local worksheet variable true in order to call sub Recalculate
' later, when the calculated-event was raised
'
p_RecalcButtonClicked = True
'
' instruct excel to recalculate
'
Application.CalculateFullRebuild
'
' now let excel perform until worksheet calculated event is raised
'
Case xlCalculating
'
' ... calculating ...
' ... set local worksheet variable true in order to call sub Recalculate
' later, when the calculated-event was raised
'
p_RecalcButtonClicked = True
'
' let excel continue until worksheet calculated event is raised
'
Case Else
End Select
End Sub
'''
' worksheet calculation finished
' this event is raised AFTER calculation was finished
' (shold actually be named Worksheet_Calculated)
'
Private Sub Worksheet_Calculate()
' check if the RecalcButton was clicked
If p_RecalcButtonClicked Then
p_RecalcButtonClicked = False
Recalculate
End If
End Sub
'''
' Recalculation
'
Public Sub wm_Recalculate()
'
' wait for calculation to be done
' just in case...
'
If Not waitForRecalculation Then
MsgBox "Press Ctrl+Alt+F9 for full recalculation", vbCritical + vbOKOnly, "Excel-calculation not done"
Exit Sub
End If
' [...] Your calculation here...
End Sub
'''
' Helper function to wait and do events until Excel-calculations are done
' returns true if calculation is done within the given time
'
Public Function waitForRecalculation() As Boolean
Const MAXTIME_S = 10
Dim t As Double
t = Timer()
' in case of sql-async queries this might be required
'
' Application.CalculateUntilAsyncQueriesDone
'
' As a safety net,
' the second solution is to
' do System events until calculation is done
'
If Application.CalculationState <> xlDone Then
Do
DoEvents
If Timer() - t > MAXTIME_S Then Exit Do
Loop Until Application.CalculationState = xlDone
End If
'
' return true if calculations are done
'
waitForRecalculation = (Application.CalculationState = xlDone)
End Function