What is going on in there is:
Let A1 contain =B1+C1 , B1 contain 2 & C1 contain 3
The function ValuesInsteadOfPrecedents(Range("A1")) will return the string "=\2+\3".
The first step in doing that is to call the function ArrayOfPrecidents(Range("A1")), which needs to show the precedent arrows to obtain its result.
However, functions called from a worksheet can not change the environment, eg. coloring cells or drawing arrows.
In order for the worksheet UDF SubVal to work, it must call a routine that won't work when called from a worksheet.
This work-around uses Public variables subValCollection and (boolean) CollectionHoldsValues and the Change event.
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Rem some code
CollectionHoldsValue = True
Calculate
CollectionHoldsValue = False
End Sub
Since the change event happens AFTER the UDF has execuited, the sequence of events is
Function SubVal is run
some code is run
Function SubVal is run again
(pseudo-code)
Function SubVal(inputRange As Range) As String
If CollectionHoldsValues Then
SubVal = SubValCollection(inputRange.Address)
Else
SubValCollection.Add item:=inputRange, key:=inputRange.Address
SubVal = "null"
End If
End Function
On the first pass through SubVal, CollectionHoldsValues = False, and the second leg of the If executes, putting inputRange into the collection and returning a dummy value.
Then the Change event runs and <some code> takes inputRange out of the collection and replaces it with ValuesInsteadOfPrecedents(inputRange). Since this is not called from a worksheet, it runs properly.
With CollectionHoldsValue set to True, the Calculate triggers another run of SubVal, this time taking the first leg, which reads the return value from the collection.
The previous version used the memory locations for data validation to impliment a similar logic, but the Collection approach is more robust, adapting to inserted and deleted cells.
The actual code is
in a normal module
Public CollectionHoldsValues As Boolean
Public subValCollection As New Collection
Function SubVal(inputRange As Range) As String
Application.Volatile
On Error GoTo OutOfFtn
If CollectionHoldsValues Then
On Error Resume Next
SubVal = subValCollection(inputRange.Range("A1").Address(, , , True))
On Error GoTo 0
Else
On Error Resume Next
subValCollection.Add Item:=inputRange.Range("a1"), key:=inputRange.Range("a1").Address(, , , True)
On Error GoTo 0
SubVal = "null"
End If
Exit Function
OutOfFtn:
SubVal = vbNullString
On Error GoTo 0
End Function
in the ThisWorkbook code module
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Dim oneCell As Range, i As Long
Dim startPlace As Range, startWindow As Window
Dim newValue As String
If 0 < subValCollection.Count Then
Set startPlace = Selection
Set startWindow = ActiveWindow
Application.EnableEvents = False
For i = 1 To subValCollection.Count
Set oneCell = subValCollection(i)
newValue = ValuesInsteadOfPrecedents(oneCell)
subValCollection.Remove oneCell.Address(, , , True)
If subValCollection.Count = 0 Then
subValCollection.Add Item:=newValue, key:=oneCell.Address(, , , True)
Else
subValCollection.Add Item:=newValue, key:=oneCell.Address(, , , True), before:=1
End If
Next i
CollectionHoldsValues = True
Calculate
startWindow.Activate
Application.Goto reference:=startPlace, Scroll:=False
Application.EnableEvents = True
End If
Set subValCollection = Nothing
CollectionHoldsValues = False
End Sub