Reproduce formula with colored references

mswoods1

Board Regular
Joined
Aug 6, 2010
Messages
60
When you edit a cell that references other cells, you can see the referenced cells in different colors and they are highlighted in that color.

Is there any way to reproduce the colored formula?

Basically I want to build a macro that rebuilds a formula and:
- Colors the cell references a different color
- Colors all other code as black

Example:

=D7*.35+D8
 

Excel Facts

Workdays for a market open Mon, Wed, Friday?
Yes! Use "0101011" for the weekend argument in NETWORKDAYS.INTL or WORKDAY.INTL. The 7 digits start on Monday. 1 means it is a weekend.
Try this out. The code will ask you for source cell (where the actual formula is), and a target cell (where you want the reproduced colored references to be). Caveat: it won't recognize references to other worksheets correctly, nor structured references a la Excel 2007/2010 tables.

I picked an arbitrary cycle of colors that I thought were easy enough to read. Here's an example of what the result looks like, source=C4 and target=G4:

ColorReferences.jpg


Note, I adapted this from a procedure used for a different purpose, so there will be some declarations for variables that aren't actually used, as well as parameters for functions you won't need. I didn't test this application very thoroughly, but it looks like it will work for your problem as I understand it.

Post back after you try it.

Code:
Const debugMode As Boolean = True

Public Sub ColorReferences()
  Dim rngSource As Excel.Range
  Dim rngTarget As Excel.Range
  
  Dim rng As Excel.Range
  
  Dim v As Variant
  Dim iColor() As Long
  Dim i As Long, j As Long, k As Long
  
  Dim ColorCycle As Variant
  ColorCycle = Array(3, 4, 5, 7, 9, 10, 12, 13, 14, 18, 29, 38, 43, 44, 46, 48)
  
  On Error Resume Next
  Set rngSource = Application.InputBox( _
                        Prompt:="Select source cell:", _
                        Type:=8)
  If rngSource Is Nothing Then Exit Sub
  Set rngTarget = Application.InputBox( _
                        Prompt:="Select target cell:", _
                        Type:=8)
  If rngTarget Is Nothing Then Exit Sub
  On Error GoTo 0
  
  v = extractAddress(rngSource, False)
  
  ' set colors
  ReDim iColor(1 To UBound(v, 2))
  
  k = 0
  For i = 1 To UBound(v, 2)
    If i > 1 Then
      For j = 1 To i - 1
        If StrComp(v(1, i), v(1, j)) = 0 Then
          iColor(i) = iColor(j)
          Exit For
        End If
      Next j
    End If
    If iColor(i) = 0 Then
      If k > UBound(ColorCycle) Then
        k = 1
      Else
        k = k + 1
      End If
      iColor(i) = ColorCycle(k - 1)
    End If
  Next i
  
  rngTarget.Value = "'" & rngSource.Formula
  For i = 1 To UBound(v, 2)
    Set rng = rngSource.Parent.Range(v(1, i))
    For j = XlBordersIndex.xlEdgeLeft To XlBordersIndex.xlEdgeRight
      With rng.Borders(j)
        .Weight = XlBorderWeight.xlMedium
        .ColorIndex = iColor(i)
      End With
    Next j
    rngTarget.Characters(v(2, i) + 1, v(3, i)).Font.ColorIndex = iColor(i)
  Next i
  
'  rngTarget.Offset(1, 0).Resize(3, UBound(v, 2)).Value = v
End Sub

