I have this function below. Trying to set the value is causing it to fail. If i comment out the value section it works as planned but i need to show the other 2 values in the cells given. I get the #Value! error when they are not commented out. Thanks for assistance.
Function GetAreas(str As String, Optional ttl1 As Range, Optional ttl2 As Range) As String
ttl1.Value = 250
ttl2.Value = 200
GetAreas = str
End Function
I am calling the function using a formula like this.
=IF($H3<>"",GetAreas($H3, $J3, $K3),"")
The problem here is that you are attempting to have a UDF change the value of cells other than the cell that called it.
By design, this is not allowed.
Instead of a UDF, you should use a Sub.
Related
I am trying to create a UDF to replace the INDIRECT function for use in the COUNTIFS function to allow dynamic ranges.
Goal:
Current:
=countifs(indirect([cell reference to named range],[criteria1]...)
I am trying to achieve:
=countifs(INDIRECTVBA([cell reference to named range],[criteria1...)
The function returns #VALUE at the moment.
Here is my VBA:
Public Function INDIRECTVBA(ref_text As String)
INDIRECTVBA = Range(ref_text)
End Function
The ref_text argument would be a reference to a cell in which a VLOOKUP is dynamically listing a named range based on user selection elsewhere.
My first guess is a data type mismatch but I am out of ideas.
Any help is appreciated!
Thanks,
Jordan
You're returning a Variant value instead of a Range.
This would give an error because CountIf is expecting a Range in its first argument and not a Variant value.
If you want to return a range add As Range to your function declaration and use a Set statement.
Public Function INDIRECTVBA(ref_text As String) As Range
Set INDIRECTVBA = Range(ref_text)
End Function
Because you omit Set the compiler is interpreting it as a Let statement. Without As Range in the function declaration, it is defaulting to As Variant. So the function is retrieving the value within the Range instead of returning the Range itself (Range objects give their Range.Value property by default when in Let statements).
I'm not too educated in CS, so this answer might be inaccurate. I genuinely appreciate learning more when people correct me.
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.
I am trying out a simple summation vba code. I want to find the sum of the first 2 rows in a defined range. However, I get a #VALUE error return upon running the following code:
Function test(prices As Range)
test = prices.Rows.Value + prices.Rows.Offset(1, 0).Value
End Function
Please advise, thanks v much!
Value will only look at a single cell while Rows is looking at all rows in your range so your code is trying to add a single cell that thinks it's the entire row and coming back with the computer equivalent of a shoulder shrug.
Try using:
Function test(prices As Range) As Double
test = Application.WorksheetFunction.Sum(prices.Rows(1).Resize(2))
End Function
It will look at the first two rows of whichever range you give it.
This would also work - now looking up as not sure of difference of leaving out the Worksheetfunction bit:
Function test(prices As Range) As Double
test = Application.Sum(prices.Rows(1).Resize(2))
End Function
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.
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.