VBA - applying function to a sub - vba

im a VBA newbie. I have to create function, that transform state address to state full name eg. CA -> California and apply it to the whole column with addresses.
Given task:
*Open VBE and add module named modFunction. In this module create function (named this function StateFullName) which transform state address into state full name. Function should take one parameter (state address) and return one string value (state full name)
In column 7 each rows with data should have full state name (Tip: use loop and function prepared in modFunction).*
I have created a function, but now I don't know how to apply it in sub with loop.
My function:
Function StateFullName(state_address As String) As String
Select Case state_address
Case "CA": StateFullName = "California"
Case "AZ": StateFullName = "Arizona"
Case "MT": StateFullName = "Montana"
Case "NM": StateFullName = "New Mexico"
End Select
End Function

When setting up your Function as a public function, you can use it in two ways, as a formula and as a Function on other Macros/Subscripts.
Here is a sample of your code:
Public Function statestring(twoletter As String) As String
Select Case twoletter
Case "CA": statestring = "California"
Case "AZ": statestring = "Arizona"
Case Else: statestring = "Unknown State"
End Select
End Function
And here I am using it as a Formula on Column B, top right you can see the formula:
And here I am using it on another Sub as a Function to fill Column C:
(It could be a lot better coded but I just wrote it as it came to mind)
Edit: After a quick thought an offset would be make it much better
Sub getFullState()
Set wk = ThisWorkbook
With wk.Sheets("Sheet1")
'startRow = 2 ' Removed
For Each Rng In .Range("A2:A4")
'Next line Replaced with Offset
'.Range("C" & startRow).Value = statestring(Rng.Value)
Rng.Offset(0, 2).Value = statestring(Rng.Value)
'startRow = startRow + 1 'Removed
Next
End With
End Sub

Related

VBA, string arguments in UDF that does not have quotation marks - how to access their value?

My function in a VBA is:
Function myFunc(a)
myFunc = a
End Function
When I use this function in Excel sheet in this way =myFunc("abc"), it is working, but when I use formula without pair of quotating marks =myFunc(abc), then I'm receiving error #NAME?.
Trying change argument from Function myFunc(a) to Function myFunc(chr(34) & a & chr (34) ) leads to error Expected: ).
How can I access a value that was typed without quotation marks in a UDF ( user defined function )?
Update: I need it to simplify usage of UDF for end user.
I don't know why do you need something like that. But it is possible! Read about Application.Caller - it's rng where UDF is running.
Private Function myFuncCalc(ByVal xstr As String)
' it is your main function to calculate what you want
' just sample code to test below
If xstr = "USD" Then
myFuncCalc = "yes it's american dollar!"
Else
myFuncCalc = "it's no american dollar"
End If
End Function
Function myFunc(a)
' function just to be available in worksheet
' and extracting currency letter codes from formula between brackets
bra1 = InStr(Application.Caller.Formula, "(")
bra2 = InStr(Application.Caller.Formula, ")")
x = Mid(Application.Caller.Formula, bra1 + 1, bra2 - bra1 - 1)
myFunc = myFuncCalc(x)
End Function
Voila!
If you use it without quotes, excel is expecting a named range. If what you want is to get the contents from another cell, you should define the argument as myfunction(a as Range) and then get its value using a.Value2 or a.Text.

VBA instr Function used in Left function?

What I Have:
A column I have this data 24/25,25/26, up to 100/101 and in B column I have this data 24,25,26, up to 101. Using Left function I'm checking Left(24/25,2) with column B.... when coming to 100/101 it's giving me return as 10.
What I want:
Left(100/101,2) Then its have return 100 how do I do that I know if we give 3 then it will return but when we give three then 24/ also come. Give me any Suggestion.
You can use the Split Function:
Dim str As String
str = "24/25,25/26"
Dim arr As Variant
arr = Split(str, "/")
The result then is
'arr(0) is "24"
'arr(1) is "25,25"
'arr(2) is "26"
Sheet formula version:
You want to Find the position of "/" in the cell, then remove 1. That will give the number of characters before the "/" that you are after for Left. So, if data was in A2, you would put the following in B2:
=IFERROR(LEFT(A2,FIND("/",A2)-1),"")
A VBA version (User defined function) might look like:
As you mention using Instr.
Option Explicit
Public Sub TEST()
Debug.Print GetLeft([A2].Text)
End Sub
Public Function GetLeft(ByVal aString As String) As Variant
If InStr(aString, "/") > 0 Then
GetLeft = Left$(aString, InStr(aString, "/") - 1)
Else
GetLeft = CVErr(xlErrNA)
End If
End Function
Use Like.
If Range("A4").Value2 & "/" Like "*" & range("B4").Value2 & "/*" Then
I added a "/" at the right of each side, to avoid 10 matching 100 or 101.

