How to prevent VBA function from re-executing inside the code - vba

I'm calling VBA function from an Excel worksheet. When I change a cell inside the code of the VBA function, Excel tries to re-execute that function again (and again in the second iteration and ...)
Example: if you have the code:
Function test() As Variant
Range("A1") = 1
test = "test"
End Function
When you use "=test()" anywhere, it will return #VALUE!. A debug will show that when you update A1, it will try to re-execute test().
Can you prevent Excel from doing this? E.g. saying 'don't update any of my numbers until I'm done with this function'? I've tried the Application.Calculation flag, or doing some external concurrency checks, but that doesn't seem to work ...

Try:
Function test() as Variant
test="test"
End Function

As Gary's student says in his comment: UDF's should only change worksheet entries of the cells that call the function, and this value should be returned as a result of the function, not by changing a cell value of an explicit address.
In other words, this does not work:
Function test() As Variant
Range("A1") = 1
test = "test"
End Function
Instead call test() from cell A1 and do this:
Function test() As Variant
test = 1
End Function
If the desired behaviour is to edit multiple cells (the return value of the function AND another cell), this should be implemented through a sub rather than a function.

To answer your specific question of how to stop the Function re-executing when you change A1 or your function, and stop it returning #Value: 1. Use the Excel user interface (Formulas--> calculation options) to change calculation mode to manual 2. add some error trapping to your function like this.
Function test() As Variant
On Error Resume Next
Range("A1") = 1
test = "test"
End Function
as has already been said the function will not change the value of A1

Related

Excel VBA own function in worksheet which changes value on a sheet

I'm trying to use my own VBA function in an Excel sheet with a return value and the same function manipulates a cell on the same or on an other sheet, but the result is #VALUE! A minimal working example (Office Prof Plus 2010, 32-bit):
Function abc() As Integer
Dim i%
i = 0
Sheet1.Cells(1, 2).Value = 2
abc = i
End Function
When I execute Debug.Print abc it obviously writes a 2 to the cell B2 and the printed result is 0. What I want to do now is =abc() in cell A1 on Sheet1, but I only get #VALUE!. Btw, it doesn't work either if Application.EnableEvents and Application.Calculation is disabled resp. set to manual.
Two questions: Why? And any idea how to resolve? Thx.
It's well known that you can't update the worksheet with a UDF (other than the cell with the UDF in). However, there is a pretty awful workaround which checks every time you make a change on your worksheet and places a 2 in B1 for you if your function happens to be =abc()
Have your dummy function in in a standard module
Public Function abc() As Integer
abc = 0
End Function
Then in the Sheet1 module place the following code
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Cells.Count > 1 Then Exit Sub
If Target.Formula = "=abc()" Then
Me.Cells(1, 2).Value = 2
End If
End Sub
This type of User Defined Function (called from within a cell) can't modify cell values other than to return a value to the cell containing the UDF.

Why does IfError return error?

I'm trying to create a wrapper for the INDEX/MATCH lookup method (which is faster/better than the regular VLOOKUP method) by creating a custom function and loading it afterwards as an add-in.
So far so good, but my desired behavior is that, when the value to be looked up is not found, it should return blank ("") and not #VALUE!. So I am trying to use the IfError WorksheetFunction in order to achieve this the same way I would on an Excel sheet:
Function FastLookup(parLookupKey As Variant, parLookupRange As Range, parReturnRange As Range) As Variant
FastLookup = Application.WorksheetFunction.IfError(Application.WorksheetFunction.Index(parReturnRange, Application.WorksheetFunction.Match(parLookupKey, parLookupRange, 0)), "")
End Function
Again, this still returns #VALUE! if a value is not found on the parLookupRange range.
Needless to say, if I use =IFERROR(FastLookup(H6,E3:E6,F3:F6),"") directly on the Excel sheet, it works.
Any ideas on how to make the VBA version of IfError work?
Because the error never makes it past the MATCH function in vba. As soon as that throws an error the code will stop.
Use this instead:
Function FastLookup(parLookupKey As Variant, parLookupRange As Range, parReturnRange As Range) As Variant
Dim t As Long
On Error Resume Next
t = Application.WorksheetFunction.Match(parLookupKey, parLookupRange, 0)
On Error GoTo 0
If t > 0 Then
FastLookup = parReturnRange(t)
Else
FastLookup = ""
End If
End Function
We now capture the error and ignore it. Then we test for it and return the correct value.

