Abiel
Abiel

Reputation: 5475

Stop VBA Evaluate from calling target function twice

I am having trouble getting VBA's Evaluate() function to only execute once; it seems to always run twice. For instance, consider the trivial example below. If we run the RunEval() subroutine, it will call the EvalTest() function twice. This can be seen by the two different random numbers that get printed in the immediate window. The behavior would be the same if we were calling another subroutine with Evaluate instead of a function. Can someone explain how I can get Evaluate to execute the target function once instead of twice? Thank you.

Sub RunEval()
    Evaluate "EvalTest()"
End Sub

Public Function EvalTest()
    Debug.Print Rnd()
End Function

Upvotes: 8

Views: 6338

Answers (6)

azizi
azizi

Reputation: 11

I face the same problem, after investigation i found the function called twice because i have drop down list and the value used in a user defined function.

working around by the code bellow, put the code in ThisWorkbook

Private Sub Workbook_Open()
'set the calculation to manual to stop calculation when dropdownlist updeated and again calculate for the UDF
     Application.Calculation = xlCalculationManual
End Sub

Private Sub Workbook_SheetChange(ByVal Sh As Object, _
        ByVal Source As Range)
'calculate only when the sheet changed  
     Calculate
End Sub

Upvotes: 1

stenci
stenci

Reputation: 8501

It looks like Application.Evaluate evaluates always twice, while ActiveSheet.Evaluate evaluates once if it is an expression.

When the object is not specified Evaluate is equivalent to Application.Evaluate.

Typing [expression] is equivalent to Application.Evaluate("expression").

So the solution is to add ActiveSheet and to make that an expression by adding zero:

ActiveSheet.Evaluate("EvalTest+0")

Upvotes: 0

Jonathan Mori
Jonathan Mori

Reputation: 1

After seeing there is no proper way to work around this problem, I solved it by the following:

Dim RunEval as boolean

Sub RunEval()
    RunEval = True
    Evaluate "EvalTest()"
End Sub

Public Function EvalTest()
    if RunEval = true then
     Debug.Print Rnd()
     RunEval = False
    end if
End Function

problem solved everyone.

Upvotes: -1

Charles Williams
Charles Williams

Reputation: 23550

This bug only seems to happen with UDFs, not with built-in functions. You can bypass it by adding an expression:

Sub RunEval()
    ActiveSheet.Evaluate "0+EvalTest()"
End Sub

But there are also a number of other limitations with Evaluate, documented here http://www.decisionmodels.com/calcsecretsh.htm

Upvotes: 9

jtolle
jtolle

Reputation: 7123

I don't know of a way to stop it, but you can at least recognize when it is happening most of the time. That could be useful if your computation is time consuming or has side effects that you don't want to have happen twice and you want to short circuit it.

(EDIT: Charles Williams actually has an answer to your specific quesion. My answer could still be useful when you don't know what data type you might be getting back, or when you expect to get something like an array or a range.)

If you use the Application.Caller property within a routine called as a result of a call to Application.Evaluate, you'll see that one of the calls appears to come from the upper left cell of of the actual range the Evaluate call is made from, and one from cell $A$1 of the sheet that range is on. If you call Application.Evaluate from the immediate window, like you would call your example Sub, one call appears to come from the upper left cell of the currently selected range and one from cell $A$1 of the current worksheet. I'm pretty sure it's the first call that's the $A$1 in both cases. (I'd test that if it matters.)

However, only one value will ever be returned from Application.Evaluate. I'm pretty sure it's the one from the second eval. (I'd test that too.)

Obviously, this won't work with calls made from the actual cell $A$1.

(As for me, I would love to know why the double evaluation happens. I would also love to know why the evaluator is exposed at all. Anyone?)

EDIT: I asked on StackOverflow here: Why is Excel's 'Evaluate' method a general expression evaluator?

I hope this helps, although it doesn't directly answer your question.

Upvotes: 2

Kevin Brock
Kevin Brock

Reputation: 8944

I did a quick search and found that others have reported similar behavior and other odd bugs with Application.Evaluate (see KB823604 and this). This is probably not high on Microsoft's list to fix since it has been seen at least since Excel 2002. That knowledge base article gives a workaround that may work in your case too - put the expression to evaluate in a worksheet and then get the value from that, like this:

Sub RunEval()
    Dim d As Double

    Range("A1").Formula = "=EvalTest()"
    d = Range("A1").Value
    Range("A1").Clear
    Debug.Print d
End Sub

Public Function EvalTest() As Double
    Dim d As Double
    d = Rnd()
    Debug.Print d
    EvalTest = d + 1
End Function

I modified your example to also return the random value from the function. This prints the value a second time but with the one added so the second print comes from the first subroutine. You could write a support routine to do this for any expression.

Upvotes: 1

Related Questions