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

时间:2017-10-24 10:46:53

标签: excel vba excel-vba

在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程序中进一步处理。 我的问题是:如何保证电子表格范围内的更新值?

2 个答案:

答案 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