User Defined function not recognized

I've written a function in VBA that modifies the current VLOOKUP to return nothing instead of #N/A when a value is not found. The function works fine when I have it saved in a module in my active workbook but when I move it to my .xlam project the output is always #NAME?. I should also note that autocomplete does find the function when I begin typing it in a cell.
Here is the code I have so far:
Function NAVLOOKUP(val As Variant, rng As Range, ofst As Integer) As Variant
Dim temp As Variant
On Error Resume Next
temp = Application.WorksheetFunction.VLookup(val, rng, ofst, False)
If IsEmpty(temp) Then
NAVLOOKUP = ""
Else
NAVLOOKUP = temp
End If
End Function
It's pretty straightforward so I'm not sure what the problem is. I also made a dummy function in the same .xlam project that just adds an "S" to any value and it has been working with no problem.
Function ADDS(val As Variant) As String
ADDS = val & "S"
End Function
Since the second function works I am at a loss here. Any help would be appreciated.

Excel: Passing the current cell as argument when calling vba function from worksheet

I'm trying to make a VBA function that accepts a cell as argument and works from there using a variety of Range.Offset. This function will be called in the worksheet cells. For testing I'm using this simple script:
Public Function testPublic(targetCell As Range) As Boolean
targetCell.Offset(0, 3).Value2 = "Test is successful!"
testPublic = True
End Function
To see if I can get the cell reference to work, I pass simple references such as C5, but I only get #VALUE! error. Not sure what's wrong with this.
Tried to change Range as Variant, still doesn't work
You cannot use a function called as a UDF from a worksheet to update another cell in the worksheet: the function can only return a value to the cell containing the function (an array formula can return multiple values, but again only to the cells where the formula was entered).
For more information see: https://support.microsoft.com/en-us/topic/description-of-limitations-of-custom-functions-in-excel-f2f0ce5d-8ea5-6ce7-fddc-79d36192b7a1
Try below calling code
If testPublic(targetCell:=Range1) Then
MsgBox "success"
End If
The function code is as below. Change for ByRef and ByVal as per your logic.
Public Function testPublic(ByRef targetCell As Range) As Boolean
targetCell.Offset(0, 3).Value2 = "Test is successful!"
testPublic = True
End Function

Count visible blank cells using VBA?

When I enter the following function as a UDF in a cell:
Function VisibleBlankCells(r As Range) As Long
On Error Resume Next
VisibleBlankCells = Intersect(r.SpecialCells(xlCellTypeVisible), r.SpecialCells(xlCellTypeBlanks)).Count
On Error GoTo 0
End Function
r.SpecialCells(xlCellTypeBlanks) evaluates ALL cells in r as empty regardless of whether they contain text or not. What might be the cause of this and an alternative solution?
Get rid of the On Error Resume Next for a start - you should always assume that your code will fail and account for it accordingly, simply ignoring errors will just complicate matters.
Secondly ,there is no need to use Intersect - just identify the visible cells directly, and then use a further SpecialCells() method to identify the blank child cells.
Function VisibleBlankCells(r As Range) As Long
VisibleBlankCells = r.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeBlanks).Count
End Function
tested with this:
Sub test_code()
Dim r As Range: Set r = Selection
Debug.Print CountBlanks(r)
End Sub
Function CountBlanks(r As Range) As Long
CountBlanks = r.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeBlanks).Count
End Function
This kind of filter mechanism won't work in an UDF (see this for information on that). I suggest a looping inside your UDF:
Public Function VisibleBlankCells(rng As Range) As Long
Dim i As Integer
Dim cell As Range
i = 0
For Each cell In rng
If cell.Rows.Hidden = False And _
cell.Columns.Hidden = False And _
cell.Value = "" Then
i = i + 1
End If
Next
VisibleBlankCells = i
End Function
However, there may be some problems regarding the updating and functionality:
The value of the UDF only updates after editing the referenced range or calling other UDFs. So if you hide a column or row in that range, it won't have an instant effect
In the (working) execution of your code in a Sub, the visible cells (also) refer to yet unused cells in your worksheet to be "not visible". In my solution however, all cells that are not contained in a hidden row/column are considered visible.