应用程序事件,确定 Excel 何时真正准备好读取

Application events, determine when Excel is really ready to read from

我一直在考虑允许应用程序使用 COM 互操作或 Excel-dna link 和 Excel。理想情况下,用户可以 link 任何可能包含或不包含 VBA 代码的 Excel 工作簿。通常操作是

i) C# 写入 Excel sheet

ii) Excel 重新计算

iii) 如果用户

包含任何 Worksheet_Change 或 Worksheet_Calculate 事件,则 Excel 可以 运行 VBA

iv) C# 然后从 Excel sheet 中读回,其中将包含来自 calcs/VBA

的更新数据

是否有最终应用程序事件通知一切何时完成?

例如,应用程序级事件包括 AfterCalculate()SheetChange,但其中一个或两个都可能触发,这使得很难知道对上面的 iv) 中的哪一个做出反应。

我在 VBA 中设置了一个小型模拟项目,当更改作品时 sheet 应用程序级事件按以下顺序触发

Worksheet_Calculate
mApp_AfterCalculate Calc ended at: 20/03/2020 13:23:29
Worksheet_Change Started
Worksheet_Change Ended
mApp_SheetChange Calc ended at: 20/03/2020 13:23:29 --> New Value: 20/03/2020 13:23:29

如果有一个简单的方法可以知道 mApp_AfterCalculate 将是最终事件还是 mApp_SheetChange?

模拟项目代码:

工作表 1:

Private Sub Worksheet_Calculate()
    Debug.Print ("Worksheet_Calculate")
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)

    Debug.Print ("Worksheet_Change Started")
    Call Setup

    Application.EnableEvents = False
    Sheet1.Range("F1").Value = Now
    Application.EnableEvents = True

    Debug.Print ("Worksheet_Change Ended")
End Sub

Class 模块:clsAppEvents

Option Explicit

Private WithEvents mApp As Application

Private Sub Class_Initialize()
    Set mApp = Application
End Sub

Private Sub mApp_AfterCalculate()
    Debug.Print "mApp_AfterCalculate Calc ended at: " & Now
End Sub

Private Sub mApp_SheetChange(ByVal Sh As Object, ByVal Target As Range)
    Debug.Print "mApp_SheetChange Calc ended at: " & Now & " --> New Value: " & Sh.Range("F1").Value

End Sub

模块 1:

Private mAppEvents As clsAppEvents

Public Sub Setup()
    If mAppEvents Is Nothing Then
        Debug.Print ("Setup initialized")
        Set mAppEvents = New clsAppEvents
    End If
End Sub

根据我对你问题的理解,根本问题是你试图通过假设事件将按特定顺序执行来预测 Excel 的计算图,并且它们将始终同步执行...两者都无法可靠地预测 100% 的时间。

我的建议是使用一种不同的、更可靠的方法,将写作与阅读分离:

  • Write:在写入 spreadsheet 的 C# 代码上,就这样做。写而不等待任何东西(并且不读任何东西)

  • Read:同样在 C# 端,订阅 Application.SheetChange 并在那里做出反应 - 即每次此事件 运行s在 C# 端,从 spreadsheet.

  • 中读取你需要的值

这样,谁更改了点差都没有关系sheet...如果是您的 C# 代码、VBA 代码或用户手动更改它。您知道,每次传播 sheet 发生变化时,都会导致您的 C# 代码变为 运行 并使您能够根据变化是否发生在 sheet 中做出反应(或不反应)你关心并且变化发生在你关心的范围内。

伪代码你的 Application.SheetChange 会是什么样子:

public void Application_SheetChange(object sheet, Range range) 
{ 
    if (range.Worksheet.Name != "SheetYouAreInterested")
    {
        return; // nothing to do
    }

    if (!range.IntersectsWith(rangeYouAreInterested))
    {
        return; // nothing to do
    }

    // Read the values that changed, etc.
}

现在,事件的顺序也不再重要,因为每次发生更改时您的事件处理程序都会继续执行,因此您之前可能已经阅读过的任何陈旧值最终都会在最后一次刷新事件 运行s.

当然,读取代码不要直接对sheet做任何修改...应该只读取,否则你会陷入死循环:D