【问题标题】:Excel VBA code to trace precedents of cellExcel VBA代码跟踪单元格的先例
【发布时间】:2017-09-03 17:01:36
【问题描述】:

我有以下代码,它跟踪活动单元格的先例并吐出一个带有信息的消息框。 (它还在其他工作表和工作簿中搜索先例)。

我是 VBA 新手,我想请求有关更改此代码的帮助,以便在活动工作表之后将前面的单元格、公式和地址吐出到新工作表中。请有人帮我理解如何做到这一点。

我应该创建一个新函数来创建一个新工作表并将动态信息复制到第一个 sub 中吗?

例如,如果我在 Sheet1 的单元格 C1 中有公式 A1 + B1,那么我想要在 Sheet2(新创建的工作表)中显示目标单元格为 C1,目标工作表为 Sheet1,源单元格为A1,源表为Sheet1。我还想要在 Sheet2 中显示目标单元格为C1,目标表为Sheet1,源单元格为B1,源表为Sheet1

表 2:

代码:

Option Explicit 
Public OtherWbRefs As Collection 
Public ClosedWbRefs As Collection 
Public SameWbOtherSheetRefs As Collection 
Public SameWbSameSheetRefs As Collection 
Public CountOfClosedWb As Long 
Dim headerString As String 

Sub RunMe() 
    Call FindCellPrecedents(ActiveCell) 
End Sub 

Sub FindCellPrecedents(homeCell As Range) 
    Dim i As Long, j As Long, pointer As Long 
    Dim maxReferences As Long 
    Dim outStr As String 
    Dim userInput As Long 

    If homeCell.HasFormula Then 
        Set OtherWbRefs = New Collection: CountOfClosedWb = 0 
        Set SameWbOtherSheetRefs = New Collection 
        Set SameWbSameSheetRefs = New Collection 

        Rem find closed precedents from formula String 
        Call FindClosedWbReferences(homeCell) 

        Rem find Open precedents from navigate arrows 
        homeCell.Parent.ClearArrows 
        homeCell.ShowPrecedents 
        headerString = "in re: the formula in " & homeCell.Address(, , , True) 
        maxReferences = Int(Len(homeCell.Formula) / 3) + 1 
On Error GoTo LoopOut: 
        For j = 1 To maxReferences 
            homeCell.NavigateArrow True, 1, j 
            If ActiveCell.Address(, , , True) = homeCell.Address(, , , True) Then 
                Rem closedRef 
                Call CategorizeReference("<ClosedBook>", homeCell) 
            Else 
                Call CategorizeReference(ActiveCell, homeCell) 
            End If 
        Next j 
LoopOut: 

        On Error GoTo 0 
        For j = 2 To maxReferences 
            homeCell.NavigateArrow True, j, 1 
            If ActiveCell.Address(, , , True) = homeCell.Address(, , , True) Then Exit For 
            Call CategorizeReference(ActiveCell, homeCell) 
        Next j 
        homeCell.Parent.ClearArrows 

        Rem integrate ClosedWbRefs (from parsing) With OtherWbRefs (from navigation) 
        If ClosedWbRefs.Count <> CountOfClosedWb Then 
            If ClosedWbRefs.Count = 0 Then 
                MsgBox homeCell.Address(, , , True) & " contains a formula with no precedents." 
                Exit Sub 
            Else 
                MsgBox "string-" & ClosedWbRefs.Count & ":nav " & CountOfClosedWb 
                MsgBox "Methods find different # of closed precedents." 
                End 
            End If 
        End If 

        pointer = 1 
        For j = 1 To OtherWbRefs.Count 
            If OtherWbRefs(j) Like "<*" Then 
                OtherWbRefs.Add Item:=ClosedWbRefs(pointer), key:="closed" & CStr(pointer), after:=j 
                pointer = pointer + 1 
                OtherWbRefs.Remove j 
            End If 
        Next j 

        Rem present findings 
        outStr = homeCell.Address(, , , True) & " contains a formula with:" 
        outStr = outStr & vbCrLf & vbCrLf & CountOfClosedWb & " precedents in closed workbooks." 
        outStr = outStr & vbCr & (OtherWbRefs.Count - CountOfClosedWb) & " precedents in other workbooks that are open." 
        outStr = outStr & vbCr & SameWbOtherSheetRefs.Count & " precedents on other sheets in the same workbook." 
        outStr = outStr & vbCr & SameWbSameSheetRefs.Count & " precedents on the same sheet." 
        outStr = outStr & vbCrLf & vbCrLf & "YES - See details about Other Books." 
        outStr = outStr & vbCr & "NO - See details about The Active Book." 
        Do 
            userInput = MsgBox(prompt:=outStr, Title:=headerString, Buttons:=vbYesNoCancel + vbDefaultButton3) 
            Select Case userInput 
            Case Is = vbYes 
                MsgBox prompt:=OtherWbDetail(), Title:=headerString, Buttons:=vbOKOnly 
            Case Is = vbNo 
                MsgBox prompt:=SameWbDetail(), Title:=headerString, Buttons:=vbOKOnly 
            End Select 
        Loop Until userInput = vbCancel 
    Else 
        MsgBox homeCell.Address(, , , True) & vbCr & " does not contain a formula." 
    End If 
End Sub 

