Macro functions that access cell functions fail when spreadsheet is opened

Macro functions I have written that access cell values fail when the spreadheet first loads (they work fine after a Cntrl-Shift-F9). The problem appears to be that ThisComponent cannot find the sheets when the spreadsheet opens. The following fail.
ThisComponent.getCurrentController().getActiveSheet()
ThisComponent.getSheets()
Surely there has to be a way to write a macro function that accesses cell values without failing on startup. Am I doing something wrong? If not, isn’t this a HUGE deficiency?

The function takes two arguments: one is a key word that indicates the desired row and the other is a keyword that indicates the desired column. It allows for writing formulas that are readable.
GetValue ("Tom", "GreenAccount")
The function has the intelligence to transform the keywords into the appropriate row and column numbers and there are lots of those keywords. To solve using named named ranges would required way too many definitions and/or would requiring using a long unreadable calls like this to access a value.
INDIRECT(ADDRESS(ROW(), MATCH("FINAL", $XTRA.$A$2:$V$2,0), 1, 1, "XTRA"))
If you have a formula using 10 values, each using something like the above to specify them, the formula is a nightmare.

1 Like

It is unclear why you want to do this in a macro: maybe this could be helpful.
Also, VLOOKUP/XLOOKUP look to be the functions that could simplify your formulas.
In general, rushing to create UDFs before mastering the available functionality is prone to errors.

Hi Mike,

on the same topic, I’ve got a macro in BASIC that allows to read Calc custom properties values and insert the values into cells. This works OK when the code is inserted in the document but gives the very same result as the OP when the code is transferred into MyMacros Standard library.
IMO, my wanted functionality (getting property values) should be made possible. Any hint how to achieve this macro-wise? Would an add-in do? Or would this require an enhancement request (didn’t check if there’s any yet)?

Thanks,

This one may be tdf#125042 (a real bug); but without any specifics (the exact macro code), it’s just a guess.

Yes, here’s the code, sorry to forget that. This very rough proof-of-concept function retrieves simple custom property values (text, boolean and numeric).

Function PropVal(ByRef pName As String) As Variant
'Retrieves a given custom property value.
'Input:
'-- pName: the property name to query.
'          Charcase must be respected.

	Dim lo_Doc As Object
	Dim lo_CProps As Object		'custom properties
	Dim l_Value As Variant		'value as read
	
	'commenting this line out makes an error appear
	'at document opening: lo_Doc is reported as an XInterface without properties
	'thus .DocumentProperties doesn't exist
	On Local Error Goto ErrHandler

	lo_Doc = ThisComponent
	lo_CProps = lo_Doc.DocumentProperties.UserDefinedProperties
	If lo_CProps.PropertySetInfo.hasPropertyByName(pName) Then
		l_Value = lo_CProps.getPropertyValue(pName)
	End If
	
	ErrHandler:
		'do nothing

	PropVal = l_Value
End Function 'PropVal

I created this spreadsheet:

propVal.ods (10.9 KB)

and it has a single formula in A1 =PROPVAL("")

and it loads without a problem, it seems (using 24.8.4.1).

(I’m using 24.2.7.2)
Did you put the macro in MyMacros?

It is in the file. Possibly I should also try with “My Macros” placement, too.
Aha - yes, in My Macros, it fails.

Do this:

  • put the code in MyMacros (and remove it from the document macros) (you also may want to comment the On Local Error instruction out)
  • close all LibreOffice open documents
  • Try to open the Calc document that contains =PROPVAL()

You get a Property or method not found: DocumentProperties
error message.
The document finally loads. If you File>Reload, it works OK then.

There’s nothing you can do (except what you already done), until the mentioned bug is fixed. I added my findings to the bug.

Many thanks!

I do not think mikekaganski’s solution is the answer.

Big picture, this is a financial planning spreadsheet. Just one smaller component of the spreadsheet is calculating future costs for “reserve” items. Reserve items are high cost events that do not occur yearly, for example replacing a hot water heater. There may be rows for a hundred of different reserve items with column for a span of 100 years. To calculate the expense for any item in the list, one needs to know what is the FINAL year it is subject to being an expense, what was the PREV year it was replaced, what is the YEAR the calculation is being done for, what is the expected LIFE of the item, and what was the PRICE of the item at what YEAR. There is also a constant, INFLATION that is common to all items. The equation to calculate the amount for any item for any year is this.

 ((YEAR > FINAL) || (YEAR < PREV)) ? 0 :
	((YEAR - PREV - EXTRA) % LIFE) != 0 ? 0 :
		COST * (INFLATION ^ (YEAR - PRICED))

This is a formula implementing that using a macro function (SRC is a named range that passes in an argment that indicates the calling cells row and col number).

IF OR(GETVAL_(SRC,"YEAR") > GETVAL_(SRC,"FINAL"), GETVAL_(SRC,"YEAR") < GETVAL_(SRC,"PREV"), 
    0,  
    IF MOD(GETVAL_(SRC,"YEAR") - GETVAL_(SRC,"PREV") - GETVAL_(SRC,"EXTRA"), GETVAL_(SRC,"LIFE")) > 0,
	0
	GETVAL_(SRC,"COST") * (INFLATION ^ (GETVAL_(SRC,"YEAR")-GETVAL_(SRC,"PRICED"))),  
    )
)

