15
votes

I've got a simple if statement set up in a worksheet where the if condition is VBA user defined function:

Function CellIsFormula(ByRef rng)
    CellIsFormula = rng(1).HasFormula
End Function

This function seems to work fine:

Evaluate 1Evaluate 2

But for some reason that I can't figure out, the cell is evaluating to an error. What's worse, is when evaluating the formula, excel is attributing the error to a calculation step that doesn't produce an error:

Evaluate 4Evaluate 5Evaluate 6

To top it all off, and what really blows my mind, is that if I simply re-enter the formula, or force a full recalculation (Ctrl+Alt+F9) - the formulas evaluate no problem!

Re-Enter FormulaCalculation worked

I've tried making the formula volatile by adding Application.Volatile to the function code, but it didn't change anything. Other methods to refresh the calculation, such as setting calculation to manual and then back to automatic, hidding "recalculate sheet", or just using F9 or Ctrl+F9 do not work, only re-entering the formula or Ctrl+Alt+F9 will cause the function to recalculate properly.

Changing one of the cells referenced in the if statement will not fix the problem, but, changing the cell referenced by the "CellIsFormula" function, does fix the problem. Every time the sheet is re-opened though, the error is back.

3
What if you simply your expression in the cell to something like: =IF(CellIsFormula(B1),"Formula","!Formula"). What happens?Kevin Bowersox
@kmb385 The same happens. When the workbook is opened, #VALUE! appears in every cell. After Ctrl+Alt+F9 or re-entering the formula, the value will show "Formula". If I overwrite the cell next to it with a value, the cell will change to "!Formula".Alain
I also could not reproduce. I hate asking you to try quasi-mystical stuff like this, but have you tried Ctrl-Shft-Alt-F9 (full dependency rebuild and recalc)? And also exporting your VBA code as text, removing the code modules, and reimporting the text back in? My first thought was that you're not actually accessing the value of the range argument, and that that might be confusing Excel about the true dependencies involved. But when I tried out your existing code and formula, they worked for me. Plus why should that cause a #VALUE! error anyway?jtolle
Good thoughts. 1. Ctrl+Shift+Alt+F9 also works to trigger the formula to fix itself, but the problem remains when the workbook is saved, closed, and opened up again. 2. I have in fact also tried removing and re-importing my code. I use an add-on to keep my workbooks slim and that's something it does, but it didn't help. 3. No idea why you can't reproduce it. I'll see if I can host a copy of the problematic workbook somewhere.Alain

3 Answers

15
votes

I discovered the exact problem but I want to up-vote you all for trying to help me figure this out, and give GSerg the credit because, while I wasn't completely out of luck, he was dead on with his suggestion that

Excel does like to make certain properties of a range unavailable during certain stages of calcualtion.

Good find GSerg.

The problem was with Event Handlers. The workbook contains a series of event handlers like Workbook_Open, Worksheet_Change, etc. Every now and then, one of the actions taken by these event handlers will cause some cells in the workbook to recalculate. If excel triggers a recalculation while a macro is running, any cells containing this UDF will result in an error. This is because for some reason, during the VBA triggered recalculation, the .HasFormula property was unavailable, just like @GSerg said: Property Unavailable

Presumably - the next bit is an oversight on Excel's part, but once the macro is done running, if a recalculation has been done, resulting in errors because UDFs didn't run properly, excel will not try to run the UDFs again. The resulting error value will be assumed to be the return value of the call, and will not change unless it thinks the parameter to that UDF has changed. Excel will cache the result of the User Defined Function call until the cell its parameter references is changed.

That is why stepping through 'Evaluate Formula' will show everything working until the very last step, where it doesn't actually evaluate the last step, it just shows the value from the spreadsheet as was last calculated.

Solution

There were actually two possible solutions. The first solution I found was to disable automatic calculation at the beginning the Event Handlers, and re-enable it afterwards. For some reason, even though a macro is running at the time calculation is set back to xlCalculationAutomatic, it will cause the UDFs to be successfully re-evaluated, and the property is available.

The second solution, which I prefer because it prevents this from accidentally ever happening again, is to use a different method to check for a formula:

Function CellIsFormula(ByRef rng As Range) As Boolean
    CellIsFormula = Left(rng(1).Formula, 1) = "="
End Function

The .Formula property is never unavailable. So this problem never occurs.

6
votes

I couldn't reproduce this error, but:

  1. The signature should be:

    Public Function CellIsFormula2(ByVal rng As Range) As Boolean
      CellIsFormula2 = rng.Cells(1).HasFormula
    End Function
    
  2. Excel does like to make certain properties of a range unavailable during certain stages of calcualtion. I've many times seen the .Text property being suddenly unavailable. So if changing the signature does not work, then you are probably out of luck.

1
votes

I think your problems are because the 'HasFormula' Property returns a variant, not a boolean. If the range has mixed formulas and values, HasFormula will return null. Plus your not defining the rng as a Range object, and not specifying output type. I suggest an approach like this. It can be modified to return a boolean pretty easily.

Public Function CellIsFormula(rng As Range) As String

Application.Volatile

    Dim testVal As Variant

    testVal = rng.HasFormula 'HasFormula returns variant type

    'testval is null if cells are mixed formulas and values
    If IsNull(testVal) Then
        testVal = "Mixed"
    End If

    Select Case testVal
        Case True
            CellIsFormula = "All Cells in Range Have formula"
        Case False
            CellIsFormula = "No Cells in Range Have formula"
        Case "Mixed"
            CellIsFormula = "Some Cells in Range Have formula"
        Case Else
            CellIsFormula = "Error"
    End Select
End Function