Excel formula calculating once then deleting

I have an excel formula:
=SplitKey(GetSysCd(INDEX([ReportValue],MATCH("mtr_make_model",[FieldName],0)),INDEX([ListName],MATCH("mtr_make_model",[FieldName],0))), 0)
which is running a few subroutines in VBA, but mainly matching values and inserting those values into a cell. When it finds a value for "mtr_make_model" it runs and matches the values inside a sys codes table. The issue I am having is that it is calculating once and then it removes the formula and now has solely the value... In the event that I go to the mtr_make_model field and change the value, the formula does not recalculate. Has anyone heard of this happening? Is this due to something in the VBA code? How do I make that formula stay put and if certain values change, the formula recalculates?
Thanks in advance.
Here are the two functions:
Public Function GetSysCd(ByVal name As String, sysCdTableName As String) As String
Dim r As Integer
Dim sysCdTable As Range
Dim nameList As Variant
Dim sysCd As String
On Error GoTo GetSysCd_Error
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
nameList = WorksheetFunction.Index(sysCdTable, 0, 2)
r = WorksheetFunction.Match(name, nameList, 0)
sysCd = WorksheetFunction.Index(sysCdTable, r, 1)
GetOutOfHere:
On Error GoTo 0
GetSysCd = sysCd
Exit Function
GetSysCd_Error:
sysCd = ""
GoTo GetOutOfHere
End Function
Public Function SplitKey(s As String, v As Integer)
Dim aString As Variant
Dim r As Integer
If Len(s) > 2 Then
aString = Split(s, "_")
If v = 0 Or v = 1 Then
SplitKey = aString(v)
Else
SplitKey = aString(0)
End If
Else
SplitKey = ""
End If
End Function
I don't think the functions are relevant at this point, but rather just a matter of the function not recalculating when a variable in the formula changes...
The problem could be that Excel only recalculates functions when one of their arguments changes, and your GetSysCd function is referring to a range that is not in its argument list
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
where sysCdTableName is just a string rather than a reference.
You can make the functions recalculate in real time by adding Application.Volatile True to the top of each function.

Get one cell from passed column/row/range VBA Excel

