检测嵌套公式

时间:2015-05-01 19:31:58

标签: excel vba excel-vba

我有一个非常复杂的工作簿,有很多标签。标签可以具有普通数据或各种单元格中的公式。在公式的情况下,公式可以从一个图纸嵌套到下一个图片(即图纸1上的公式指的是图纸2上的公式,其反过来指的是图纸3上的公式等)。

我有一个隐藏的标签,其中包含以下内容:来源表,来源范围,目标表和目标范围。

已在这4个字段和所有适用的行上创建了命名范围。

当我们希望将数据保存到数据库时,我们遍历范围映射中的每一行,并将数据从源表/范围复制到目标表/范围。在此之后,适用的数据被序列化为XML并发送到要保存的Web服务。

我们希望解决的问题是,当用户对源范围进行更改时,我们希望在隐藏工作表上标记单元格。由于公式可以嵌套,因此Worksheet_Change事件不会获取更改。

由于一张纸上的更改可能会影响另一张不是活动纸张的纸张,因此Workbook_SheetChange事件也不会捕获更改。

当映射中定义的工作表发生更改时,是否有任何形式可以捕获,即使它是公式更改多个级别的结果?

修改

感谢您的回复。我试图找到最快且过程最少的方法来确定数据是否在监控范围内发生变化。数据可能包含实际数据或嵌套公式。

我的研究表明,由于我无法检测到监控范围内的数据是否被修改,因此无法通过范围交叉实现此结果。这是因为监控范围可能不在活动工作表上,也可能包含公式。

我已经展示了用于实际检测下面的更改的方法。如果有更好的方法可以获得相同的结果,我会很感激。

2 个答案:

答案 0 :(得分:1)

如果通过公式更改单元格值,则Worksheet_Change事件将无效。您需要Worksheet_Calculate。

查看我的示例工作簿here

Here用于示例代码的WebPage

答案 1 :(得分:0)

当被监控的公式不在活动工作表上时,没有“简单”的方法来检测嵌套公式是否已更改。虽然我希望检测修改后的范围并使用范围的交集来设置标志,但这是不可能的,因为Worksheet_Change事件不适用于公式,而Workbook_SheetChange事件仅适用于活动工作表。由于我的工作簿有超过20个标签和20 - 30个范围被监控,这种方法不起作用。出于速度目的,这种方法是理想的。

相反,工作簿需要“检查”以查看当前值是否与上次调用save to database事件时的值相同。如果没有,将设置脏标志。

此方法的代码如下所示。

下图显示了映射范围的一个示例,但实际上有20-30行包含此范围。

Mapping Range

还有其他三张工作表,其中Sheet3包含A1中的实际数据:H1和Sheet2具有指向Sheet3的公式。 Sheet1具有指向Sheet2的公式。

正如映射范围所示,我们正在查看Sheet1上的范围,即使可能对Sheet3进行了更改。

使用的代码如下所示。

Option Explicit
Public Sub DetermineIfEditOccurred()

Dim oMappingRange As Range
Dim szSourceTab As String
Dim szSourceRange As String
Dim oSourceRange As Range
Dim szTargetTab As String
Dim szTargetRange As String
Dim oTargetRange As Range
Dim oWorksheetSource As Worksheet
Dim oWorksheetTarget As Worksheet
Dim oRangeIntersection As Range
Dim nRowCounter As Long
Dim nCellCounter As Long

Dim szSourceValue As String
Dim szTargetValue As String
Dim oCell As Range
Dim bIsDirty As Boolean

If Range(ThisWorkbook.Names("DirtyFlag")).Value = 0 Then
    Set oMappingRange = Range(ThisWorkbook.Names("Mapping"))

    For nRowCounter = 1 To oMappingRange.Rows.Count
        szSourceTab = oMappingRange(nRowCounter, 1)
        szSourceRange = oMappingRange(nRowCounter, 2)

        szTargetTab = oMappingRange(nRowCounter, 3)
        szTargetRange = oMappingRange(nRowCounter, 4)

        Set oWorksheetSource = ThisWorkbook.Worksheets(szSourceTab)
        Set oWorksheetTarget = ThisWorkbook.Worksheets(szTargetTab)
        Set oSourceRange = oWorksheetSource.Range(szSourceRange)
        Set oTargetRange = oWorksheetTarget.Range(szTargetRange)

        nCellCounter = 1

        For Each oCell In oSourceRange.Cells
            szSourceValue = oCell.Value

            If szSourceValue = "#NULL!" Or _
               szSourceValue = "#DIV/0!" Or _
               szSourceValue = "#VALUE!" Or _
               szSourceValue = "#REF!" Or _
               szSourceValue = "#NAME?" Or _
               szSourceValue = "#NUM!" Or _
               szSourceValue = "#N/A" Then

               szSourceValue = ""
            End If

            szTargetValue = GetCellValueByPosition(oTargetRange, nCellCounter)

            If szSourceValue <> szTargetValue Then
                Range(ThisWorkbook.Names("DirtyFlag")).Value = 1
                bIsDirty = True
                Exit For
            End If

            nCellCounter = nCellCounter + 1
        Next

        If bIsDirty Then
            Exit For
        End If
    Next
End If
End Sub
Public Function GetCellValueByPosition(oRange As Range, nPosition As Long) As String

   Dim oCell As Range
   Dim nCounter As Long
   Dim szValue As String

   nCounter = 1

   For Each oCell In oRange
       If nCounter = nPosition Then
            szValue = oCell.Value

            Exit For
       End If

       nCounter = nCounter + 1
   Next

   GetCellValueByPosition = szValue
End Function

Workbook_SheetChange事件如下:

Option Explicit
Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Call DetermineIfEditOccurred
End Sub
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
    If Sh.Name <> "MAPPING" Then
        Call DetermineIfEditOccurred
    End If
End Sub