How to ensure Excel calculation is completed from within a VBA procedure

前端 未结 2 589
渐次进展
渐次进展 2021-01-16 07:39

In an excel-spreadsheet user-defined functions are used to calculate basic results as spread-sheet matrices (cross-section values of composite elements).

Pu         


        
2条回答
  •  长情又很酷
    2021-01-16 07:57

    Finally, the follwing solution satisfies my needs:

    When a button for recalculation is pressed, vba checks the current Excel calculation state. In case, calculation is done, the VBA-Procedure for calculation Recalculate is started directly. In case, calculation-mode is pending or calculating, then only the local worksheet-variable p_RecalcButtonClicked is set to true. When excel calculation is done, each worksheet fires the Worksheet_Calculate event after it was calculated. And so we can instruct Excel to Recalculate then.

    As a safety measure, I kept the solution described in the related two questions from the above comment before at the beginning of the sub Recalculate using the function waitForRecalculation. To avoid inactivity, I introduced a timer to tell the user, if calculation could not be finished within a given amount of time.

    This is the code of the main worksheet:

    ' ##### 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
    

提交回复
热议问题