Sub CategorizeReference(Reference As Variant, Home As Range) 
    Rem assigns reference To the appropriate collection 
    If TypeName(Reference) = "String" Then 
        Rem String indicates reference To closed Wb 
        OtherWbRefs.Add Item:=Reference, key:=CStr(OtherWbRefs.Count) 
        CountOfClosedWb = CountOfClosedWb + 1 
    Else 
        If Home.Address(, , , True) = Reference.Address(, , , True) Then Exit Sub 
        If Home.Parent.Parent.Name = Reference.Parent.Parent.Name Then 
            Rem reference In same Wb 
            If Home.Parent.Name = Reference.Parent.Name Then 
                Rem sameWb sameSheet 
                SameWbSameSheetRefs.Add Item:=Reference.Address(, , , True), key:=CStr(SameWbSameSheetRefs.Count) 
            Else 
                Rem sameWb Other sheet 
                SameWbOtherSheetRefs.Add Item:=Reference.Address(, , , True), key:=CStr(SameWbOtherSheetRefs.Count) 
            End If 
        Else 
            Rem reference To other Open Wb 
            OtherWbRefs.Add Item:=Reference.Address(, , , True), key:=CStr(OtherWbRefs.Count) 
        End If 
    End If 
End Sub 

Sub FindClosedWbReferences(inRange As Range) 
    Rem fills the collection With closed precedents parsed from the formula String 
    Dim testString As String, returnStr As String, remnantStr As String 
    testString = inRange.Formula 
    Set ClosedWbRefs = New Collection 

    Do 
        returnStr = NextClosedWbRefStr(testString, remnantStr) 
        ClosedWbRefs.Add Item:=returnStr, key:=CStr(ClosedWbRefs.Count) 
        testString = remnantStr 
    Loop Until returnStr = vbNullString 

    ClosedWbRefs.Remove ClosedWbRefs.Count 
End Sub 
Function NextClosedWbRefStr(FormulaString As String, Optional ByRef Remnant As String) As String 
    Dim workStr As String 
    Dim start As Long, interval As Long, del As Long 
    For start = 1 To Len(FormulaString) 
        For interval = 2 To Len(FormulaString) - start + 1 
            workStr = Mid(FormulaString, start, interval) 
            If workStr Like Chr(39) & "[!!]*'![$A-Z]*#" Then 
                If workStr Like Chr(39) & "[!!]*'!*[$1-9A-Z]#" Then 
                    interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "#") 
                    interval = interval - 3 * CLng(Mid(FormulaString, start + interval, 1) = ":") 
                    interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]") 
                    interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]") 
                    interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]") 
                    interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]") 
                    NextClosedWbRefStr = Mid(FormulaString, start, interval) 
                    Remnant = Mid(FormulaString, start + interval) 
                    Exit Function 
                End If 
            End If 
        Next interval 
    Next start 
End Function 

Function OtherWbDetail() As String 
    Rem display routine 
    OtherWbDetail = OtherWbDetail & "There are " & OtherWbRefs.Count & " references to other workbooks. " 
    OtherWbDetail = OtherWbDetail & IIf(CBool(CountOfClosedWb), CountOfClosedWb & " are closed.", vbNullString) 
    OtherWbDetail = OtherWbDetail & vbCr & "They appear in the formula in this order:" & vbCrLf & vbCrLf 
    OtherWbDetail = OtherWbDetail & rrayStr(OtherWbRefs, vbCr) 
End Function 
Function SameWbDetail() As String 
    Rem display routine 
    SameWbDetail = SameWbDetail & "There are " & SameWbOtherSheetRefs.Count & " ref.s to other sheets in the same book." 
    SameWbDetail = SameWbDetail & vbCr & "They appear in this order, including duplications:" & vbCrLf & vbCrLf 
    SameWbDetail = SameWbDetail & rrayStr(SameWbOtherSheetRefs, vbCr) 
    SameWbDetail = SameWbDetail & vbCrLf & vbCrLf & "There are " & SameWbSameSheetRefs.Count & " precedents on the same sheet." 
    SameWbDetail = SameWbDetail & vbCr & "They are (out of order, duplicates not noted):" & vbCrLf & vbCrLf 
    SameWbDetail = SameWbDetail & rrayStr(SameWbSameSheetRefs, vbCr) 
End Function 
Function rrayStr(ByVal inputRRay As Variant, Optional Delimiter As String) 
    Rem display routine 
    Dim xVal As Variant 
    If IsEmpty(inputRRay) Then Exit Function 
    If Delimiter = vbNullString Then Delimiter = " " 
    For Each xVal In inputRRay 
        rrayStr = rrayStr & Delimiter & xVal 
    Next xVal 
    rrayStr = Mid(rrayStr, Len(Delimiter) + 1) 
End Function

【问题讨论】:

  • 这就是为什么我说“我有以下代码”=p haha​​ 尽量不重新发明轮子
  • 这就是为什么我提出“甚至不需要弄清楚你的原始代码在做什么!”在我的回答中。从MsgBox 获取预先格式化的数据似乎更容易(也更有趣):)

标签: vba excel


【解决方案1】:

我认为最好添加两个新功能:

  1. 添加“信息表”(并将其存储在变量中以备后用)

    Sub addInfoSheet()
        Dim oldSheet
        Set oldSheet = ActiveSheet
        Sheets.Add After:=ActiveSheet
        Set infoSheet = Sheets(ActiveSheet.Index)
        oldSheet.Select
    End Sub
    
  2. 将一行存储到工作表的子程序,例如:

    Sub addRowToInfoSheet(targetSheet As String, targetRange As String, sourceSheet As String, sourceRange As String)
      infoSheet.Cells(rowInInfoSheet, 1) = targetSheet
      infoSheet.Cells(rowInInfoSheet, 2) = targetRange
      infoSheet.Cells(rowInInfoSheet, 3) = sourceSheet
      infoSheet.Cells(rowInInfoSheet, 4) = sourceRange
    
      rowInInfoSheet = rowInInfoSheet + 1
    End Sub
    

如果这有帮助,请告诉我。

