I originally had a VBA function that returned a two dimensional variant array:
Public Function SplitIntoCells(some_data As String) As Variant()
End Function
I used the Formula array syntax to call it from another vba function:
Public Function MyWrapper() as Variant
MyWrapper = SplitIntoCells("somestring")
End Function
From Excel, if I select a big enough range and then do:
=MyWrapper()
followed by CTRL+SHIFT+ENTER, the data is nicely split into every individual cell in that range.
However in order to automate that, if I change MyWrapper to:
Public Function MyWrapper()
ActiveSheet.Range("A1:E20").Select
Selection.FormulaArray = SplitIntoCells("somestring")
End Function
The above doesn't work. I see nothing being displayed in Excel.
What am I doing wrong?
Update:
Just for testing, if I slightly modify MyWrapper() to:
Public Function MyWrapper()
Dim variant_temp() as Variant
variant_temp = SplitIntoCells("somestring")
ActiveSheet.Range("A1:E20").Select
Selection.FormulaArray = variant_temp
End Function
variant_temp predictably has the 2-d array after returning from SplitIntoCells but the subsequent Selection.FormulaArray still has nothing in it even after the assignment. I am sure I am missing something blindingly obvious.
A VBA user-defined function cannot modify the cell or range it's being called from: it can only return a value.
Related
I have essentially a simple syntax question concering Application.Run. I want to write a bit of code where I pass a UDF a string coantaining the name of a worksheet function, e.g. 'iserror' or some other UDF returning boolean. The function will then be exectued for each cell within the passed range and do something depending on result.
However, I have not been able to work out the proper Syntax. Error Messages Change along with my Trials, but non are particularly helpfull. e.g.:
?hrCull(Range("Data!A1:B10"),"Worksheetfunction.iserror", False)
(Error message in German, I'll try my best to translate, but it probably won't 100% match the English Version):
Runtime error 1004:
The macro 'Worksheetfunction.iserror' can not be exectued. The macro may not be available in this worksheet or macros have been deactivated.
Of course, macros have not been deactivated, but it isn't really a macro anyway. Also tried without the leading 'Worksheetfunction', same error message.
In my code the call Looks like this:
Public Function hrCull(r As Range, func As String, Optional invert As Boolean = False) As Range
Dim c As Range
Dim selector As Boolean
...
selector = Application.Run(func, c)
...
end function
I omitted code not relevant.
So what is the proper Syntax?
Misc:
- I'm Aware that I can not assert that the passed function returns a boolean.
- Excel 2016 on Windows 7
A solution using CallByName:
selector = CallByName(Application.WorksheetFunction, "IsError", VbMethod, c)
Lose the WorksheetFunction. prefix, Evaluate doesn't like it as Evaluate is for worksheet functions.
In your function, use:
selector = Application.Evaluate(func & "(" & c.Address & ")")
To test, use:
Debug.Print hrCull(Range("A1"), "ISERROR")
I think you'd be better off declaring your own Enum and adding the functions that you want into this. Then execute them using built in syntax instead of trying to evaluate a string
Public Enum xlSheetFunction
xlIsError
End Enum
Public Function hrCull(r As Range, func As xlSheetFunction, Optional Invert As Boolean = False) As Range
Dim selector As Boolean
Select Case func
Case xlIsError
selector = WorksheetFunction.IsError(r)
End Select
Debug.Print selector
Set hrCull = r
End Function
Public Sub test()
Debug.Print hrCull(Range("A1"), xlIsError)
End Sub
i have created user defined function to calculate football match results.
My Root Function looks that:
Function calculatePoints(personTypes As Range, matchesResults As Range) As Integer
calculatePoints = getAllPersonPoints(personTypes, matchesResults)
End Function
getAllPersonPoints function:
Private Function getAllPersonPoints(personTypes As Range, matchesResults
AsRange) As Integer
Dim x As Long
Dim y As Long
Dim isTheSurest As Boolean
getAllPersonPoints = 0
For x = 1 To personTypes.Rows.Count
For y = 1 To personTypes.Columns.Count
isTheSurest = isTheSurestResult(personTypes.Cells(x,
y).DisplayFormat.Interior.PatternColorIndex)
getAllPersonPoints = getAllPersonPoints +
getPoints(matchesResults.Cells(x, y).Value, personTypes.Cells(x, y).Value,
isTheSurest)
Next y
Next x
End Function
When i am trying to call this function by setting manually parameters: personTypes range and matchesResults ragne - everythink works fine.
But when i am trying to call it from sheet i got #VALUE error in selected cell.
But at function form there is correct result:
A have been trying to debug return value and always i got correct value. I have problem only with error in return cell.
Any ideas ?
The issue is that DisplayFormat object does not work with UDF's
See MSDN article
The usual solution to this is to evaluate the Conditional Format conditions to determine which one is active. For example, see cpearson.com
I resolved problem by code:
personTypes.Cells(x, y).Interior.ColorIndex
instead of:
personTypes.Cells(x,y).DisplayFormat.Interior.PatternColorIndex
This solution works.
I want to call a bloomberg function inside a UDF, hence, I can't use
Cells(x,y).Formula = bdp(equity, field)
Which is what I normally see. I've tried Aplication.Run and WorkSheetFunction without success, are there other ways so that I can call this function?
A UDF isn't setting formulas, nor is it even setting a value - it is returning a value - so just return the value of the Bloomberg function as the result of your UDF.
For example:
Public Function MyUDF(....) As Variant
'...
'whatever calcs you are doing to determine "equity" and "field"
'(or maybe they are parameters)
'...
MyUDF = bdp(equity, field)
End Function
I'm writing a few VBA functions for work and ran into a problem that should be easy to solve, but somehow I can't manage to, despite my best attempts at finding an answer here and on Google. I wrote a function that should give me the range between two strings in a column:
Function FindRng(StartRng As String, EndRng As String) As Variant
Dim TopOfRange As Single
Dim BottomOfRange As Single
TopOfRange = WorksheetFunction.Match(StartRng, Sheets("InfCom").Range("B:B"), 0)
BottomOfRange = WorksheetFunction.Match(EndRng, Sheets("InfCom").Range("B:B"), 0)
FindRng = Range(Sheets("InfCom").Cells(TopOfRange, 2), Sheets("InfCom").Cells(BottomOfRange, 2))
End Function
So if the inputs A and B are on rows 100 and 105, it should return B100:B105. When I test this by adapting the code to read FindRng = Range(...).Address, I indeed get $B$100:$B$105.
However, when I then input the result of FindRng into a customized Index Match function, I get an error. The function is as follows:
Function subsetPBPC(rngReturn As Range, LookupValueH As Variant, TopOfRange As String, BottomOfRange As String, LookupValueV As Variant) As Variant
subsetPBPC = sPBPC(rngReturn, LookupValueH, FindRng(TopOfRange, BottomOfRange), LookupValueV)
End Function
The problem is that it seems to read the output of FindRng not as a range, but as the content of that range: when I use the Evaluate Formula tool on FindRng embedded in another formula, it shows the output of FindRng as {A,B,C,D,E} instead of $B$100:$B$105, where A to E are the contents of the cells in the range. I have the feeling the solution is really simple, but I don't see it. The functions underlying the customized Index Match function have been tested and all work like a charm.
Set instead of let. Let assigns the value of an expression to a variable. Set assigns an object reference to a variable. You want to return a reference to the range object, not return the value produced by the range object's default property.
In VBA writing
FindRng = Range(...)
is implicitly writing
Let FindRng = Range(...)
However you want
Set FindRng = Range(...)
Edit 1:
It is quite important to understand the difference between an object reference and a value in VBA. This is a similar concept to passing arguments by value or by reference. Hopefully these two links help some:
The Let statement on MSDN
The Set statement on MSDN
Edit 2:
Oh, and I guess I should touch on default properties! Some objects like range have default properties. If you treat the range as a value instead of an object, it uses the default property instead of throwing an error because it's an object not a value. In the case of range the default property is Value. So if you say A = Range("A1") what you're actually saying is Let A = Range("A1").Value when you might mean Set A = Range("A1"). So you're getting the value contained in the cell A1, instead of a range object representing that cell.
Picking up that your current code should both
use Set as per AndADM's commnet
dimension SetRng as a Range rather than Variant
you can simplify your function as below (which may save time if you are calling it repetitively)
Also, you could test for this range being Nothing (if your two strings werent found), whereas you current code will error out if either string is missing.
Function SetRng(str1 As String, str2 As String) As Range
With Sheets("infCom").Columns(2)
Set SetRng = Range(.Find(str1, , xlValues, xlWhole), .Find(str2, , xlValues, xlWhole))
End With
End Function
I'm 99% sure that the answer is "no", but I'm wondering if someone who is 100% sure can say so.
Consider a VBA UDF:
Public Function f(x)
End Function
When you call this from the worksheet, 'x' will be a number, string, boolean, error, array, or object of type 'Range'. Can it ever be, say, an instance of 'Chart', 'ListObject', or any other Excel-VBA object model class?
(The question arose from me moving to Excel 2007 and playing with Tables, and wondering if I could write UDFs that accept them as parameters instead of Range. The answer to that seems to be no, but then I realized I didn't know for sure in general.)
Your suspicions are correct - you can only pass in limited object types. For example, if I have table on the active worksheet and wanted to know it's column count, I could create a UDF called TableColumnCount and pass in the table name into a function like:
Function TableColumnCount(tn As String) As Integer
Dim myTableName As ListObject
Dim ActiveS As Worksheet
Set ActiveS = ActiveWorkbook.ActiveSheet
Set myTableName = ActiveS.ListObjects(tn)
TableColumnCount = myTableName.Range.Columns.Count
End Function
and then call it on sheet with the name of my able as a string, like =TableColumnCount("Table1").
Or as a range object like:
Function TableColumnCount(tn As Range) As Integer
TableColumnCount = tn.Columns.Count
End Function
And then call it like: =TableColumnCount(Table1)