Print array called from Excel cell (VBA) - excel

I'm not sure if I need a sub or a function here, but I need something that can:
Be called from a cell in Excel
Can print values into other cells in the same worksheet
For eg.
Function PrintDemo(CellRef As Range)
Set CellRef = ActiveCell.CurrentRegion
Dim i as Integer
CellRef.Offset( , 5).Value = i
PrintDemo = 3
End Function
If I try to call this using =PrintDemo(D1) in an excel cell, hoping to print '123' 5 cells to the right, it just shows VALUE error. The function doesn't actually need to return anything, I made it hoping it would solve the error (it didn't). Nothing gets printed.
I haven't used VBA much (definitely not for printing) and I'm sure I'm missing something very fundamental in terms of how things are called. Please help me out.
EDIT: Makes sense that I can't change other cells, can I store my result as an array somewhere and have a range of cells access its values one at a time?
eg. outputArr=[1,2,3,4,5]
A2 -> =outputArr(0)
B2 -> =outputArr(1) and so on
It'll actually be 2D array but I don't think that should make much of a difference. The reason I need formulae everywhere is because this has to run in many places in the sheet, hence can't have any manual steps.

Related

MS-Excel: Retrieve Row Number where VBA Function is located

I hope I'm asking this in the correct forum:
I'm writing a UDF in VBA for MS-Excel; it basically builds a status message for the transaction on that row. It steps through a series of IF statements, evaluating cell values in different columns FOR THAT ROW.
However, this UDF will reside in multiple rows. So it might be in C12, C13, C14, etc. How would the UDF know which row to use? I'm trying something like this, to no effect
Tmp_Row = Application.Evaluate("Row()")
which appears to return a null
What am I missing here ?
Thanking everyone in advance
Application.Caller is seldom used, but when a UDF needs to know who called it, it needs to know about Application.Caller.
Except, you cannot just assume that a function was invoked from a Range. So you should validate its type using the TypeOf...Is operator:
Dim CallingCell As Excel.Range
If TypeOf Application.Caller Is Excel.Range Then
'Caller is a range, so this assignment is safe:
Set CallingCell = Application.Caller
End If
If CallingCell Is Nothing Then
'function wasn't called from a cell, now what?
Else
'working row is CallingCell.Row
End If
Suggestion: make the function take its dependent cells as Range parameters (if you need the Range metadata; if you only need the values then take in Double, Date, String parameters instead) instead of making it fetch values from the sheet. This decouples the worksheet layout from the function's logic, which in turn makes it much more flexible and easier to work with - and won't need any tweaks if/when the worksheet layout changes.
Application.ThisCell
MS Docs:
Returns the cell in which the user-defined function is being called from as a Range object.
You can put it to the test using the following code:
Function testTC()
testTC = Application.ThisCell.Row
End Function
In Excel use the formula
=testTC()
and (Cut)Copy/Paste to various cells.

vba function to return a cell background color

