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.
Related
I am trying to dynamically reference a name range based on the contents of a cell.
For example, I have the following dynamic named ranges:
tCat1_Pass
tCat2_Pass
tCat3_Pass
Assuming we have the value 2 in Cell A1, I would like to reference tCat2 like the following
"tCat"&A1&"_Pass" which would = "tCat2_Pass"
INDIRECT doesn;t work with this, are there any other solutions, apart from writing a UDF or using CHOOSE?
See below screenshot to use INDIRECT() function.
I have solved this by writing a small UDF:
Public Function nm_return(cat_nbr As Integer, tbl_nm As String)
nm_return = Range("tCat" & cat_nbr & tbl_nm)
End Function
You can also use EVALUATE within Name Manager, i.e. define nm_return as:
=EVALUATE("tCat"&$A$1&"_Pass")
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'm trying to use structured references to the current columns the same as CountIf does for my UDF function. While
=COUNTIF(Data[Team];Overview[Team])
works, my new function
=CONCATENATEIF(Data[Team];Overview[Team];Data[Data])
doesn't work, since the Overview[Team] criteria Range can't be cast to a single value which is [#This Row].
I tried to change the parameter "criteria" As String as well as different methods. Calling
=CONCATENATEIF(Data[Team];Overview[#Team];Data[Data])
with "#" works as intended. But CountIf can handle [#Team], [Team] and normal ranges like [A1:A4]. So how they do it?
Public Function CONCATENATEIF(check_range As Range, criteria As Range, data_range As Range) As Variant
Dim mydic As Object
Dim L As Long
Set mydic = CreateObject("Scripting.Dictionary")
For L = 1 To check_range.Count
If check_range(L) = criteria Then
mydic(L) = data_range(L)
End If
Next
CONCATENATEIF= Join(mydic.items, ", ")
End Function
What cast does criteria need to work like CountIf's criteria? How can i transform the structured Reference [Team] to [#Team] vba-wise, so it selects the same row, where the Formular is used later.
The table for the problem (sadly can't embed images yet)
COUNTIF works due to inferred reference¹.
If you put a bunch of values in column A and then use =INDEX(A:A, , ) (Index(<column_A>, <all_rows>, <all_columns>)) in an unused column to the right of the data then the result will be from the common row in column A. Since you haven't provided a specific row reference where a single cell reference is expected, the associated (or inferred) row is used. This is why COUNTIF works; it is using an inferred reference from the Overview[Team] column to reference a single cell for criteria; e.g. the cell in Overview[Team] that is on the same row as the formula (also known as Overview[#Team]).
The VBA code is not using an inferred reference. It is referencing the whole column of Overview[Team] where it needs a single cell for criteria (e.g. Overview[#Team]).
You could try to artificially parse the column of criteria down to a single cell with something like Application.Caller.Row or you could just use Overview[#Team] as the criteria like it was intended.
¹ I hope I got that term right. I use it so little that I have a hard time remembering the correct term sometimes.
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.
I'm trying to use range.Find to find the cell in another sheet that contains a string input on a previous sheet (I'm searching for a column header in a table so I won't have to update my macro if columns get shifted around). I'm using the function below, but I always get an error saying that an Object is Required. I've looked at similar topics and tried their methods, but what works for them just isn't working for me. How can I use Range.Find to find the cell that a column header is in in a different sheet? Here's the code I'm using:
Function FindColumn(ByVal name as String) As Range
'cds is the other worksheet I need to find the column header in
Set FindColumn = cds.Range("A1:AA1").Find(name)
If FindColumn Is Nothing Then MsgBox(name & " Not Found!")
Exit Function
End Function
in your code, cds would appear to be Nothing, which could contribute to that error. Is cds assigned a worksheet object elsewhere in your code? If so, what is that variable's scope? If it is not module or public, then that would explain it.
If it is module/public, ensure that the variable cds is assigned prior to calling the function.
If it is a procedure-level variable, you will need to pass it to the function like:
FindColumn "columnname", cds
And modify the function to accept this additional argument:
Function FindColumn(byVal name as String, cds as Worksheet)