i'm writing a user defined function for excel in VBA.
User may pass a whole column/row into the function instead of one cell. How do you get cell that is in the same row (for column case) and in the same column (for row case), where the function is.
For example, when you are writing in Excel in cell, say, C3 the formula "=A:A*B:B" it calculates A3*B3 in fact. I want to have the same behaiviour in my UDF.
Let's assume function that returns passed argument for simplicity reasons.
This code does not work (returns #VALUE! for columns/rows/ranges):
Public Function MyTestFunction(ByVal arg1) As Variant
MyTestFunction = arg1
End Function
My option is as follows, but I am concerned about performance and the fact that user may want to pass a value to the formula instead of Range.
Public Function MyTestFunction2(ByVal arg1 As Range) As Variant
If arg1.Count = 1 Then
MyTestFunction2 = arg1.Value
Else
' Vertical range
If arg1.Columns.Count = 1 Then
MyTestFunction2 = arg1.Columns(1).Cells(Application.Caller.Row, 1).Value
Exit Function
End If
' Horizontal range
If arg1.Rows.Count = 1 Then
MyTestFunction2 = arg1.Rows(1).Cells(1, Application.Caller.Column).Value
Exit Function
End If
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
End If
End Function
How do you solve this problem?
Thanks to valuable comments code has been slightly updated and now can be used in other formulas to filter input values.
Public Function MyTestFunction2(ByVal arg1) As Variant
If Not TypeName(arg1) = "Range" Then
MyTestFunction2 = arg1
Exit Function
End If
If arg1.Count = 1 Then
MyTestFunction2 = arg1.Value
Else
' Vertical range
If arg1.Columns.Count = 1 Then
' check for range match current cell
If arg1.Cells(1, 1).Row > Application.Caller.Row Or _
arg1.Cells(1, 1).Row + arg1.Rows.Count - 1 < Application.Caller.Row Then
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
Exit Function
End If
' return value from cell matching cell with function
MyTestFunction2 = arg1.Worksheet.Columns(1).Cells(Application.Caller.Row, arg1.Column).Value
Exit Function
End If
' Horizontal range
If arg1.Rows.Count = 1 Then
' check for range match current cell
If arg1.Cells(1, 1).Column > Application.Caller.Column Or _
arg1.Cells(1, 1).Column + arg1.Columns.Count - 1 < Application.Caller.Column Then
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
Exit Function
End If
' return value from cell matching cell with function
MyTestFunction2 = arg1.Worksheet.Rows(1).Cells(arg1.Row, Application.Caller.Column).Value
Exit Function
End If
' Return #REF! error to user
MyTestFunction2 = CVErr(xlErrRef)
End If
End Function
In the first code snippet change MyTestFunction = arg1 to Set MyTestFunction = arg1. Also add a small mechanism that recognizes the TypeName() of the arg1 and make sure that the function is receiving a Range type object.
Public Function MyTestFunction(ByVal arg1) As Variant
Set MyTestFunction = arg1
End Function
example
Then, if you get to your spreadsheet and type in =MyTestFunction(A:A) on any row and you'll receive the equivalent value from the column you're passing to the function that sits on the same row.
And your second idea about getting a similar behaviour as =A:A*B:B you can achieve with
Public Function MyTestFunction2(ParamArray arr() As Variant)
MyTestFunction2 = arr(0)
End Function
example
I think you need to use Application.ThisCell property to do it. According to MSDN:
Application.ThisCell- Returns the cell in which the user-defined
function is being called from as a Range object.
Let me present how to use it on simple example.
Imagine we have data as presented below in column A:B and we want to achieve results which comes from =A*B for each row separately.
In such situation you need the function below and put it next in C column in this way: =MyTestFunction(A:A,B:B)
Function MyTestFunction(rngA As Range, rngB As Range)
Dim funRow As Long
funRow = Application.ThisCell.Row
MyTestFunction = rngA(funRow) * rngB(funRow)
End Function
Please keep in mind that Application.ThisCell will not work if you call your function from other VBA procedure.

How can I check if a cell in Excel spreadsheet contains number

I have a column of addresses and I have to find those which don't contain street numbers. Unfortunately, addresses have been input by various users and they do not follow the same pattern so the street type, street name, suburb are in different order and I can't use functions like LEFT, RIGHT or MID to check if particular character is a number. The column looks like this:
10 Willsons Drive, Manhattan
Epping, 23 Wet Rd
Longsdale St, Kingsbury
11 Link Crt, Pakenham
Is there an Excel or VBA function that can tell me if cell / string contains numbers?
Put this into a Module, then in your worksheet, may be a column next to it, put formula =HaveNumbers(A2) and check if you want it like that (True/False). You can change it to Return a String instead. This Returns TRUE / FALSE.
Function HaveNumbers(oRng As Range) As Boolean
Dim bHaveNumbers As Boolean, i As Long
bHaveNumbers = False
For i = 1 To Len(oRng.Text)
If IsNumeric(Mid(oRng.Text, i, 1)) Then
bHaveNumbers = True
Exit For
End If
Next
HaveNumbers = bHaveNumbers
End Function
There isn't a single VBA function that will do what you want, but the following function should do the trick:
Public Function ContainsNumbers(Inp As String) As Boolean
Dim Strings() As String, Str As Variant
Strings = Split(Inp, " ")
For Each Str In Strings
If IsNumeric(Str) Then
ContainsNumbers = True
Exit For
End If
Next
End Function
Then put something like =ContainsNumbers(A1) in a nearby cell.
Thanks Monty. In my case, though, numbers were not always separated from words so I had to iterate over each character. I used following:
Function ContainsNumber(text As String)
'checks if given cell contains number
For i = 1 To Len(text)
If IsNumeric(Mid$(text, i, 1)) Then
ContainsNumber = True
Exit Function
End If
Next
ContainsNumber = False
End Function