Non-volatile UDF always recalculating - vba

I am trying to make a non-volatile UDF but it seems not possible. So here is a my very simple test-UDF:
Option Explicit
Dim i As Integer
Sub Main()
i = 0
[A1] = "zyy"
MsgBox i
End Sub
Function Test(rng As Range)
Application.Volatile (False)
Test = rng.Value
i = i + 1
End Function
I got a otherwise empty worksheet that uses this function a couple of times, and every time I call Main() and change any cell on the sheet with the UDFs all of them recalculate. How can I make this (any) UDF no-volatile? The Application.Volatile (False) should have that effect but obviously doesn't work.
Edit: If I change a cell manually it works like intended, it only recalculates when I change a cell via VBA. Is this normal behaviour or can I change it?

I am posting a new answer instead of trying to salvage my previous answer, even though I think they point to the same thing, it will be better to start fresh.
Background:
Previously I had tested your code and the results were exactly as I would expect them to be if you simply omit the False from that statement. I have never seen any reason to explicitly do Application.Volatile (False), because that is equivalent to simply omitting the statement entirely.
If omitted, the function is non-volatile and the UDF evaluates only when a reference changes (i.e., not when other, non-referent cells change)
If included as Application.Volatile (or Application.Volatile
(True), the UDF becomes volatile and any change to the worksheet
will force re-evaluation.
Continuing investigation
You commented that you still observed otherwise. So I made some changes to my code and tested again. All of a sudden weird stuff was happening. No matter what I did with the Application.Volatile function, any change to the worksheet was re-evaluating the UDF.
This didn't make sense, so I started googling and doing a little more testing.
In my tests I created three functions.
The first one is explicitly Volatile:
The second one is explicitly not volatile.
The third omits any statement of volatility.
I put one instance of each formula on a worksheet. Each referenced a different range.
I tested each of these by making changes to the worksheet (manually), and through a named subroutine. I used a Print statement and monitored the Immediate window in the VBE to confirm that in all cases, the functions evaluated (or not) only as expected. The first one always evaluates, while 2 and 3 only evaluate if reference range changed.
Function f_appvol(rng As Range)
Application.Volatile
Debug.Print "f_appvol"
f_appvol = rng.Value
End Function
Function f_appNOTvol(rng As Range)
Application.Volatile (False)
Debug.Print "f_appNOTvol"
f_appNOTvol = rng.Value
End Function
Function f_omit(rng As Range)
Debug.Print "f_omit"
f_omit = rng.Value
End Function
Then it got weird...
I started making changes within these functions and they start to behave wonky.
Specifically I got lucky and noticed that if I changed my non-volatile function to a volatile one, then all functions started acting as if they were volatile -- even the f_omit. I believe this may be the condition you are experiencing.
Somehow, we have managed to "confuse" Excel
I saved the workbook and tried again... back to normal!
Then I changed the argument in the volatile statement, and the strange behavior happened again.
This appears to be a bug
I don't see anything in the documentation that suggests this is normal/expected behavior, and it sure as hell is not desirable behavior from a debugging standpoint. This is the sort of thing that makes you pull out your hair in frustration!
I am using Excel 2010, Win 7 64b.
Resolution
The cause of the error seems to be making change to the volatility of a UDF.
In order to restore expected behavior, it seems necessary to save the workbook. Again, I don't think this is normal but it seems to solve your problem (or at least a very similar problem that I was able to replicate while troubleshooting yours).
On a possibly related note
There appears to be at least one bug related to volatility, as mentioned here. I link to it mainly because this writer echos my own sentiment: there is no reason to do Application.Volatile (False) because that is (or should be) the "normal" state of a UDF.
I have to admit that I had never seen the point of using Application.Volatile False since thats supposed to be what you get if you omit the Application.Volatile statement altogether.

I found the solution, and it is indeed a very simple one but also made this hard to debug:
If you make any change to your VBA code all the UDF get flagged for recalculation!
I modified the degug code of David:
Sub main()
'nothing depends on the Value in [A13]
[A13] = ""
[A13] = "hgdg"
[A13] = ""
i = 46
End Sub
Function f_appvol(rng As Range)
Application.Volatile
Debug.Print "f_appvol"
f_appvol = rng.Value
End Function
Function f_appNOTvol(rng As Range)
Application.Volatile (False)
Debug.Print "f_appNOTvol"
f_appNOTvol = rng.Value
End Function
Function f_omit(rng As Range)
Debug.Print "f_omit"
f_omit = rng.Value
End Function
After entering the code and running it for the first time, only f_appvol is recalculated. If you now change i=46 to i=47 and execute it, all the UDF get recalculated. All subsequent runs after that first run after the change give the expected behaviour.

Related

VBA Function #VALUE and debugging disabled

Every time I try to put some arguments in a Function Excel would return #VALUE. Below is one of the examples. Also, I cannot debug when I put arguments in. What is the possible cause? Thank you.
Function lastrowC(SelectedCell As Range)
sc = SelcetedCell.Column
lastrowC = ActiveSheet.Cells(Rows.Count, sc).End(xlUp).Row
End Function
Your code does not work due to a typo. If you add Option Explicit to the top of your code, then try to calculate, VBA will
show you the problem (you misspelled Selected)
Either way, please consider the below code which will target the correct worksheet rather the active worksheet. Your code, as is, will likely look to the wrong sheet to determine the last row under certain circumstances. You need to look at the sheet where the range was selected, which is not always going to be the same as the active sheet
Paste the below code in a Module to call function from excel
Function lastrowC(Target As Range) As Long
With Target.Worksheet
lastrowC = .Cells(.Rows.Count, Target.Column).End(xlUp).Row
End With
End Function

Add-in function Range.Delete method fails

First, I would like to apologize for my bad language, I hope you'll understand my problem.
I looked after a way to get generic function in Excel and I found the add-in method. So I tried to use it in developping custom functions whitch may help me in my everyday work. I developed a first function which work. So I thought that my add-in programmation and installation was good. But when I try to implement worksheet interractions nothing appened.
My code has to delete rows identified by a special code in a cell of those ones. I get no error message and the code seems to be totally executed. I tried other methods like Cells.delete, Cells.select, worksheet.activate or range.delete but I encounter the same issue.
This is my function's code :
Public Function NotBin1Cleaning(rSCell As Range) As Integer
Dim sht As Worksheet
Dim aLine As New ArrayList
Dim iLine As Integer
Dim iCpt As Integer
Dim iFail As Integer
Dim i As Integer
Dim oRange As Object
Set sht = rSCell.Parent
iLine = sht.Cells.Find("*PID*").Row
For Each rCell In Range(sht.Cells(iLine, 1), sht.Cells(sht.Cells(iLine, 1).End(xlDown).Row, 1))
If sht.Cells(rCell.Row, 2) > 1 Then
iLine = rCell.Row
iCpt = iLine + 1
Do Until sht.Cells(iCpt, 2) = 1
If Not sht.Cells(iCpt, 1) = rCell Then Exit Do
iCpt = iCpt + 1
Loop
If sht.Cells(iCpt, 1) = rCell Then
sht.Range(sht.Cells(iLine, 1), sht.Cells(iCpt - 1, sht.Cells(iCpt, 1).End(xlToRight).Column)).Delete xlUp
iFail = iFail + 1
End If
End If
Next
NotBin1Cleaning = iFail
End Function
it's the line:
sht.Range(sht.Cells(iLine, 1), sht.Cells(iCpt - 1, sht.Cells(iCpt, 1).End(xlToRight).Column)).Delete xlUp
which isn't producing any effect.
I would be really thankful for your help.
This issue is described on the Microsoft support site as part of the intentional design
section below, more detail here (emphasis mine)
A user-defined function called by a formula in a worksheet cell cannot change the environment of Microsoft Excel. This means that such
a function cannot do any of the following:
Insert, delete, or format cells on the spreadsheet.
Change another cell's value.
Move, rename, delete, or add sheets to a workbook.
Change any of the environment options, such as calculation mode or screen views.
Add names to a workbook.
Set properties or execute most methods.
The purpose of user-defined functions is to allow the user to create a
custom function that is not included in the functions that ship with
Microsoft Excel. The functions included in Microsoft Excel also cannot
change the environment. Functions can perform a calculation that
returns either a value or text to the cell that they are entered in.
Any environmental changes should be made through the use of a Visual
Basic subroutine.
Essentially, this means that what you're trying to do won't work in such a concise manner. The limitation, as I understand from further reading, is because Excel runs through cell equation/functions several times to determine dependencies. This would lead to your function being called two or more times. If you could delete rows, there is the potential of accidentally deleting more then twice the numbers of rows intended, due to the excess number of runs.
However, an alternative could be to have the function output a unique string result that shouldn't be found anywhere else in your workbook (maybe something like [#]><).
Then you can have a sub, ran manually, which finds all instances of that unique string, and deletes those rows. (Note: if you included any of the typical wildcard symbols in your string, you will have to precede them with a ~ to find them with the .Find method.) You can even set up the sub/macro with a shortcut key. Caution: if you duplicate a shortcut key Excel already uses, it will run the macro instead of the default. If there will be other users using this workbook, they could experience some unexpected results.
If you decide to go this route, I would recommend using this line:
Public Const dummy_str = "[#]><" ' whatever string you decided on.
in your module with your code. It goes outside any functions or subs, so it'll be global, and then you can refer to the const just as you would any other string variable.
When you write:
sht.Range(sht.Cells(iLine, 1),....
This first parameter should be the row number, but you're refering to a Cell instead. You should change sht.Cells(iLine, 1) for iLine.
BUT
Instead of all this, its easier to use the method Row.Delete:
Rows(iLine).EntireRow.Delete

How do I switch off recalculation when using Solver in VBA?

I tried to use Solver on a working sheet, say sheet A. I also have some cells with "=rand()" on sheet A, or say any formula on the sheet using custom functions with Application.Volatile written inside. I'd like these volatile cells to stop recalculate when I am doing solver, so I used Application.Calculation = xlCalculationManual in my Solver program, but turned out after I ran the Solver, I found that those volatile cells have changed. Where have I done wrong?
There are 2 ways to avoid this:
Do not use solver! If you write an own sub to do the stuff you can use Range.Calculate while the calculation is manual to just calculate this cells (all other cells will be ignored).
Change the formulas and go with iteration options -> formulas -> enable iterative calculation. Now use a helper-cell which holds true/false (or 1/0 or whatever) and the change the formulas you do not want to calculate as follows(helper-cell A1 / formula A2):
=IF(A1,A2,[your old formula])
This way, as long as A1 is true it will output the value from before the calculation.
As far as I know, there is no other way, because solver does a Calculate each time a new cycle is tested.
EDIT:
Having volatile UDF which do not need to be volatile the whole time, you can use a simple trick (again a helper-cell) is needed:
A1: [volatile function like =NOW() or =RAND()]
A2: =MY_UDF(A1)
In Module:
Public Function MY_UDF(a As Variant) As Double
a = a
MY_UDF = Now
End Function
As long as A1 holds something volatile, your UDF will recalculate every time. But if you empty out A1 (or make it at leas non-volatile) there will be no change anymore to the value submitted to your UDF and this way excel/vba assumes that also no change will happen and just skip it for recalculation. This way you also can build up your own RAND-UDF (with different name of course) to just stop ALL volatile functions in your workbook as long as your helper-cell is non-volatile.
As a note: after making A1 (in this example) non-volatile, the first calculation afterwards will still run 1 time like it is volatile. Also changing A1 from one value to another (like 0 to 1) will run one time.
Here is a workaround that might help if you want to have random values in a spreadsheet that can be recalculated at will and whose volatility can be turned on and off.
First, create a 1-cell named range, say "trigger"
Then, put the following code in a standard code module:
Function SemiVolatileRand(x As Variant) As Double
SemiVolatileRand = x - x + Rnd()
End Function
Sub ReSample()
Randomize
Range("trigger").Value = Range("trigger").Value + 0.01
End Sub
Attach the ReSample sub to a button. Replace all occurrences of =RAND() by
=SemiVolatileRand(trigger). They will recalulcate whenever the button is pressed. Also, if you ever want to turn full volatility back on, just put the formula =RAND() in the trigger cell. (Getting full volatility in this last case seemed to require that my code does something with the dummy variable x, hence the somewhat poinless x - x).
Randomize reseeds the random number generator from the system clock. It should be called at least once per session. If you don't, then each time you open an Excel workbook which uses VBA rnd, you will get the same sequence of random values. You can verify this by making a blank workbook and in the ThisWorkbook code module put:
Private Sub Workbook_Open()
MsgBox Rnd()
End Sub
The message block will display the same value each time you open the workbook. On the other hand if you put the line Randomize before the MsgBox then you will get different values each time you open it.
Note that the workbook open event is a natural place to put the statement Randomize if you are planning to use rnd.
The reason I didn't put Randomize in the function itself was both to save CPU cycles and also because of a nagging concern that a certain percentage of the time you will be reseeding the random number generator with exactly the same system time. That might be impossible with modern architectures running recent versions of Excel, but e.g. does sometimes happen if you had Randomize Timer (which you sometimes encounter when reading other's code) since the timer function has 1 millisecond resolution irrespective of the system clock.
The code I have does have the drawback that if you bypass the button and just change the trigger cell then you could miss the reseeding. If this is a concern, 1 possibility would be like this:
Public Initialized as Boolean
Function SemiVolatileRand(x As Variant) As Double
If Not Initialized Then
Randomize
Initialized = True
End If
SemiVolatileRand = x - x + Rnd()
End Function
This will prevent the function from running if rnd isn't properly seeded.
Excel itself takes care of seeding automatically with the worksheet function Rand(), so it is strictly a VBA complication.

Unused UDF being called when doing CalculateFullRebuild

I have some User Defined Functions in an Excel book. I used them for a while but, after a while, I deleted the calls to these functions from the cells because I found a better way to accomplish the same task (I didn't delete the function definition itself in the VBA editor). So, these functions are no longer being called neither in the book nor from any VBA code, I checked it using a search to be 100% sure.
Now I'm doing some review on my code and I noticed something strange: in a Sub procedure in the same workbook (which has nothing to do with these functions) I call Application.CalculateFullRebuild. When this happens those UDF get called, I can see it by setting a break point inside the UDF.
I'd like to know why is it happening and what can be done to avoid it, as it is slowing that Sub unnecessarily.
Thanks!
Application.CalculateFullRebuild MSDN reference has this to say:
The CalculateFullRebuild method is similar to re-entering all formulas. ... [When run] a full calculation of the data in all open workbooks is performed and the dependencies are rebuilt.
Further MSDN reference states:
Causes Excel to rebuild the dependency tree and the calculation chain
This means that any UDFs in the module code or sheet code will be recalculated because Excel is rebuilding and testing functions for dependency and use in the calculation chain.
If you are looking for a way to simply manually calculate the existing formulas in the sheet via your Sub, you can use 'Application.Calculate' (MSDN):
Application.Calculate 'for all open Sheets
Sheets("Name of Sheet").Calculate 'Specific Sheet
Sheets("Name of Sheet").Range("Name of Range").Calculate 'Specific Range
The system is working as it should. Consider:
Function qwerty() As String
qwerty = "qwerty"
MsgBox "XX"
End Function
It is non-Volatile and has no arguments. It will be calculated at the time it is entered in a worksheet cell. Application.Calculate may cause it to be calculated once, however:
Sub ytrewq()
Application.CalculateFullRebuild
End Sub
will cause the UDF to be re-calculated each time ytrewq is run.
To the moment my approach has been commenting all the code inside the UDF with two objectives: increasing speed on one side and checking if any side effect happened on the other side. To the moment, I have not observed any side effect, so more to the point that they are not being used anywhere.
Right now the application I'm developing is working quite well, but I'll try the solutions you're proposing just out of curiosity. By bets are on either it's being used somewhere hidden and forgoten or simply that I have some rubbish inide the workbook structure that is not getting cleaned.
Thanks!
Update
Tried again the next day and those UDF are no longer being called. Thus, I'll have to assume that something odd was going on with Excel that went away when I restarted it.
Anyway, thanks a lot for the Application.Caller thing, which I didn't know about.

updating VBA Custom Functions

I have the following function(which returns the last row number of any selected column)
Function LastrowCC(SelectedRange As Range)
Dim SelectedColumnNum As Long
SelectedColumnNum = SelectedRange.Column
LastrowCC = ActiveSheet.Cells(Rows.count, SelectedColumnNum).End(xlUp).Row
End Function
the problem is that when the last row of the selected column is deleted the function does not update automatically
can we make VBA custom functions update automatically when inputs change?
At risk of repeating the helpful comments and answers already posted, let me point out that there are several issues involved in your question, summed up nicely in the MSDN article "Excel Recalculation."
A more specific question, including the Excel version and the way calculation is handled on your worksheet, may help narrow things down a bit.
Let me sum up some things you may want to check out, all listed in the article above:
There are several ways a recalculation is triggered, including functions. Studying the way this happens may shed some insight. According to the article, "The calculation of worksheets in Excel can be viewed as a three-stage process:
Construction of a dependency tree,
Construction of a calculation chain,
Recalculation of cells."
Volatile functions are an option, but because of resource consumption should be used sparingly and wisely.
You may also explore Range.Dirty and Range.Calculate methods, which starting in Excel 2002 (that's farther back in the past than some of us realize!) allows "forced recalculation," to again quote the article above.
These are a few options and things to consider.
Make it Volatile :
Function LastrowCC(SelectedRange As Range) As Long
Application.Volatile
Dim SelectedColumnNum As Long
SelectedColumnNum = SelectedRange.Column
LastrowCC = ActiveSheet.Cells(Rows.Count, SelectedColumnNum).End(xlUp).Row
End Function
The key to making a function work within Excel's calculation tree and not be volatile is to include everything you need in the arguments to the function. That is, don't reference any ranges that are not included in the arguments to the function.
In your example, you can send it any single cell and it will return the last row of that same column. But you're looking outside of that single cell so Excel doesn't know that the whole column should be in the calculation tree.
In this rewrite, the function only accepts single, whole column arguments. If you send it less than a whole column, it returns an error. If you send it more than one column, it returns an error. But since the whole column is in the argument, any changes in the column trigger a recalc.
Public Function LastRowCc(ByVal SelectedRange As Range)
If SelectedRange.Address = SelectedRange.EntireColumn.Address And SelectedRange.Columns.Count = 1 Then
LastRowCc = SelectedRange.Find("*", SelectedRange.Cells(1), , , , xlPrevious).Row
Else
LastRowCc = CVErr(xlErrValue)
End If
End Function