VBA Excel UDF retaining original value - vba

I have a UDF non-volatile function in VBA, which requires a global variable (callback to VSTO) initialized.
This function resides in xla or xlam (same behaviour)
Is there a way to cancel calculation of the formula if the variable not yet available?
In Excel Automatic recalculation mode (I don't want to change that) Excel recalculates cells with my function every time I open a workbook, replacing value saved in a cell with error: "#ARG!".
I know how to return empty cell if my variable is not initialized, but is it possible to return original value? I checked with a debugger that it has been saved in the workbook. Trying to return Application.Caller.Value2 causes cyclic reference.
Also, the function isn't called (expected behaviour) if the VBA code is embedded in the workbook (xlsm), or if the workbook is in xls format. However, converting a workbook to xlsx causes the cells to be recalculated after workbook is opened as described.

You can do this in XLM or a C XLL by flagging the function as a macro function. In VBA the only easy way I know is an ugly hack : return Application.Caller.Text if the variable is not initialised.
This suffers from the major flaw that it gives you the formatted value of the cell rather than the actual value.
Otherwise you have to build a way of persisting the value of the cell in the closed-but-saved workbook: possible schemes include using Defined Names, Cell Comments, the registry, external files etc, but I don't think there is a clean VBA solution.

I eventually went with the xlsm approach.

Related

Excel VBA: Naming a range within a user defined function

I am trying to create a user defined function to create a named range and assign a value to the cell. The below code is giving me a #Value error
My code:
Public Function NameARange(CValue As String, NameR As String) as String
Dim ReferAdd As String
ReferAdd = "='" & ActiveSheet.Name & "'!" & ActiveCell.Address
ActiveWorkbook.Names.Add Name:=NameR, RefersTo:=ReferAdd
NameARange = CValue
End Function
Any help to fix this code will be much appreciated. Thanks
Are you sure it's not a #NAME? error that you're getting?
Either way, there are a couple issues with your formula. The short explanation is it doesn't make logical sense.
Let's say you stick that formula in cell A1 of Sheet1...
You're trying to create a named range with a Worksheet Function. Worksheet functions recalculate (re-execute) every time something changes on the worksheet. Excel would try to recreate a new named range by the existing name, over and over and over.
Imagine if you had to sweep the floor anytime sometimes changed in your house... but sweeping the floor changes your house. You'd be stuck in an infinite loop.
You're also want the function to assign a formula to the cell that the function is sitting in. What if you could clone yourself, but the only place that clone could ever stand is exactly where you are standing. Wouldn't work out.
And, finally, you want to finish by returning a value to the same cell that has the function (and the infinite copies of itself)... but not just any value: the value that you called the function with in the first place.
It's like a Catch-22 of a Quagmire of a Paradox.
There is no solution for what you're trying to do except, "don't". Excel won't let you anyhow, which is good because otherwise the universe just might implode.
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.
During calculation, Excel examines the precedents of the cell that contains a user-defined function. If not all precedents have been calculated so far during the calculation process, Excel eventually calls the user-defined function and passes a Null or Empty cell to the function. Excel then makes sure that enough calculation passes occur for all precedents to be calculated. During the final calculation pass, the user-defined function is passed the current values of the cells. This can cause the user-defined function to be called more frequently than expected, and with unexpected arguments. Therefore, the user-defined function may return unexpected values.
For correct calculation, all ranges that are used in the calculation should be passed to the function as arguments. If you do not pass the calculation ranges as arguments, instead of referring to the ranges within the VBA code of the function, Excel cannot account for them within the calculation engine. Therefore, Excel may not adequately calculate the workbook to make sure that all precedents are calculated before calculating the user-defined function.
(Source: Microsoft : Description of limitations of custom functions in Excel)

Excel 2003 - Display bug with conditional formatting

This is actually an improvement for this previous topic.
Context:
Excel 2003, Windows 7 Professional SP1.
Working on workbook A, which contains a cell with conditional formatting calling a personal function. This function refers to a defined name of workbook A.
Workbook B is opened or edited (not only activated). It can be any workbook.
We activate back workbook A.
Problem:
The conditional formatting causes various bugs if workbook B's window overlaps workbook A's. In other words, when at some point workbook A gets behind workbook B.
Depending on the window state (whether Excel is in full screen or not, whether ActiveWindow.WindowState = xlNormal or xlMaximized, whether the window is narrow or wide ...), the bugs can be :
Mainly: cells calling personal (volatile) functions that refer to defined names don't calculate when switching back to workbook A. This is a huge problem, as I need to force calculation with Ctrl+Alt+F9.
Display bugs often occur, notably when workbooks A and B are floating next to each other, overlapping each other. Cell content vanish, ghosts cells of the ones with conditional formatting appear, the screen freezes, window border trails (W98 style) are displayed when trying to move around one of the workbooks ...
In Workbook_Activateof workbook A, breakpoints don't stop the code (though debug.print and MsgBox work) and you can't use Application or ActiveWindow methods. Application.CalculateFull sometimes causes all the cells' content to disappear until they are selected (using Ctrl+A, for example).
How to solve or avoid this problem?
I will answer to myself below, if it can be of some interest for anyone!
Solution 1:
Just don't use personal functions referring to defined cells names in conditional formatting formulas.
This solution solves every problem, but it's not acceptable when you have to have complex conditional formatting.
Solution 2:
Bug sample
Bug sample (solved)
Create a named cell anywhere in workbook A, for example call it "CondFormat". Set its value to "TRUE".
This will be the boolean driving the call of personal functions in your conditional formatting formulas.
Edit every conditional formatting this way:
=MyFunctionWithCellNames($A1)
becomes:
=IF(CondFormat,MyFunctionWithCellNames($A1))
Automatically toggle the boolean when you activate or deactivate the workbook:
Private Sub Workbook_Activate()
[CondFormat] = True
End Sub
Private Sub Workbook_Deactivate()
ThisWorkbook.Names("CondFormat").RefersToRange = False
End Sub
Note 1: sometimes, [CondFormat] isn't toggled when leaving the workbook. The ThisWorkbook.Names syntax makes it work all the time.
Note 2: we have to use one of the cells for the boolean CondFormat, because only macros can access to VBA global variables.
Note 3: I spent a lot of time figuring this out, I hope it will be useful for others. I think that most problems with conditional formatting can only be solved this way in Excel 2003, as Sheet.EnableFormatConditionsCalculation method only appeared in Excel 2007.

Excel Reference External Sheet

An excel question for you gurus. I've tried searching high and low and haven't come up with an effective solution.
I'm trying to create a formula that will lookup a value in an external sheet. I'm using the SUMPRODUCT formula and it works perfectly. Formula is below:
=SUMPRODUCT(--('File\Path\[file.xlsx]SheetName!$D$1:$D$1000=$B3), --('File\Path\[file.xlsx]SheetName'!$O$1:$O$1000=$A3), 'File\Path\[file.xlsx]SheetName'!$Q$1:$Q$1000)
The issue I'm running into, however, is that the source file is updated every day. Although the workbook name stays the same, the sheet name changes. A random string gets assigned to the source sheet name each time it is updated. As such SheetName becomes SheetName ase341.
Is there a way to have the formula read the external sheet number instead of the name? I want the formula to update regardless of the sheet name. If there's no way to read the sheet position is there a way to change the sheet name via a formula in an external workbook?
Usage Example
I have a workbook (analysis) and it pulls data from another workbook (source). Source is updated every day with new data. The data in Source is updated by downloading a report from the internet and saving over the old source file. As such, the file name stays the same but whatever is inside the file is always different (including the sheet name). There is always only ever one sheet in the Source with the same number of columns, always in the same position.
There is a really neat way to refer to a block of cells in an external workbook in which the sheetname or even the block address may vary. Say we have:
=SUM('C:\Users\James\Desktop\[Book1.xlsx]Sheet1'!$B$2:$B$9)
however the sheetname may vary. First assign a Defined Name to the block in Book1 (say XXX)
Then we can use:
=SUM('C:\Users\James\Desktop\Book1.xlsx'!XXX)
It does not matter if the sheetname changes, the Defined Name will change with it!
Your issue would be most efficiently solved with VBA, but if you're just getting started this might not be the best route.
You can get the sheetname or filename with just a formula, though:
http://www.ozgrid.com/VBA/return-sheet-name.htm

Excel VBA: Initialise public module-level variables when file is opened?

I have a whole bunch of dates in three columns which are used by several macros. Currently each macro that uses these dates has to:
Declare the range
Have a for-loop through the range to get the date-cell and
Read the value of the cell into a variable of type date
This also includes checking the cell isn't empty, checking the date is valid.
Instead of this, I would like to have a macro that reads these dates into (VBA) arrays, which persist as long as the file is opened. I would also like to have this macro run when the file is opened, so that the dates are guaranteed to be initialised when any macro that uses them is run.
Any way to do these things?
Put your code in the Workbook_Open() event.
Alt-F11 to bring up the VBA Editor
Double-click the "ThisWorkbook" in the Project Explorer.
You'll see two drop-downs at the top of the code window. Pick "Workbook" from the first one and "Open" from the second to have a stub for your code created.

Apache POI and SUMPRODUCT formula evaluation

I have a template XLS file that I load with Apache POI and write loads of data in it, then save it as another file.
I have formulas in my XLS file like this:
=SUMPRODUCT((DS!B:B="IN_THIS_ONLY")*(DS!D:D="New trade"))
also tried
=SUMPRODUCT(0+(DS!B:B="IN_THIS_ONLY"),0+(DS!D:D="New trade"))
these evaluate correctly if I press Enter on the cell in Excel.
However, simply calling
HSSFFormulaEvaluator.evaluateAllFormulaCells(workbook);
does not seem to evaluate them, neither does pressing on the "Calculate now" button in Excel - so I guess this is a special formula or function.
The other, more conventional COUNTIFs and SUMIFs work fine, however these do not allow multiple conditions to be specified.
POI does not support array formulas.
Is there any way to make these work. I'm using POI version 3.7.
One can press CTRL-ALT-F9 to manually re-evaluate all formulas forcefully in Excel.
And here is the trick to make it work automatically on workbook open.
Add the following to your formula:
+(NOW()*0)
so for example, my SUMPRODUCT above becomes
=SUMPRODUCT((DS!B:B="IN_THIS_ONLY")*(DS!D:D="New trade"))+(NOW()*0)
And this works! Excel now recalculates my special formula cells on open.
The reason for this is that NOW() is a volatile function. Here is where I learned about this: http://msdn.microsoft.com/en-us/library/bb687891.aspx
Application.CalculateFull also works, but only in Excel 2007 and later (and of course, one must enable macros to run). Unfortunately, in my case even though I use Excel 2007 my workbook will be opened by Excel 2003 users as well, so this was not an option.
Is SumProduct an array based formula function?
If so, that would explain the issue. One option is to contribute a patch to POI to add the missing support. There's been some discussion on the dev list and bugzilla on what's needed, and if you were to post to the dev list then we'd be happy to help you get started.
Otherwise, you could just set the formula recalculation flag and get Excel to recalculate the value on load