As the subject says, I need a function to return the cell background color. My vba function is
Public Function ReturnedBackGroundColor(rnge As Range) As Integer
ReturnedBackGroundColor = rnge.Offset(0, 0).Interior.Color
End Function
In the cell, I have
=IF(ReturnedBackGroundColor("G9") =3,1,2)
and I get a #VALUE! error. I have searched for a couple of hours off and on this morning and get no joy.
I have tried putting the function in both the sheet code and a module. I have tried Color and ColorIndex. I have tried 3 and vbRed (which BTW isn't recognized by VBA for reasons that I have forgotten. I have tried making the function public in both the sheet code and a module. I have tried recalculating the sheet in both cases.
What I suspect is happening is that I have to set something up in order to make the connection between the two for both the vbred and the function to the sheet.
This is the main issue with these forums. People put up working code but they don't tell you the shtuff that has to go around the code in order for it to work. And that is most likely what I am missing here.
Remove .Offset(0, 0), return a Long (your code is fine otherwise), and give it a Range instead of a String:
=IF(ReturnedBackGroundColor(G9)=3,1,2)
G9 being the actual cell reference, rather than a string literal containing a cell address.
That will be more reliable than building the Range from the string inside the function, since when you're invoking it from VBA code you will not want to assume what sheet is currently active.
Minor changes:
Public Function ReturnedBackGroundColor(rngeADDY As String) As Long
ReturnedBackGroundColor = Range(rngeADDY).Interior.Color
End Function
we:
pass a string
return a long
You can choose to pass either a String or a Range. But there should be consistency between the usage in the worksheet cell and the VBA coding.
Note:
The usual choice is to pass a Range as this helps to establish the correct level of volatility.

Excel no longer calculates my fields

I have written a simple VBA function to look up a value by key in a long table of key/value pairs. It then returns that value as a Double:
Public Function GetValue(sheet As String, key As String) As Double
Dim WS As Worksheet
Dim K As Range
GetValue = 0
Set WS = ThisWorkbook.Worksheets(sheet)
If WS Is Nothing Then Exit Function
Set K = FindKeyAnywhere(WS, key)
If K Is Nothing Then Exit Function
GetValue = WS.Cells(K.Row, 5).Value
End Function
I have about 60 of these formulas in a summary sheet:
=GetValue("Data", B$41)
Where "Data" is the name of the page with key/values, and B$41 is a key. Everything was working perfectly, then...
I fat-fingered the VBA, changing the = 0 to = o and calced. Now every cell on the summary still said #VALUE. When I realized what happened, I fixed the error and recalced. However, every cell on the summary still said #VALUE.
If I click in the cell and hit return, it updated fine.
I checked, autocalculate is turned on. I also clicked calc sheet several times. Nothing.
So I went to every single cell and hit return, so they were all updated. Now they don't say #VALUE, but they still don't update when I change data on the data page.
Is there anything special I'm missing? It seems like Excel is "stuck" thinking the calculation isn't valid.
UPDATE:
Using a named range does not work well, because it has to be typed into every formula. Consider...
KEY1 KEY2 ANOTHERKEY
Data1 =GetValue(A$1,B$1)
Data2
When the user CnPs or drag-fills that formula, it will get the key and sheet automatically. If we use a range name instead, they would have to type in the name in every single cell, and there's hundreds.
Is there a way to take a string and return the named range? Like =Range(A1)
As John Coleman says: Excel does not know it needs to recalculate when something on the Data sheet changes because the precedents of your UDF do not include the range od information on the Data sheet.
You need to:
Either Make the function Volatile - but this has bad recalculation
consequences.
Or pass the range on the Data sheet that contains the information
instead of passing a string. This is the best solution.
Based on your update I think you are looking for INDIRECT, which can convert a string to a range.
But there are major downsides to INDIRECT: its volatile, single-threaded and behaves badly when the string the user gives it does not exist.
IMHO this is not a good direction to go: I would recommend you consider a redesign of your data/algorithms.

In Excel, how can I avoid repeating a big part of the formula just to check if the return value is blank?

I have a situation where I am referencing cells in a different worksheet and returning the values of cells from that worksheet. Although it works, I find my current method inefficient because I have to repeat the formula in the logical test part of the IF statement:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **EXTREMELY LONG COMPLICATED FORMULA**, "")
As you can see, I must repeat the main part of the formula just to check if it is blank first. If I do not do this, I get a zero in the cell (for blank values in the referenced worksheet). I'm looking for something more like:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **RETURN VALUE**, "")
This looks cleaner to me because I won't have to repeat myself. Also, if we ever have to update the formula, I won't have to duplicate my changes to the repeated parts. Is there a way to do this?
The above is actually a simplified version of my problem, but the answer should get me where I need to go. My actual formula has nested IF statements checking along the way for blanks. For reference, here it is:
=IFERROR(IF(SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))<>"",IF(INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2)))<>"",INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))),""),""),"")
The most obvious solution is to use a helper column or cell. Just put EXTREMELY LONG COMPLICATED FORMULA somewhere in your spreadsheet, then refer to that cell in your IF formula.
Edit
To avoid a helper column, here is a trick I've used on occasion:
=IFERROR(VALUE(long_formula&""),"")
What this does is, concatenate the result of long formula with an empty string (which converts it to a string), then take the value of all that (which converts it back to a number if possible), then substitute any errors with a blank. (An error would occur if you attempt to take the value of something that's not numerical.)
This will only work if you either have a numerical result or an empty result. It will fail if you have a text result.
As of March 2020, Excel includes the LET function. You can write:
=LET(ELCF,**EXTREMELY LONG COMPLICATED FORMULA**,IF(ELCF <> "", ELCF, ""))
Where the three parameters are:
the name you will use to refer to your calculation,
the calculation itself, and
the final formula using the calculation.
The function also allows for multiple names to be defined. The general syntax is:
=LET(name1, name_value1, calculation_or_name2, [name_value2, calculation_or_name3...])
https://support.microsoft.com/en-us/office/let-function-34842dd8-b92b-4d3f-b325-b8b8f9908999
Do you need the blank in the cell for further calulations or if-functions or Do you just dont want to see the 0s?
Second case:
Just use a number format for the column like
0,00;-0,00;"";#
First case:
Put the following code in a module:
Option Explicit
Public Function IfEmpty(LongFormula As String) As String
If LongFormula = "" Then
IfEmpty = ""
Else
IfEmpty = LongFormula
End If
End Function
And use it in your worksheet like
=IfEmpty(**EXTREMELY LONG COMPLICATED FORMULA**)

Custom Excel VBA Function (Modified VLOOKUP) from Cell referring to a range in different file gives an error

First time I'm writing a stackOverflow question so please let me know if I do anything wrong.
I've searched for a few hours now and haven't been able to find a solution to my problem (usually I find the answer hence why this is my first question after using stackoverflow for a few years as a lurker).
Basically I'm trying to write a modified VLOOKUP function that functions similar to VLOOKUP except it return the "next smallest larger" value instead of the default "previous largest smaller" value. I'm aware of the index/match method unfortunately I would need to carefully replace literally thousands of VLOOKUPs manually that already exist in the workbook I'm currently cleaning up at work. Therefore I resorted to writing a VLOOKUPnew function so I could just "find/replace" all the VLOOKUP with VLOOKUPnew.
Function VLOOKUPnew(lookup_value As Variant, table_array As Range, _
col_index_num As Integer, Optional exactMatch As Boolean) As Variant
Dim row As Integer
Debug.Print table_array.Address
With Application
On Error GoTo NO_ROW
row = .Match(lookup_value, table_array.Columns(1), 0)
On Error GoTo 0
If row = -1 And Not exactMatch Then
row = .Match(lookup_value, table_array.Columns(1), 1)
row = row + 1
End If
VLOOKUPnew = .index(table_array.Columns(col_index_num), row, 0)
End With
Exit Function
NO_ROW:
row = -1
Resume Next
End Function
And I succeeding in writing the function but hit a snag. Because I declared "table_array" as a Range, vba fails to identify range references to other workbooks
e.g. "=VLOOKUPnew($A432,'reallyLongFilepath/[filename.xlsx]tablename'!$B$6:$N$35,columnNumber,0),FALSE)" resolves to a #VALUE error
The really weird thing is that if I open the file, then the filepath drops out of the formula (becoming just "=VLOOKUPnew($A432,'[filename.xlsx]tablename'!$B$6:$N$35,columnNumber,0),FALSE)") and then my custom function works just fine returning the correct value.
So my problem is how do I resolve not having to open the other file to use this workbook. I'm not even sure how Excel is passing the address or range to the custom formula so I'm suspecting it's breaking when the filepath is included in the range reference. Is there a way to split the filepath, filename, sheet and address (after it has been passed in)? Or possibly pass it in as a string then easily split it? Or pass it in as something that will correctly identify the range in the different workbook?
Keep in mind that I'm trying to avoid changing the arguments of the function because I want to do the find/replace trick and that this is for work so there's a restraint on too much change in data layout. Also that the workbook is for other employees to use and I'm just setting it up for use.
Thanks in advance!
Andrew
You face quite a dilemma here!
The root problem is that while VLOOKUP can look into closed workbooks, a Range parameter in a UDF cannot. The range reference resolves to an error, so the function call fails with a type mismatch. If you change the table_array parameter type to Variant and put a break on the function header, you will see the parameter value as Error 2036.
While there are ways to look into closed workbooks, all of them (AFAIK) are quite slow. Since you mention ... I would need to carefully replace literally thousands of VLOOKUPs ... I suspect any solution along these lines would be unacceptably slow.
My reccomendation would be to go the INDEX/MATCH route, and write a VBA macro to do the formula updates for you.

Resources