如何确保Excel计算在VBA过程中完成
在excel电子表格中,用户定义的函数用于将基本结果计算为电子表格矩阵(复合元素的横截面值) 这些结果一方面用于电子表格中。另一方面,基于这些电子表格结果中的一些值,使用VBA程序执行相对复杂且耗时的计算(结构模型的静态分析)。此过程由按钮触发如何确保Excel计算在VBA过程中完成,vba,excel,Vba,Excel,在excel电子表格中,用户定义的函数用于将基本结果计算为电子表格矩阵(复合元素的横截面值) 这些结果一方面用于电子表格中。另一方面,基于这些电子表格结果中的一些值,使用VBA程序执行相对复杂且耗时的计算(结构模型的静态分析)。此过程由按钮触发 Public Sub Recalculate() [...] myValue = range("SomeXLResult").Value ' calculation ' notification updates '
Public Sub Recalculate()
[...]
myValue = range("SomeXLResult").Value
' calculation
' notification updates
' drawing
' places different results into cells
End Sub
现在,我的问题是,当触发子重新计算时,电子表格计算已过时。
我发现在Excel 2016中,电子表格计算被分成多个线程。并且体验到用户交互有时比电子表格计算更快
因此,我得到折旧值,以便在VBA过程中进行进一步处理。
我的问题是:如何保证电子表格范围内的更新值?以下解决方案满足我的需要:
按下重新计算按钮时,vba将检查当前Excel计算状态。如果计算完成,则直接启动用于计算的VBA过程<代码>重新计算代码>。如果计算模式为挂起或计算,则只有本地工作表变量p\u RecalcButtonClicked
设置为true。excel计算完成后,每个工作表在计算后触发工作表\u Calculate
事件。因此,我们可以指示Excel重新计算
作为一项安全措施,我使用函数waitForRecalculation
保留子recalculation
开头所述的解决方案。为了避免不活动,我引入了一个计时器来告诉用户,如果计算不能在给定的时间内完成
这是主工作表的代码:
' ##### 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
工作表代码
'''
'要确定的专用工作表变量,
'如果在工作表计算事件之前按下该按钮
'
私有p_RecalcButton被标记为布尔值
'''
'处理单击的按钮的过程
'(使用指定了宏的形状或
'将此过程作为事件处理程序的Active-X-Button:最好使用{Button}鼠标,因为{Button}单击的按钮偶尔会由excel本身触发)
'
公共子按钮单击()
'
'根据计算状态。。。
'
选择Case Application.CalculationState
案例xlDone
'
' ... 全部完成,很好。。。
' ... 直接调用计算程序子重新计算
'
p_RecalcButtonClicked=False
重新计算
案件42未决
'
' ... 悬而未决的
' ... 将本地工作表变量设置为true以调用子重新计算
'稍后,当引发计算的事件时
'
p_RecalcButtonClicked=真
'
'指示excel重新计算
'
Application.calculatealReBuild
'
'现在让excel执行,直到引发工作表计算事件
'
案例xl计算
'
' ... 精明的。。。
' ... 将本地工作表变量设置为true以调用子重新计算
'稍后,当引发计算的事件时
'
p_RecalcButtonClicked=真
'
'让excel继续,直到引发工作表计算事件
'
其他情况
结束选择
端接头
'''
'工作表计算已完成
'此事件在计算完成后引发
'(应实际命名为工作表_计算)
'
专用子工作表_Calculate()
'检查是否单击了重新校准按钮
如果p_重新单击按钮,则
p_RecalcButtonClicked=False
重新计算
如果结束
端接头
'''
"重新计算",
'
公共子wm_重新计算()
'
'等待计算完成
“以防万一。。。
'
如果不等待重新计算,则
MsgBox“按Ctrl+Alt+F9进行完全重新计算”,vbCritical+vbOKOnly,“Excel计算未完成”
出口接头
如果结束
“[…]你在这里的计算。。。
端接头
'''
'Helper函数等待并执行事件,直到Excel计算完成
'如果在给定时间内完成计算,则返回true
'
公共函数WaitForRecomulation()为布尔值
常量最大时间=10
调暗t为双色
t=计时器()
'对于sql异步查询,这可能是必需的
'
'Application.CalculateUntilAsyncQueriesDone
'
作为安全网,,
第二个办法是,
'在计算完成之前执行系统事件
'
如果Application.CalculationState xl完成,则
做
多芬特
如果Timer()-t>MAXTIME\u S,则退出Do
循环直到Application.CalculationState=xlDone
如果结束
'
'如果计算完成,则返回true
'
WaitForRecomulation=(Application.CalculationState=xlDone)
端函数
以下解决方案满足了我的需要:
按下重新计算按钮时,vba将检查当前Excel计算状态。如果计算完成,则直接启动用于计算的VBA过程<代码>重新计算代码>。如果计算模式为挂起或计算,则只有本地工作表变量p\u RecalcButtonClicked
设置为true。excel计算完成后,每个工作表在计算后触发工作表\u Calculate
事件。因此,我们可以指示Excel重新计算
作为一项安全措施,我使用函数waitForRecalculation
保留子recalculation
开头所述的解决方案。到av
' ##### 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
Option Explicit
Private WithEvents mApp As Application
Private Sub Class_Initialize()
Set mApp = Application
End Sub
Private Sub mApp_AfterCalculate()
Debug.Print "Calc ended at: " & Now
ConsumeAfterCalculate
End Sub
Option Explicit
Private mAppEvents As clsAppEvents
Private mEnableConsume As Boolean
Public Sub RunMe()
Set mAppEvents = New clsAppEvents
End Sub
Public Sub ConsumeAfterCalculate()
If mEnableConsume Then
Debug.Print "Sub called at: " & Now
mEnableConsume = False
End If
End Sub
Public Sub ConsumeButtonClick()
Debug.Print "Button clicked at: " & Now
mEnableConsume = True
'For demo purposes I'm just forcing a calculation on existing data.
Sheet1.EnableCalculation = False
Sheet1.EnableCalculation = True
End Sub