I just figured out that setting optional parameters requires "Call" infront of the method.
Public Sub Test()
Call abc("aaa")
Call abc("aaa", 2)
abc("aaa") ' is fine
abc("aaa", 2) ' is a syntax error
End Sub
Function abc(a As String, Optional iCol As Long = 3)
MsgBox (iCol)
End Function
Can you add a "why does this make sense?" to my new information?
Greetings,
Peter
Edit: PS the function abc for no other use than to simplify the question.
Documentation
Call is an optional keyword, but the one caveat is that if you use it you must include the parentheses around the arguments, but if you omit it you must not include the parentheses.
Quote from MSDN:
You are not required to use the Call keyword when calling a procedure.
However, if you use the Call keyword to call a procedure that requires arguments, argumentlist must be enclosed in parentheses. If you omit the Call keyword, you also must omit the parentheses around argumentlist. If you use either Call syntax to call any intrinsic or user-defined function, the function's return value is discarded.
To pass a whole array to a procedure, use the array name followed by empty parentheses.
Link: https://msdn.microsoft.com/en-us/library/office/gg251710.aspx
In Practice
This means that the following syntaxes are allowed:
Call abc("aaa")
Call abc("aaa", 2)
abc "aaa", 2
abc("aaa") ' <- Parantheses here do not create an argument list
abc(((("aaa")))) ' <- Parantheses here do not create an argument list
The following syntaxes are not allowed:
Call abc "aaa", 2
abc("aaa", 2) ' <- Parantheses here create an argument list
Function Return Values
This doesn't take effect when using a function to get a return value, for example if you were to do the following you need parentheses:
Function abc(a As String, Optional iCol As Long = 3)
abc = iCol
End Function
'## IMMEDIATE WINDOW ##
?abc("aaa", 2) 'this works
?abc "aaa, 2 'this will not work
?Call abc "aaa", 2 'this will not work
?Call abc("aaa", 2) 'this will not work
If you are using Call on a Function then consider changing it to a Sub instead, functions are meant to return a value as in the cases above.
Something like this would work:
Option Explicit
Public Sub Test()
Dim strText As String
strText = "E"
Call Abc(strText, 3)
Call Abc(strText, 2)
Abc (strText)
' Abc (strtext,5)
Abc strText, 2
Abc strText
End Sub
Public Sub Abc(strText As String, Optional iCol As Long = 5)
Debug.Print iCol
End Sub
Absolutely no idea why the commented code does not work...
Call is an optional keyword, as already described in detailed in the answer above.
for your second option
abc("aaa", 2) ' is a syntax error
Just use:
abc "aaa", 2
Note: there is little use to have a Function if you don't return anything, you could have a regular Sub instead.
to have a Function that return a String for instance (just something made-up quick):
Function abc(a As String, Optional iCol As Long = 3) As String
abc = a & CStr(iCol)
End Function
and then call it:
Public Sub Test()
d = abc("aaa", 2)
MsgBox d
End Sub
Related
I have a function which updates a form, "LoadingInterface". The function looks like this:
Private Sub updateLoadingBar(Optional tekst As String, Optional barOnePerc As Long, Optional barTwoPerc As Long)
If Not IsMissing(tekst) Then
LoadingInterface.Label1.Caption = tekst
End If
If Not IsMissing(barOnePerc) Then
LoadingInterface.Bar.Width = barOnePerc * 1.68
LoadingInterface.prosent.Caption = barOnePerc & "%"
LoadingInterface.prosent.Left = barOnePerc * 1.68 / 2 - 6
End If
If Not IsMissing(barTwoPerc) Then
LoadingInterface.SubBar.Width = barTwoPerc * 1.68
End If
LoadingInterface.Repaint
End Sub
I then call the function like this, expecting it to only update the textfield, since the other two arguments are missing.
Call updateLoadingBar(tekst:="Test")
This works fine for updating Label1, but unfortunately the other two values are updated too - it seems that not including any values in the function-call makes VBA assume the two variables values are 0. What's more, it appears that the IsMissing function does not detect that the two values are missing when the function is called, which is the bigger problem. Stepping through the code using F8 confirms that all the if-statements are indeed entered.
Is there any way to make the code skip the two lowermost if-statements in my function, if no values are provided for the parameters barOnePerc and barTwoPerc?
IsMissing only works if the argument is declared as a Variant.
I don't think you can validly distinguish between 0 and no passed parameter for the Long. In that case you would need to declare as Variant in the signature. You can later cast if required.
I guess you could put a default (unlikely number) and test for that. Note: I wouldn't advise this. This just screams "Bug".
IsMissing:
IsMissing returns a Boolean value indicating whether an optional Variant argument has been passed to a procedure.
Syntax: IsMissing(argname)
The required argname argument contains the name of an optional Variant
procedure argument.
Remarks: Use the IsMissing function to detect
whether or not optional Variant arguments have been provided in
calling a procedure. IsMissing returns True if no value has been
passed for the specified argument; otherwise, it returns False.
Both methods:
Option Explicit
Public Sub Test()
RetVal
RetVal2
End Sub
Public Function RetVal(Optional ByVal num As Long = 1000000) As Long
If num = 1000000 Then
MsgBox "No value passed"
RetVal = num
Else
MsgBox "Value passed " & num
RetVal = num
End If
End Function
Public Function RetVal2(Optional ByVal num As Variant) As Long
If IsMissing(num) Then
MsgBox "No value passed"
Else
MsgBox "Value passed " & num
RetVal2 = CLng(num)
End If
End Function
I would like to be able to call application functions such as left() and right() using a string variable.
The reason is that my current code works fine, but has multiple instances of left() and right() that may need to change every time I run it. I'd like to be able to only change it once ("globally") every time.
After googling, I tried CallByName and Application.Run. It seems that they only work with a custom class/macro. Is this true? Is there anything else I should look into? I don't need specific code, thank you!
You can build a custom function where you pass if you want Left or Right.
Option Explicit
Sub Test()
Debug.Print LeftRight("L", "StackOverflow", 5)
Debug.Print LeftRight("R", "StackOverflow", 8)
End Sub
Function LeftRight(sWhich As String, sValue As String, iLength As Integer) As String
Select Case sWhich
Case "L": LeftRight = Left(sValue, iLength)
Case "R": LeftRight = Right(sValue, iLength)
End Select
End Function
You just use "L" or "R" as needed. Change it once and pass as sWhich each time.
You can even use a cell reference for this and update the cell before running code.
Results
Stack
Overflow
The easiest way around this is to replace all your Left and Right calls with a generic function, e.g. instead of
x = Left("abc", 2)
say
x = LeftOrRight("abc", 2)
and then have a function
Function LeftOrRight(str As Variant, len As Long) As Variant
'Uncomment this line for Left
LeftOrRight = Left(str, len)
'Uncomment this line for Right
LeftOrRight = Right(str, len)
End Function
Then you can just change the one function as required.
You could also use SWITCH to implement Scott's idea (no error handling if string length is invalid):
Sub Test()
Debug.Print LeftRight("L", "StackOverflow", 5)
Debug.Print LeftRight("R", "StackOverflow", 8)
End Sub
Function LeftRight(sWhich As String, sValue As String, iLength As Integer) As String
LeftRight = Switch(sWhich = "L", Left$(sValue, iLength), sWhich = "R", Right$(sValue, iLength))
End Function
Looking over vba arrays and stumbled upon something and need someone to clear it up.
Sub AAATest()
Dim StaticArray(1 To 3) As Long
Dim N As Long
StaticArray(1) = 1
StaticArray(2) = 2
StaticArray(3) = 3
PopulatePassedArray Arr:=StaticArray
For N = LBound(StaticArray) To UBound(StaticArray)
Debug.Print StaticArray(N)
Next N
End Sub
AND
Sub PopulatePassedArray(ByRef Arr() As Long)
''''''''''''''''''''''''''''''''''''
' PopulatePassedArray
' This puts some values in Arr.
''''''''''''''''''''''''''''''''''''
Dim N As Long
For N = LBound(Arr) To UBound(Arr)
Arr(N) = N * 10
Next N
End Sub
What's happening at
PopulatePassedArray Arr:=StaticArray
in AAATest sub
There are two ways you can pass arguments to another procedure: using named arguments or in order. When you pass them in order, you must past them in the same order as the procedure definition.
Function DoTheThing(arg1 As Double, arg2 As String, arg3 As Boolean) As Double
When you call this function (in order), you call it like
x = DoTheThing(.01, "SomeString", TRUE)
When you call the function using named arguments, you use :=, the name of the argument, and the value of the argument. The := is not a special assignment operator - well I guess it kind of is. The upshot is that when you use named arguments, you can supply them in any order.
x = DoTheThing(arg2:="SomeString", arg3:=TRUE, arg1:=.01)
Some people also think that named arguments make your code more readable. I'm not one of those people. It clutters it up and if you're passing more than two or three arguments, you're doing it wrong anyway.
I would like to be able to only pass q as argument to a function, so that the user does not have to enter a string "q".
I have a function defined in a module
Function doThis(val As Variant)
MsgBox CStr(val)
' Here is a comparison of val with other strings and additional code
End
I call it from my worksheet:
=doThis(q)
And the messagebox returns
Error 2029
I have tried with String and Boolean as value type as well, but only variant fires the function.
Is it possible to receive a q as argument?
Quite simple. First create a Defined Name for q
Secondly in a standard module:
Function doThis(val As Variant)
MsgBox CStr(val)
doThis = ""
End Function
Finally in the worksheet:
Using VBA with Access 2010, I have a sub:
Public Sub setInterest(account As String, dmonth As Integer)
...somecode...
End Sub
And I am calling it with
setInterest("myAccount",3)
And I get syntax errors.
Modifying the sub to only take one argument and leaving out the 3 gives no errors, the problem is only when I have 2 arguments.
When using multiple arguments, you can either write:
setInterest "myAccount", 3
Or
Call setInterest("myAccount", 3)
In both examples you can name the arguments:
setInterest account:="myAccount", dmonth:= 3
I add this answer, for Why your syntax works with one argument ?
Public Sub setInterest(account As String)
'...somecode...
End Sub
setInterest ("myAccount")
Note :
When there is not any , between ( and ), VBA thinks it's a formula and exactly one argument.
When formula calculate the result will be like this:
Dim str As String
str = ("TEST")
Debug.Print str
[Output:]
TEST