How do I read the arguments from a formula in VBA? - excel

Say I have the following function:
function MyFunction(A as string, Optional B as integer) as string
to call this function in cell A1:
=MyFunction("Hello","15")
I want to access the arguments of this function from another routine by reading the contents of the cell at A1:
Range("A1").Formula
This however returns the entire formula with arguments as a string as the formula was entered in the cell. Is there a way to easily extract the arguments ("Hello" and 15)?I am trying to avoid the clunky text manipulation route.

If all of your arguments are Ranges, you can use the Precedents property to get at the arguments.
If that's not the case, I'm afraid text manipulation is what you've got. If you're only trying to parse a particular function (instead of any function), it's not that bad.
Function MyFunc(ar1, ar2)
MyFunc = 1
End Function
Public Sub ReadMyFunc(rng As Range)
Dim vaArgs As Variant
vaArgs = Split(Split(Left(rng.Formula, Len(rng.Formula) - 1), "MyFunc(")(1), ",")
Debug.Print "First arg is: " & vaArgs(0) & vbNewLine & "Second arg is: " & vaArgs(1)
End Sub
The result from the immediate window
?activecell.Formula
=MyFunc(A1,"1")
readmyfunc activecell
First arg is: A1
Second arg is: "1"

Related

Using a variant of the SUMPRODUCT formula in VBA

I want to use following type of SUMPRODUCT formula in VBA:
=SUMPRODUCT(A1:A2,C1/B1:B2)
It works fine in excel, but when using it as a VBA function it gives an #VALUE! result:
Function Test(LineA As Range, LineB As Range, ValueC As Double)
Test = Application.WorksheetFunction.SumProduct(LineA, ValueC / LineB)
End Function
How can I write that formula with its values using vba?
Try using the Evaluate method...
Function Test(LineA As Range, LineB As Range, ValueC As Double)
Test = Evaluate("SUMPRODUCT(" & LineA.Address(external:=True) & "," & LineB.Address(external:=True) & "/" & ValueC)
End Function
Wrap it in evaluate
Converts a Microsoft Excel name to an object or a value.
Name... Variant .....A formula or the name of the object, using the naming
convention of Microsoft Excel. The length of the name must be less than or equal to 255 characters.
Application.Evaluate("=SUMPRODUCT(A1:A2,C1/B1:B2)")
Very similar to the above - but just in case it helps anyone, here is what I did with reference to sheet names and variable column lengths
Set a = Sheets("TheSheetName").Range([M10], [M10000].End(xlUp))
Set b = Sheets("TheSheetName").Range([L10], [L10000].End(xlUp))
c = Application.Evaluate("=SUMPRODUCT(" & a.Address & ",1/" & b.Address & ")")

Transpose a split range into a variant array

Morning all, I just tried something and it didn't work.
If I use this function:
Public Function GetPeople()
GetPeople = Application.WorksheetFunction.Transpose(wsPeople.Range("A2:A10").Value2)
End Function
I get a 1D variant / array of 9 strings with the values in cells from A2:A10
I'm trying to do the same for a split range:
Public Function GetPeople2()
GetPeople2 = Application.WorksheetFunction.Transpose(wsPeople.Range("A2,A5,A10").Value2)
End Function
But it only returns the value from A2 and not all three like I wanted.
In reality this split range is defined by a helper column with an "x" - any rows marked "x" will need to be included in this split range.
What is the simplest way to get the same 1D variant array of strings as a function return, but by using a split range?
It's probably just a classic cycling through rows but wondered if .Transpose could still be used.
Office 365 solution (via contiguous range reference)
In reality this split range is defined by a helper column with an "x" - any rows marked "x" will need to be included in this split range.
This allows to pass the entire (contiguous) data column range as function argument evaluating the condition only "x" using the new worksheet function FILTER() with its dynamic Office 365 possibilities.
This allows to get the wanted data directly instead of going the long way round creating a non contiguous range reference first and you can code a one-liner:
Public Function GetPeopleX(rng As Range, _
Optional ByVal criteria = "x", _
Optional ByVal myOffset As Long = 1)
GetPeopleX = Application.Transpose(Evaluate("=Filter(" & rng.Address & "," & rng.Offset(, myOffset).Address & "=""" & criteria & ""","""")"))
End Function
Example call
Sub ExampleCall()
Debug.Print Join(GetPeopleX(Sheet1.Range("A2:A10")), ", ")
End Sub
Addendum
If it's probable that there isn't at least one row marked by x you could add the following error handling to the function GetPeopleX():
On Error Resume Next
Debug.Print UBound(GetPeopleX) ' << force possible error if nothing found
If Err.Number <> 0 Then GetPeopleX = Array(): Err.Clear
As the error handling returns only a declared array without entries (LBound: 0, Ubound: -1), this allows to use Join() for any result, but to check for positive array results in the calling routine via boundaries. So an items count could be done via LBound().
Filter() function
The WorksheetFunction itself building the basis of GetPeopleX() in a simplified form could be
=FILTER(A2:A10,B2:B10="x","")
Syntax: =FILTER(array,include,[if_empty])
c.f. Help Reference Filter function

How to find the text between two values in a string?