【讨论】:

    【解决方案2】:

    编辑: (v0.2) 现在适用于当前工作簿中的所有工作表。 (并充实了其他工作簿。)


    你可以偷偷摸摸地钩住 MsgBox 函数并从它的输出中解析数据。

    只需在您的代码中全局搜索MsgBox 并将其替换为例如MsgBoxInterceptor

    然后你写MsgBoxInterceptor()函数,哦,像下面这样说;)

    像平常一样运行RunMe() sub,瞧!不是输出到屏幕,而是输出到一个新的工作表。

    甚至无需弄清楚您的原始代码在做什么!

    NB 提供的功能仅从活动工作簿中提取先例。

    'v0.2
    Private Function MsgBoxInterceptor _
                    ( _
                               Prompt, _
                      Optional Buttons As VbMsgBoxStyle = vbOKOnly, _
                      Optional Title, _
                      Optional HelpFile, _
                      Optional Context _
                    ) _
            As VBA.VbMsgBoxResult
    
      Const i_TargetCell  As Long = 1
      Const i_TargetSheet As Long = 2
      Const i_SourceCell  As Long = 3
      Const i_SourceSheet As Long = 4
    
      Static slngState As Long
      Static srngDataRow As Range
      Static sstrTargetCell As String
      Static sstrTargetSheet As String
      Static slngClosedBookCount As Long
      Static slngOpenBookCount As Long
      Static slngSameBookCount As Long
      Static slngSameSheetCount As Long
    
      Dim f As WorksheetFunction: Set f = WorksheetFunction
      Dim lngBegin As Long
      Dim lngEnd As Long
      Dim i As Long
    
      Select Case slngState
        Case 0: ' Get counts and target
          Worksheets.Add After:=ActiveSheet
          Set srngDataRow = ActiveSheet.Range("A1:D1")
          srngDataRow.Value = Split("Target Cell:Target Sheet:Source Cell:Source Sheet", ":")
          Set srngDataRow = srngDataRow.Offset(1)
    
          lngBegin = InStr(1, Prompt, "]") + 1
          lngEnd = InStr(lngBegin, Prompt, "'")
          sstrTargetSheet = Mid$(Prompt, lngBegin, lngEnd - lngBegin)
          srngDataRow.Cells(i_TargetSheet) = sstrTargetSheet
    
          lngBegin = InStr(lngEnd, Prompt, "$") + 1
          lngEnd = InStr(lngBegin, Prompt, " ")
          sstrTargetCell = f.Substitute(Mid$(Prompt, lngBegin, lngEnd - lngBegin), "$", "")
          srngDataRow.Cells(i_TargetCell) = sstrTargetCell
    
          lngBegin = InStr(lngEnd, Prompt, ":") + 3
          lngEnd = InStr(lngBegin, Prompt, " ")
          slngClosedBookCount = Val(Mid$(Prompt, lngBegin, lngEnd - lngBegin))
    
          lngBegin = InStr(lngEnd, Prompt, ".") + 2
          lngEnd = InStr(lngBegin, Prompt, " ")
          slngOpenBookCount = Val(Mid$(Prompt, lngBegin, lngEnd - lngBegin))
    
          lngBegin = InStr(lngEnd, Prompt, ".") + 2
          lngEnd = InStr(lngBegin, Prompt, " ")
          slngSameBookCount = Val(Mid$(Prompt, lngBegin, lngEnd - lngBegin))
    
          lngBegin = InStr(lngEnd, Prompt, ".") + 2
          lngEnd = InStr(lngBegin, Prompt, " ")
          slngSameSheetCount = Val(Mid$(Prompt, lngBegin, lngEnd - lngBegin))
    
          slngState = slngState + 1
          MsgBoxInterceptor = vbNo
        Case 1: ' Get same book sources
          lngEnd = InStr(1, Prompt, "[")
          For i = 1 To slngSameBookCount
            srngDataRow.Cells(i_TargetCell) = sstrTargetCell
            srngDataRow.Cells(i_TargetSheet) = sstrTargetSheet
    
            lngBegin = InStr(lngEnd, Prompt, "]") + 1
            lngEnd = InStr(lngBegin, Prompt, "'")
            srngDataRow.Cells(i_SourceSheet) = Mid$(Prompt, lngBegin, lngEnd - lngBegin)
    
            lngBegin = InStr(lngEnd, Prompt, "$") + 1
            lngEnd = InStr(lngBegin, Prompt, Chr$(13))
            srngDataRow.Cells(i_SourceCell) = f.Substitute(Mid$(Prompt, lngBegin, lngEnd - lngBegin), "$", "")
    
            Set srngDataRow = srngDataRow.Offset(1)
          Next i
          For i = 1 To slngSameSheetCount
            srngDataRow.Cells(i_TargetCell) = sstrTargetCell
            srngDataRow.Cells(i_TargetSheet) = sstrTargetSheet
    
            lngBegin = InStr(lngEnd, Prompt, "]") + 1
            lngEnd = InStr(lngBegin, Prompt, "'")
            srngDataRow.Cells(i_SourceSheet) = Mid$(Prompt, lngBegin, lngEnd - lngBegin)
    
            lngBegin = InStr(lngEnd, Prompt, "$") + 1
            lngEnd = InStr(lngBegin, Prompt, Chr$(13))
            If lngEnd = 0 Then lngEnd = Len(Prompt) + 1
            srngDataRow.Cells(i_SourceCell) = f.Substitute(Mid$(Prompt, lngBegin, lngEnd - lngBegin), "$", "")
    
            Set srngDataRow = srngDataRow.Offset(1)
          Next i
          slngState = slngState + 1
          MsgBoxInterceptor = vbOK
        Case 2: ' Just skipping through
          slngState = slngState + 1
          MsgBoxInterceptor = vbYes
        Case 3: 'Get other book sources (STILL TODO)
          lngEnd = InStr(1, Prompt, "")
          For i = 1 To slngClosedBookCount
            srngDataRow.Cells(i_TargetCell) = sstrTargetCell
            srngDataRow.Cells(i_TargetSheet) = sstrTargetSheet
    
    '        lngBegin = InStr(lngEnd, Prompt, "]") + 1
    '        lngEnd = InStr(lngBegin, Prompt, "'")
    '        srngDataRow.Cells(i_SourceSheet) = Mid$(Prompt, lngBegin, lngEnd - lngBegin)
    '
    '        lngBegin = InStr(lngEnd, Prompt, "$") + 1
    '        lngEnd = InStr(lngBegin, Prompt, Chr$(13))
    '        srngDataRow.Cells(i_SourceCell) = f.Substitute(Mid$(Prompt, lngBegin, lngEnd - lngBegin), "$", "")
    
            Set srngDataRow = srngDataRow.Offset(1)
          Next i
          For i = 1 To slngOpenBookCount
            srngDataRow.Cells(i_TargetCell) = sstrTargetCell
            srngDataRow.Cells(i_TargetSheet) = sstrTargetSheet
    
    '        lngBegin = InStr(lngEnd, Prompt, "]") + 1
    '        lngEnd = InStr(lngBegin, Prompt, "'")
    '        srngDataRow.Cells(i_SourceSheet) = Mid$(Prompt, lngBegin, lngEnd - lngBegin)
    '
    '        lngBegin = InStr(lngEnd, Prompt, "$") + 1
    '        lngEnd = InStr(lngBegin, Prompt, Chr$(13))
    '        If lngEnd = 0 Then lngEnd = Len(Prompt) + 1
    '        srngDataRow.Cells(i_SourceCell) = f.Substitute(Mid$(Prompt, lngBegin, lngEnd - lngBegin), "$", "")
    
            Set srngDataRow = srngDataRow.Offset(1)
          Next i
          slngState = slngState + 1
          MsgBoxInterceptor = vbOK
        Case 4: ' Finished -> tidy up
          srngDataRow.EntireColumn.AutoFit
          slngState = 0
          MsgBoxInterceptor = vbCancel
        Case Else
      End Select
    
    End Function
    

    说明:

    这段代码的关键是使用静态变量,使用Static 关键字创建。即使在 VBA 停止运行并重新启动后,它们也会保留它们的值。它们在代码中用于允许构建状态机,该状态机模仿一组用户与消息框交互的序列。

    剩下的只是MsgBox消息的字符串解析。

    【讨论】:

    • 哇!谢谢,那太好了。请问我怎样才能自动化它以继续追踪源单元格的先例?我需要跟踪一直追溯到该单元格中没有公式,只有一个硬编码值。我了解您说这目前不会检查其他工作表和工作簿中的先例,对吗?
    • @Kaitlyn 是的,这是正确的 - 它只检查当前工作表。扩展它以从其他工作表和工作簿中提取也不应该太难。将先例追溯到源头要复杂一些。需要一个额外的子来遍历提取的列表,可能会处理重复项,担心循环引用等。对于已找到但尚未跟踪的每个源,此子程序将简单地调用 RunMe() 子程序。
    【解决方案3】:

    编辑: (v0.2) 现在显示错误消息。

    编辑: (v0.3) 现在对硬编码值进行完整追溯。

    抛开所有的乐趣不谈,如果您真的想一直追溯到硬编码值,最好的方法是编写一个主要的RunMe_Controller 子代码来控制原始代码。与钩子函数(和一些辅助函数)一起,这实际上是利用现有代码的最简单方法。

    MsgBoxInterceptor() 函数足够智能,可以允许错误消息通过,但会静默捕获所有其他 MsgBox() 调用。

    有关更多重要细节,请参阅答案底部的部分。

    安装:

    • 复制/粘贴新的错误修复 RunMe 代码块到模块;
    • 将以下更新代码块的 v0.3 插入到先前代码中指示的位置;
    • MsgBox 执行“当前模块”、“仅查找整个单词”搜索并替换为MsgBoxInterceptor
    • 将以下两个引用添加到 VBA 项目。
      • Microsoft VBScript 正则表达式 5.5
      • Microsoft 脚本运行时

    代码:

    '===============================================================================
    ' Module     : <in any standard module>
    ' Version    : 0.3
    ' Part       : 1 of 1
    ' References : Microsoft VBScript Regular Expressions 5.5
    '            : Microsoft Scripting Runtime
    ' Online     : https://stackoverflow.com/a/46036068/1961728
    '===============================================================================
    Private Const l_No_transformation As String = "No transformation"
    Private Enum i_
        z__NONE = 0
      SourceCell
      SourceSheet
      SourceBook
      TargetCell
      TargetSheet
      TargetBook
      Formula
      Index
      SourceRef
        z__NEXT
        z__FIRST = z__NONE + 1
        z__LAST = z__NEXT - 1
    End Enum
    Private meMsgBoxResult As VBA.VbMsgBoxResult
    'v0.3
    Public Sub RunMe_Controller()
    
      Const s_Headers   As String = "Source Cell::Source Sheet::Source Book::Target Cell::Target Sheet::Target Book::Formula"
      Const s_Separator As String = "::"
      Const l_Circular  As String = "Circular"
    
      Dim ƒ As Excel.WorksheetFunction: Set ƒ = Excel.WorksheetFunction
      Dim dictFullRefTrace As Scripting.Dictionary  '##Early Bound## As Object
      Dim varRootRef As Variant
      Dim varTargetRef As Variant
      Dim varSavedTraceStepKey As Variant
      Dim varNewTraceStep As Variant
      Dim strNewKey As String
    
      Application.ScreenUpdating = False 'Set to true for psychedelic display
      Set dictFullRefTrace = New Dictionary         '##Early Bound## = CreateObject("Scripting.Dictionary")
      varRootRef = ActiveCell.Address(External:=True)
      dictFullRefTrace.Add varRootRef & s_Separator & s_Separator, TheRefTraceStepAsArray(varRootRef)
      dictFullRefTrace.Add s_Separator & s_Separator, TheRefTraceStepAsArray() 'Need two trace steps in dict to start dynamic expansion
      For Each varSavedTraceStepKey In dictFullRefTrace: Do  ' Can't use .Items as it is not dynamically expanded
        If varSavedTraceStepKey = s_Separator & s_Separator Then ' Dummy trace step (dict exhausted) -> clean up fake trace steps
          dictFullRefTrace.Remove varRootRef & s_Separator & s_Separator
          dictFullRefTrace.Remove s_Separator & s_Separator
          Exit Do
        End If
        varTargetRef = dictFullRefTrace(varSavedTraceStepKey)(i_.SourceRef)
        Select Case True
          Case varTargetRef Like "'?:*": ' Closed Wb -> ignore for now (TODO - auto open it)
            Exit Do
          Case varSavedTraceStepKey Like "*#": ' "No transformation" (from its own trace step) -> ignore
            Exit Do
          Case varSavedTraceStepKey Like "*" & l_Circular: ' "Circular" (from its own trace step) -> ignore
            Exit Do
        End Select
        meMsgBoxResult = vbOK
        FindCellPrecedents Evaluate(varTargetRef) ' ~= RunMe() - leverage the existing code to update the global Ref Collections
        Select Case meMsgBoxResult
          Case vbOK:
            For Each varNewTraceStep In TheNewTraceSteps(fromTarget:=varTargetRef).Items
              strNewKey = varNewTraceStep(i_.SourceRef) & s_Separator & varTargetRef & s_Separator
              If dictFullRefTrace.Exists(strNewKey) Then ' Target is a circular ref -> mark it and then add it
                strNewKey = strNewKey & l_Circular
                varNewTraceStep(i_.Formula) = l_Circular
              End If
              If Not dictFullRefTrace.Exists(strNewKey) Then ' Ignore subsequent circular refs for this target
                dictFullRefTrace.Add strNewKey, varNewTraceStep
              End If
            Next varNewTraceStep
          Case vbIgnore: ' No transformation - typically occurs multiple times, so need multiple unique keys
            varNewTraceStep = TheRefTraceStepAsArray(varTargetRef, varTargetRef)
            strNewKey = varTargetRef & s_Separator & varTargetRef & s_Separator & varNewTraceStep(i_.Index)
            dictFullRefTrace.Add strNewKey, varNewTraceStep
          Case vbAbort: ' Error occurred and message was displayed
            Exit Sub
          Case Else
            ' Never
        End Select
        ' Move dummy trace step to end
        dictFullRefTrace.Remove s_Separator & s_Separator
        dictFullRefTrace.Add s_Separator & s_Separator, vbNullString
      Loop While 0: Next varSavedTraceStepKey
      ' Create, fill and format worksheet
      With Evaluate(varRootRef)
        .Worksheet.Parent.Activate
         Worksheets.Add after:=.Worksheet
      End With
      With ActiveSheet.Rows(1).Resize(ColumnSize:=i_.Index - i_.z__FIRST + 1)
        .Value2 = Split(s_Headers, s_Separator)
        .Font.Bold = True
        With .Offset(1).Resize(RowSize:=dictFullRefTrace.Count)
          .Cells.Value = ƒ.Transpose(ƒ.Transpose(dictFullRefTrace.Items)) ' Fill
          .Sort .Columns(i_.Index), xlDescending, Header:=xlNo
        End With
        With .EntireColumn
          .Columns(i_.Formula).Copy
          .Columns(i_.Index).PasteSpecial Paste:=xlPasteValues
          .Columns(i_.Formula).Delete
          .Columns(i_.SourceCell).HorizontalAlignment = xlCenter
          .Columns(i_.TargetCell).HorizontalAlignment = xlCenter
          .AutoFilter i_.Formula, l_Circular
          .Columns(i_.Formula).SpecialCells(xlCellTypeConstants).Font.Color = vbRed
          .AutoFilter i_.Formula, l_No_transformation
          .Columns(i_.Formula).SpecialCells(xlCellTypeConstants).Font.Bold = True
          .AutoFilter
          .Rows(1).Font.ColorIndex = xlAutomatic
          .AutoFit
        End With
        .Cells(1).Select
      End With
      Application.ScreenUpdating = True
    
    End Sub
    
    Private Function TheNewTraceSteps _
                     ( _
                       Optional ByRef fromTarget As Variant _
                     ) _
            As Scripting.Dictionary                        '##Early Bound## As Object
            Dim pvarTargetRef As Variant: pvarTargetRef = fromTarget
    
      Dim mtchMultiCellAddress As VBScript_RegExp_55.Match '##Early Bound## As Object
      Dim strFormula As String
      Dim rngCell As Range
      Dim strKey As String
      Dim astrTraceStep() As String
      Dim varRunMeSourceRef As Variant
      Dim varRefCollection As Variant
    
      Set TheNewTraceSteps = New Dictionary                '##Early Bound## = CreateObject("Scripting.Dictionary")
      strFormula = Evaluate(pvarTargetRef).Formula
      With New VBScript_RegExp_55.RegExp                   '##Early Bound## = CreateObject("VBScript_RegExp_55.RegExp")
        .Global = True
        .Pattern = "(?:(?:[:]| *)(?:\$?[A-Z]{1,3}\d+:\$?[A-Z]{1,3}\d+))+"
        If .test(strFormula) Then
          For Each mtchMultiCellAddress In .Execute(strFormula)
            For Each rngCell In Evaluate(mtchMultiCellAddress.Value)
              strKey = rngCell.Address
              If Not TheNewTraceSteps.Exists(strKey) Then
                astrTraceStep = TheRefTraceStepAsArray(rngCell.Address(External:=True), pvarTargetRef)
                TheNewTraceSteps.Add strKey, astrTraceStep
              End If
            Next rngCell
          Next mtchMultiCellAddress
        End If
      End With
      For Each varRefCollection In Array(SameWbSameSheetRefs, SameWbOtherSheetRefs, OtherWbRefs)
        For Each varRunMeSourceRef In varRefCollection
          strKey = Evaluate(varRunMeSourceRef).Address
          If Not TheNewTraceSteps.Exists(strKey) Then
            astrTraceStep = TheRefTraceStepAsArray(varRunMeSourceRef, pvarTargetRef)
            TheNewTraceSteps.Add strKey, astrTraceStep
          End If
          varRefCollection.Remove 1
        Next varRunMeSourceRef
      Next varRefCollection
    
    End Function
    
    Private Function TheRefTraceStepAsArray _
                     ( _
                       Optional ByRef SourceRef As Variant = vbNullString, _
                       Optional ByRef TargetRef As Variant = vbNullString _
                     ) _
            As String()
    
      Static slngIndex As Long ' Required for reverse ordering of trace output
    
      Dim pvarSourceRef As String: pvarSourceRef = Replace(SourceRef, "''", "'")
      Dim pvarTargetRef As String: pvarTargetRef = Replace(TargetRef, "''", "'")
      Dim astrTraceStepValues() As String: ReDim astrTraceStepValues(1 To i_.z__LAST)
      Dim strFormula As String: strFormula = vbNullString
      Dim astrSourceCellSheetBook() As String
      Dim astrTargetCellSheetBook() As String
    
      astrSourceCellSheetBook = Ref2CellSheetBook(pvarSourceRef)
      astrTargetCellSheetBook = Ref2CellSheetBook(pvarTargetRef)
      If pvarSourceRef = vbNullString _
      Or pvarTargetRef = vbNullString _
      Then
    '    slngIndex = 0 ' Dummy or root ref, i.e., new trace started -> intialize static variable
      Else
        slngIndex = slngIndex + 1
        With Evaluate(TargetRef)
          strFormula = IIf(.HasFormula And pvarSourceRef <> pvarTargetRef, "'" & Mid$(.Formula, 2), l_No_transformation)
        End With
      End If
    
      astrTraceStepValues(i_.SourceCell) = astrSourceCellSheetBook(1)
      astrTraceStepValues(i_.SourceSheet) = astrSourceCellSheetBook(2)
      astrTraceStepValues(i_.SourceBook) = astrSourceCellSheetBook(3)
      astrTraceStepValues(i_.TargetCell) = astrTargetCellSheetBook(1)
      astrTraceStepValues(i_.TargetSheet) = astrTargetCellSheetBook(2)
      astrTraceStepValues(i_.TargetBook) = astrTargetCellSheetBook(3)
      astrTraceStepValues(i_.Formula) = strFormula
      astrTraceStepValues(i_.Index) = slngIndex
      astrTraceStepValues(i_.SourceRef) = SourceRef
      TheRefTraceStepAsArray = astrTraceStepValues
    
    End Function
    
    Private Function Ref2CellSheetBook(ByRef Ref As Variant) As String()
      Dim × As Long: × = 4
      Dim astrCellSheetBook() As String: ReDim astrCellSheetBook(1 To i_.z__LAST)
      If IsMissing(Ref) Then GoTo ExitFunction:
      × = × - 1: astrCellSheetBook(×) = Mid$(Ref, InStr(Ref, "[") + 1, Abs(InStr(Ref, "]") - InStr(Ref, "[") - 1))
      × = × - 1: astrCellSheetBook(×) = Mid$(Ref, InStr(Ref, "]") + 1, Abs(InStr(Ref, "!") - InStr(Ref, "]") - 2))
      × = × - 1: astrCellSheetBook(×) = Mid$(Ref, InStr(Ref, "!") + 1)
      astrCellSheetBook(×) = Replace(astrCellSheetBook(×), "$", "")
    ExitFunction:
      Ref2CellSheetBook = astrCellSheetBook
    End Function
    
    Private Function MsgBoxInterceptor _
                    ( _
                               Prompt, _
                      Optional Buttons As VbMsgBoxStyle = vbOKOnly, _
                      Optional Title, _
                      Optional HelpFile, _
                      Optional Context _
                    ) _
            As VBA.VbMsgBoxResult
    
      If Buttons = vbOKOnly _
      Then
        If Prompt Like "*does not contain a formula*" _
        Or Prompt Like "*contains a formula with no precedents*" _
        Then
          meMsgBoxResult = vbIgnore
        Else
          meMsgBoxResult = vbAbort
          MsgBox Prompt, Buttons, Title, HelpFile, Context
        End If
      End If
      MsgBoxInterceptor = vbCancel
    End Function
    

    Bug 修复原代码:

    Option Explicit
    Public OtherWbRefs As Collection
    Public ClosedWbRefs As Collection
    Public SameWbOtherSheetRefs As Collection
    Public SameWbSameSheetRefs As Collection
    Public CountOfClosedWb As Long
    Dim headerString As String
    
    ' <--  Insert other code here
    
    Sub RunMe()
        Call FindCellPrecedents(ActiveCell)
    End Sub
    
    Sub FindCellPrecedents(homeCell As Range)
        Dim i As Long, j As Long, pointer As Long
        Dim maxReferences As Long
        Dim outStr As String
        Dim userInput As Long
    
        If homeCell.HasFormula Then
            Set OtherWbRefs = New Collection: CountOfClosedWb = 0
            Set SameWbOtherSheetRefs = New Collection
            Set SameWbSameSheetRefs = New Collection
    
            Rem find closed precedents from formula String
            Call FindClosedWbReferences(homeCell)
    
            Rem find Open precedents from navigate arrows
            homeCell.Parent.ClearArrows
            homeCell.ShowPrecedents
            headerString = "in re: the formula in " & homeCell.Address(, , , True)
            maxReferences = Int(Len(homeCell.Formula) / 3) + 1
    On Error GoTo LoopOut:
            For j = 1 To maxReferences
                homeCell.NavigateArrow True, 1, j
                If ActiveCell.Address(, , , True) = homeCell.Address(, , , True) Then
                    Rem closedRef
                    Call CategorizeReference("<ClosedBook>", homeCell)
                Else
                    Call CategorizeReference(ActiveCell, homeCell)
                End If
            Next j
    LoopOut:
    
            On Error GoTo 0
            For j = 2 To maxReferences
                homeCell.NavigateArrow True, j, 1
                If ActiveCell.Address(, , , True) = homeCell.Address(, , , True) Then Exit For
                Call CategorizeReference(ActiveCell, homeCell)
            Next j
            homeCell.Parent.ClearArrows
    
            Rem integrate ClosedWbRefs (from parsing) With OtherWbRefs (from navigation)
            If ClosedWbRefs.Count <> CountOfClosedWb Then '#robinCTS#' Should read (ParsedClosedWbRefs <> CountOfNavigatedClosedWbRefs)
                If ClosedWbRefs.Count = 0 Then
                    MsgBoxInterceptor homeCell.Address(, , , True) & " contains a formula with no precedents."
                    Exit Sub
                Else
                    MsgBoxInterceptor "string-" & ClosedWbRefs.Count & ":nav " & CountOfClosedWb
                    MsgBoxInterceptor "Methods find different # of closed precedents."
                    End
                End If
            End If
    
            pointer = 1
            For j = 1 To OtherWbRefs.Count
                If OtherWbRefs(j) Like "<*" Then
                    OtherWbRefs.Add Item:=ClosedWbRefs(pointer), Key:="closed" & CStr(pointer), after:=j
                    pointer = pointer + 1
                    OtherWbRefs.Remove j
                End If
            Next j
    
            Rem present findings
            outStr = homeCell.Address(, , , True) & " contains a formula with:"
            outStr = outStr & vbCrLf & vbCrLf & CountOfClosedWb & " precedents in closed workbooks."
            outStr = outStr & vbCr & (OtherWbRefs.Count - CountOfClosedWb) & " precedents in other workbooks that are open."
            outStr = outStr & vbCr & SameWbOtherSheetRefs.Count & " precedents on other sheets in the same workbook."
            outStr = outStr & vbCr & SameWbSameSheetRefs.Count & " precedents on the same sheet."
            outStr = outStr & vbCrLf & vbCrLf & "YES - See details about Other Books."
            outStr = outStr & vbCr & "NO - See details about The Active Book."
            Do
                userInput = MsgBoxInterceptor(Prompt:=outStr, Title:=headerString, Buttons:=vbYesNoCancel + vbDefaultButton3)
                Select Case userInput
                Case Is = vbYes
                    MsgBoxInterceptor Prompt:=OtherWbDetail(), Title:=headerString, Buttons:=vbOKOnly
                Case Is = vbNo
                    MsgBoxInterceptor Prompt:=SameWbDetail(), Title:=headerString, Buttons:=vbOKOnly
                End Select
            Loop Until userInput = vbCancel
        Else
            MsgBoxInterceptor homeCell.Address(, , , True) & vbCr & " does not contain a formula."
        End If
    End Sub
    
    Sub CategorizeReference(Reference As Variant, Home As Range)
        Rem assigns reference To the appropriate collection
        If TypeName(Reference) = "String" Then
            Rem String indicates reference To closed Wb
            OtherWbRefs.Add Item:=Reference, Key:=CStr(OtherWbRefs.Count)
            CountOfClosedWb = CountOfClosedWb + 1
        Else
            If Home.Address(, , , True) = Reference.Address(, , , True) Then Exit Sub '#robinCTS#' Never true as same check done in caller
            If Home.Parent.Parent.Name = Reference.Parent.Parent.Name Then
                Rem reference In same Wb
                If Home.Parent.Name = Reference.Parent.Name Then
                    Rem sameWb sameSheet
                    SameWbSameSheetRefs.Add Item:=Reference.Address(, , , True), Key:=CStr(SameWbSameSheetRefs.Count)
                Else
                    Rem sameWb Other sheet
                    SameWbOtherSheetRefs.Add Item:=Reference.Address(, , , True), Key:=CStr(SameWbOtherSheetRefs.Count)
                End If
            Else
                Rem reference To other Open Wb
                OtherWbRefs.Add Item:=Reference.Address(, , , True), Key:=CStr(OtherWbRefs.Count)
            End If
        End If
    End Sub
    
    Sub FindClosedWbReferences(inRange As Range) '#robinCTS#' Should read FindParsedOtherWbReferences
        Rem fills the collection With closed precedents parsed from the formula String
        Dim testString As String, returnStr As String, remnantStr As String
        testString = inRange.Formula
        Set ClosedWbRefs = New Collection
    
        Do
            returnStr = NextClosedWbRefStr(testString, remnantStr)
            ClosedWbRefs.Add Item:=returnStr, Key:=CStr(ClosedWbRefs.Count)
            testString = remnantStr
        Loop Until returnStr = vbNullString '#robinCTS#' Better if add " Or testString = vbNullString"
    
        ClosedWbRefs.Remove ClosedWbRefs.Count '#robinCTS#' then this no longer required
    End Sub
    Function NextClosedWbRefStr(FormulaString As String, Optional ByRef Remnant As String) As String
        Dim workStr As String
        Dim start As Long, interval As Long, del As Long
        For start = 1 To Len(FormulaString)
            For interval = 2 To Len(FormulaString) - start + 1
                workStr = Mid(FormulaString, start, interval)
                If workStr Like Chr(39) & "[![]*[[]*'![$A-Z]*#" Then        '#robinCTS#' Original was "[!!]*'![$A-Z]*#"
                    If workStr Like Chr(39) & "[![]*[[]*'!*[$1-9A-Z]#" Then '#robinCTS#' Original was "[!!]*'!*[$1-9A-Z]#" Not required?
                        interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "#") '#robinCTS#' Not required as always Like "*#" here?
                        interval = interval - 3 * CLng(Mid(FormulaString, start + interval, 1) = ":")
                        interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]")
                        interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]")
                        interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]")
                        interval = interval - CLng(Mid(FormulaString, start + interval, 1) Like "[$1-9A-Z]")
                        NextClosedWbRefStr = Mid(FormulaString, start, interval)
                        Remnant = Mid(FormulaString, start + interval)
                        Exit Function
                    End If
                End If
            Next interval
        Next start
    End Function
    
    Function OtherWbDetail() As String
        Rem display routine
        OtherWbDetail = OtherWbDetail & "There are " & OtherWbRefs.Count & " references to other workbooks. "
        OtherWbDetail = OtherWbDetail & IIf(CBool(CountOfClosedWb), CountOfClosedWb & " are closed.", vbNullString)
        OtherWbDetail = OtherWbDetail & vbCr & "They appear in the formula in this order:" & vbCrLf & vbCrLf
        OtherWbDetail = OtherWbDetail & rrayStr(OtherWbRefs, vbCr)
    End Function
    Function SameWbDetail() As String
        Rem display routine
        SameWbDetail = SameWbDetail & "There are " & SameWbOtherSheetRefs.Count & " ref.s to other sheets in the same book."
        SameWbDetail = SameWbDetail & vbCr & "They appear in this order, including duplications:" & vbCrLf & vbCrLf
        SameWbDetail = SameWbDetail & rrayStr(SameWbOtherSheetRefs, vbCr)
        SameWbDetail = SameWbDetail & vbCrLf & vbCrLf & "There are " & SameWbSameSheetRefs.Count & " precedents on the same sheet."
        SameWbDetail = SameWbDetail & vbCr & "They are (out of order, duplicates not noted):" & vbCrLf & vbCrLf
        SameWbDetail = SameWbDetail & rrayStr(SameWbSameSheetRefs, vbCr)
    End Function
    Function rrayStr(ByVal inputRRay As Variant, Optional Delimiter As String)
        Rem display routine
        Dim xVal As Variant
        If IsEmpty(inputRRay) Then Exit Function
        If Delimiter = vbNullString Then Delimiter = " "
        For Each xVal In inputRRay
            rrayStr = rrayStr & Delimiter & xVal
        Next xVal
        rrayStr = Mid(rrayStr, Len(Delimiter) + 1)
    End Function
    

    问题:

    • 已关闭的工作簿尚未自动打开
    • 引用已关闭工作簿的公式将显示路径名
    • 与您的示例不同,引用打开的工作簿的公式不会显示路径名
    • 仅扩展简单的硬编码多单元格范围(目前)
    • 不展开整列或整行,但只抓取第一个单元格
    • 未找到/扩展 INDEXOFFSET 或任何其他类似的计算范围
    • 扩展范围未排序,任何可能无法很好地排序。

    功能/增强功能:

    • RunMe 代码错误修复现在允许根据要求正确检测已关闭的工作簿引用
    • 现在可以根据要求扩展简单的多单元格范围
    • 适当地考虑了循环引用
    • 硬编码值按要求显示粗体“无转换”
    • 如果从多个目标访问硬编码值会显示多次
    • 工作表名称中的撇号得到妥善处理

    注意:如果你对我的变量命名约定感到好奇,它是基于RVBA

    【讨论】:

    • 老实说,非常感谢您的帮助和建议!我没有完成第一部分,但它确实有效。我按照我需要的方式在新工作表上获得输出(目标和源单元格、工作表和书籍)。我有几个问题,它仍然只给了我先例的第一次迭代,并且它并没有一直回到带有公式的第一个初始输入单元格的硬编码单元格。我是否必须更改 RunMe_Controller 子中的代码,您说过要插入一个循环以使其一直追溯?
    • @Kaitlyn 没错。您必须围绕整个块设置一个循环。第一次使用ActiveCell 提供With。然后你循环遍历当前的源单元格并输入这些单元格。然后输入新的源单元格。冲洗并重复。试一试,看看你是否能做到这一点。我现在很忙,但是如果您遇到困难,可以稍后提供帮助。注意:应该将整个评论线程移至聊天,但您没有足够的代表。我们将在此处继续,然后在完成后进行清理。
    • @Kaitlyn 现在睡觉(这里是午夜之后)。明天应该有时间看一下代码,PS如果你可以发布一些压缩的非机密工作簿供我测试,这将有所帮助。
    • @Egalth 如果您的意思是与使用普通的x 相比,可能不多。就像我可以使用f 而不是ƒ。因为我将× only 用于我的特殊数组赋值构造(而¡ 用于我的通用循环变量),并且这些是only 少于大约6 个字符变量我曾经使用过,我认为它们应该是特别的 ;) 至于输入它们,我可以记住 Alt-Numeric 键盘快捷键,但我将声明存储为函数和子模板.我复制/粘贴...
    • 然后,我使用Ctrl+Space,然后是Down一次或两次,然后是Space,用于ס。对于ƒ,我使用f,Ctrl+Space 然后Up 几次,然后是Space
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-09
    • 1970-01-01
    • 2015-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多