Private Function extractAddress(ByVal rng As Excel.Range, _
                      Optional extraParens As Boolean = False) As Variant()
  
  Const iIncr As Long = 10
  Dim iPos As Long, iSize As Long
  Dim retVal() As Variant
                      
  Const maxAddressLength As Integer = 12
  Dim inQuotes As Boolean, isRange As Boolean
  
  Dim parseString As String, tempString As String
  Dim addressLength As Long, parsePosition As Long
  Dim rangeLength As Long
  
  Dim cursor As Long
  Dim returnValue As String
  Dim formulaString As String
  
  If Not rng.HasFormula Then Exit Function
  
  formulaString = VBA.Right$(rng.Formula, VBA.Len(rng.Formula) - 1) & "~"
  
  If debugMode Then
    Debug.Print rng.Address & " has a formula: " & rng.Formula
  End If
  
  If Not hasReference(rng) Then Exit Function
  
  iPos = 0
  iSize = iIncr
  
  ReDim retVal(1 To 3, 1 To iSize)
  
  For cursor = 1 To VBA.Len(formulaString)
    parseString = VBA.Mid$(formulaString, cursor, maxAddressLength)
    
    If (VBA.Left$(parseString, 1) = VBA.Chr(34)) Then
      inQuotes = Not inQuotes
    End If
    
    If Not inQuotes Then
      isRange = False
      addressLength = 0
      
      For parsePosition = 2 To _
        Application.WorksheetFunction.Max(maxAddressLength, VBA.Len(formulaString) - cursor + 1)
        If isAddress(VBA.Left$(parseString, parsePosition)) Then
          addressLength = parsePosition
        ElseIf (addressLength <> 0) Then
          isRange = True
          Exit For
        End If
      Next parsePosition
      
      If isRange Then
        If VBA.Mid$(parseString, addressLength + 1, 1) = ":" Then
          'we have a range on our hands
          parseString = VBA.Mid$(formulaString, cursor, maxAddressLength * 2 + 1)
          If debugMode Then
            Debug.Print "Processing range in " & parseString
          End If
          For parsePosition = 2 To maxAddressLength
            If isAddress(VBA.Mid$(parseString, addressLength + 2, parsePosition)) Then
              rangeLength = addressLength + parsePosition + 2
            End If
          Next parsePosition
          
          addressLength = rangeLength - 1
        End If
        
        iPos = iPos + 1
        If iPos > iSize Then
          iSize = iSize + iIncr
          ReDim Preserve retVal(1 To 3, 1 To iSize)
        End If
        
        retVal(1, iPos) = VBA.Left$(parseString, addressLength)
        retVal(2, iPos) = cursor
        retVal(3, iPos) = addressLength
        cursor = cursor + addressLength - 1
      End If
    End If
  Next cursor
  
  If iPos < iSize Then
    iSize = iPos
    ReDim Preserve retVal(1 To 3, 1 To iSize)
  End If
  extractAddress = retVal
End Function

Private Function hasReference(ByVal rng As Range) _
                                       As Boolean
  If Not rng.HasFormula Then Exit Function
  
  If isAddress(rng.Formula, False) Then
    hasReference = True
  End If
End Function

Private Function isAddress(strTest As String, _
        Optional wholestring As Boolean = True) _
        As Boolean
  Dim re As Object
  Dim strPattern As String
  
  Set re = CreateObject("VBScript.RegExp")
  
  If (wholestring) Then strPattern = "^"
  strPattern = strPattern & _
     "[\$]{0,1}[A-Z]{1,3}[\$]{0,1}[1-9][0-9]{0,6}"
  If (wholestring) Then strPattern = strPattern & "$"
  
  re.Pattern = strPattern
  re.IgnoreCase = True
  
  isAddress = re.test(strTest)
End Function

Post back if you experience errors.
 
Upvote 0
Amazing. I didn't even know about "regular expressions" and looking for patterns. This blows my mind. Thanks for posting.

It works for me, but doesn't handle two things:

1) Named Ranges
2) Ranges on other sheets (I don't necessarily need to highlight these but the macro highlights B3 on the current sheet even if it's reference B3 on a different sheet)

I believe I should be able to find a solution to these though.

For 1, I'm thinking that I could go through each named range in the workbook, search for the named range in the formula, then replace the named range with its cell address.

For 2, I could perform an additional step after find a range, which would be to step back one character, look for "!" and if it finds it, we have a reference to another worksheet. Then finding where the sheet name begins could be challenging. If the sheet name has a space in it, then the sheet name will be enclosed in single quotations, so the beginning should be easy to find. However, if the sheet name doesn't have a space in it, I'm not sure how to find the beginning. I may just search for the first operator, e.g. "+", "-", "*", "/", "&", etc.

I'm also wondering if there's a way to color the cell without actually drawing borders. Something a little less permanent would be optimal. I'll experiement with conditional formatting and perhaps drawing a temp shape.

Thanks again for the post. Much appreciated. I'll report back after I've modified the macro a bit and will post some revised code.
 
Upvote 0
You and I have been going through a similar thought process since I've left the office. This has been a good exercise for me because, (a) this is my pastime similar to solving crosswords or sudokus, and (b) I'm seeing a lot of inefficiencies in my code that I've used for years and reused here.

Rather than eliminating named ranges, you could add another test in the extractAddress() function, where you build a list of defined names (easy to do) and build a RegExp pattern that looks for any of those; then replace the named range in the formulaString (within extractAddress() function) with its corresponding .RefersTo address and then the execution flow would continue as is.

You could potentially do the same thing with worksheet names - build a list, create a RegExp pattern (maybe including the !) and modify the extractAddress() function to look for those, accordingly.

I was also thinking along the same lines as you, that the possible preceding and proceeding characters to a cell/range reference are fairly limited: "( + - * / ^" etc so you can save time that way.

I couldn't think of a less permanent way to color ranges, so if you find one, do post it :)
 
Upvote 0

Forum statistics

Threads
1,224,574
Messages
6,179,628
Members
452,933
Latest member
patv

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top