I currently have a cell("Filename"). This returns H:\F0791\Purchase Requisitions\[PCS.xlsm].
I would like to single out the 'F0791' Value. I have previously used MID functions however, this does not work if 'F0791' is a different length.
Would it be possible to call up the values between the first two '\'s or is there a better alternative?
I am seeking this in both formula state and VBA. This is different to other questions because they do not offer a formula alternative.
You could use this UDF, that uses the Split Function
Function EXTRACTELEMENT(Txt As String, n, Separator As String) As String
On Error GoTo ErrHandler:
EXTRACTELEMENT = Split(Application.Trim(Mid(Txt, 2)), Separator)(n - 1)
Exit Function
ErrHandler:
' error handling code
MsgBox "ERROR: Verify if the data exists, example if the separator is correct."
On Error GoTo 0
End Function
And this is a test in VBA
Sub test()
Text = "H:\F0791\Purchase Requisitions[PCS.xlsm]"
Debug.Print EXTRACTELEMENT(CStr(Text), 2, "\")
End Sub
And you could also add it to a Cell, If E1= "H:\F0791\Purchase Requisitions[PCS.xlsm]" Then you add this to the desired result cell.
On cell F1, this formula:=EXTRACTELEMENT(E1;2;"\") gives the result on the image below:
Or open the insert function window
Optional, Description for UDF
This code adds a description for the UDF. You must run it once.
Sub DescribeFunction()
Dim FuncName As String
Dim FuncDesc As String
Dim Category As String
Dim ArgDesc(1 To 3) As String
FuncName = "EXTRACTELEMENT"
FuncDesc = "Returns the nth element of a string that uses a separator character"
Category = 7 'Text category
ArgDesc(1) = "String that contains the elements"
ArgDesc(2) = "Element number to return"
ArgDesc(3) = "Single-character element separator (spc default)"
Application.MacroOptions _
Macro:=FuncName, _
Description:=FuncDesc, _
Category:=Category, _
ArgumentDescriptions:=ArgDesc
End Sub
Another formula approach is to use this formula =MID(A1,4,FIND("----",SUBSTITUTE(A1,"\","----",2),1)-4) in cell B2 in case your strings are in column A
For a formula approach, the following worked for me, given that the "filename" cell is in A1:
=MID(A1,FIND("\",A1)+1,((FIND("\",A1,FIND("\",A1)+1))-(FIND("\",A1))-1))
This basically finds the position of the first "\" and the second "\" and uses the mid() function to pull the string between the two "\"s.
=MID(A2, FIND("\", A2)+1, FIND("\", A2, 4) - 4)
This worked for me assuming that the structure is more or less the same except with regard to the length of the code.

Not able to split for multiple special characters

I am trying to split cell value up to last "\" in string
'C:\Users\punateng\Desktop\Pending Spec Updates.xlsx' in cell A1 so that result in cell B1 should be 'C:\Users\punateng\Desktop\'
I have written below code:
Sub mu()
Cells(1, 2).Value = Split(Cells(1, 1).Value, "\")
End Sub
But i am getting result as C: in cell B1.
Please help.
This is a case where a function is better than a sub.
Excel has Find and Search functions but doesn't have a FindLast function.
In a User Defined Function (UDF) you can use some functions that aren't available in the Application.WorksheetFunction collection. One of them is InstrRev which finds the position of the first instance of a string like "\" reading backwards from the end of the string. Using This little gem of knowledge and text editing functions you can build this UDF:
Function FileNameFromPath(Path1 As String)
'Test if you have been parsed an network file path
If InStr(1, Path1, "\") > 0 Then
'Get whatever is after the last "\"
FileNameFromPath = Right(Path1, Len(Path1) - InStrRev(Path1, "\"))
Else
'Could be a SharePoint file with http// address
If InStr(1, Path1, "\") > 0 Then
'Get whatever is after the last "/"
FileNameFromPath = Right(Path1, Len(Path1) - InStrRev(Path1, "/"))
Else
'There isn't a path separator there
FileNameFromPath = "That ain't a path"
End If
End If
End Function
So you can call this UDF in any cell in your workbook by typing "=fi", hitting Tab to paste it into your cell, then selecting the cell you want to test and enter a end bracket ")".
Split returns a string array, which in this case will consist of each part of the input text that was previously separated by a \ - so, you are actually returning the array {"C:";"Users";"punateng";"Desktop";"Pending Spec Updates.xlsx"}. When you try to paste this into Cells(1,2), VBA just interprets this as being the first element of the string array.
Instead, you might want to try
Cells(1,2).Value=Left(Cells(1,1).Value,InstrRev(Cells(1,1).Value,"\")-1)
which should find the last instance of \ and return the text before it.

Text Format Macro for Excel

I'm trying to convert a list of names, each in a seperate cell, into a list with # before each name commas afterwards and combined into a single cell. What type of macro would I use for that. So:
Help
Me
Please
Thank
You
into (single cell):
#help, #me, #please, #thank, #you
Thanks
Try this code:
function convertNames(startRow as long,endRow as long,column as long) as string
dim result as string
for c=startRow to endRow
result=result & "#" & Cells(c,column) & ", "
next
result=left(result,len(result)-2)
convertNames=result
end function
You would call this function in the cell where you want to display the results as:
=convertNames(5,12,2)
substituting in the start row, end row, and column index that you need.
Try this function:
Function ConvertNames(List As Range) As String
Dim C As Range
For Each C In List
ConvertNames = ConvertNames & "#" & C.Value2 & ", "
Next C
ConvertNames = Left(ConvertNames, Len(ConvertNames) - 2)
End Function
It is inspired by sigil's answer, but this one works with a range, and allows Excel to manage the references to cells. Sigil's function should be volatile and would slow down large files.
You need to add a module to the project and put this function in the module. Then you can use it by typing =ConvertNames(A1:A5) on the cell that uses it.

Resources