Without a function, unless I am going to hardcode cell addresses (ugh), the only way I have found to do this is with INDIRECT and ADDRESS and you get this even uglier mess.

IF OR(INDIRECT(YEAR,COLUMN(),1)) > INDIRECT(ADDRESS(ROW(),FINAL,1)),INDIRECT(ADDRESS(YEAR,COLUMN(),1)) < INDIRECT(ADDRESS(ROW(),PREV,1)),
    0, 
    IF MOD(INDIRECT(ADDRESS(YEAR,COLUMN(),1)) - INDIRECT(ADDRESS(ROW(),PREV,1)) - INDIRECT(ADDRESS(ROW(),EXTRA,1)), INDIRECT(ADDRESS(ROW(),LIFE,1))) > 0,
	0
	INDIRECT(ADDRESS(ROW(),COST,1)) * (INFLATION ^ (INDIRECT(ADDRESS(YEAR,COLUMN(),1))-INDIRECT(ADDRESS(ROW(),PRICED,1)))), 
    )
)

To quote Knuth,

Instead of imagining that our main task is to instruct a computer what to to, let us concentrate rather on explaining to human beings what we want a computer to do.

Most of the 421 characters in the forumla above are noise that obscures the meaning of an already complex equation. Recall this is just one example of a situation that occurs in other places in the spread sheet. And in this one, there are only a few variables to deal with. Because functions can be passed parameters and named rnages cannot, I would need hundreds of named ranges to deal with the entire problem and maintaining many named ranges is a nightmare (there is no way of exporting or importing them so they can be easily be processed outside the spreadsheet). And that is why we have functions, so we do not have to write down something many times when all that is different is a parameter.

As to using VLOOKUP or MATCH, I hard coded the column/row numbers into this particular function because if there are changes, the only the function needs to changed and there only a few variables. In other places where there are many more variables and the columns are more likely to change, I do use VLOOKUP or MATCH. But that added robustness makes the formula even more complex by adding even more noise. Instead of this,
INDIRECT(ADDRESS(ROW(), COST,1))
You get this (HeaderRow is a named range for something like $A$2:$ZZ$2)
INDIRECT(ADDRESS(ROW(), MATCH("COST",HeaderRow,0), 1))

If anyone is interested, the macro function can be seen here, https://w3.cs.jmu.edu/arch/tmp/macro.txt but I will only keep it there temporarily. If anyone is reading this months down the line, it may no longer be available.

Basically, what you typed above is using spreadsheets not the way they are designed. Instead of creating long formula like you created, it is good to have multiple columns with shorter formulas, and having the proper named range (or the name recognition, as Iinked above), refer to the columns by semantic names. Or - with the implementation of LET function, you may assign values to names (say, set the_year to INDIRECT(YEAR,COLUMN(),1))), and then not repeat it many times, but use the semantic name.

What you wrote above (with the reference to Knuth) is trying to use Calc as a functional programming language - which is of course what I suspected it to be: you don’t know how to use the tool efficiently, but rush to fit it into your familiar paradigm.

ask114969.ods (16.5 KB)

Things become complicated as dates and times are involved.
Data type com.sun.star.util.Duration includes years and months without any start date, so we can’t know which months of 28, 29, 30, 31 days or how many leap years are included. This second sample uses the document’s NullDate as starting point, which is 1899-12-30 in the vast majority of use cases.
ask114969_2.ods (20.0 KB)

This is tdf#123005.

And as I wrote there, IMO this is not a bug:

UDFs (user-defined functions) should not operate on things like controllers, active sheets, etc; they are expected to take data, digest it, and output data. Relying on other data not directly mentioned in the formula is basically breaking the assumptions … so indeed, there might be no controller when the function is executed (and the load time is just one example; the document may also be opened in headless configurations, etc).

UDFs (user-defined functions) should not operate on things like controllers, active sheets, etc; they are expected to take data, digest it, and output data. Relying on other data not directly mentioned in the formula is basically breaking the assumptions

I do not disagree what that statement. I was hoping there was a way around that assumption. What I would question is whether the assumption is a good design principle. Most calculations in a spread sheet are based on values in other cells. I do not think the situation I am in is unusual where the locations of the other cell itself must also be calculated. To limit macro functions capabilities to not be able to do that is IMO a significant loss of usefulness.

There is no “current sheet” for a formula; there is even no controller. You may use the command line to convert the file from ODS to XLSX, and then the formulas are calculated - but then, there is no controller (the means for user to communicate with the spreadsheet), nor the “current” sheet. But your function assumes there is. Same for the time when the file loads, before it shows.

So yes, this is a proper assumption, and a good design principle. If you address other cells, you must not do that using the concepts of user interface. And your “take data from current sheet” will mean, that your formula will break as soon as you re-calculate it while another sheet is active.

1 Like