如何确保Excel计算在VBA过程中完成

如何确保Excel计算在VBA过程中完成,vba,excel,Vba,Excel,在excel电子表格中,用户定义的函数用于将基本结果计算为电子表格矩阵(复合元素的横截面值) 这些结果一方面用于电子表格中。另一方面,基于这些电子表格结果中的一些值,使用VBA程序执行相对复杂且耗时的计算(结构模型的静态分析)。此过程由按钮触发 Public Sub Recalculate() [...] myValue = range("SomeXLResult").Value ' calculation ' notification updates '

在excel电子表格中,用户定义的函数用于将基本结果计算为电子表格矩阵(复合元素的横截面值)

这些结果一方面用于电子表格中。另一方面,基于这些电子表格结果中的一些值,使用VBA程序执行相对复杂且耗时的计算(结构模型的静态分析)。此过程由按钮触发

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