VBA: Set variable to "Empty" or "Missing"? Handling multiple optional arguments? - excel

Is it possible, or desirable, to set objects/data to an "Empty" or "Missing" variant?
I want to be able to conditionally pass optional arguments to a function. Sometimes I want to use an optional argument, sometimes I don't.
In Python, you could easily pass through whichever optional arguments you wanted by using **kwdargs to unpack a dictionary or list into your function arguments. Is there something similar (or a way to hack it in VBA) so you can pass in Empty/Missing optional arguments?
In particular, I'm trying to use Application.Run with an arbitrary number of arguments.
EDIT:
I'm basically trying to do this:
Public Function bob(Optional arg1 = 0, Optional arg2 = 0, Optional arg3 = 0, Optional arg4 = 0)
bob = arg1 + arg2 + arg3 + arg4
End Function
Public Function joe(Optional arg1)
joe = arg1 * 4
End Function
Public Sub RunArbitraryFunctions()
'Run a giant list of arbitrary functions pseudocode
Dim flist(1 To 500)
flist(1) = "bob"
flist(2) = "joe"
flist(3) = "more arbitrary functions of arbitrary names"
flist(N) = ".... and so on"
Dim arglist1(1 To 4) 'arguments for bob
Dim arglist2(1 To 1) 'arguments for joe
Dim arglist3(1 To M number of arguments for each ith function)
For i = 1 To N
'Execute Application.Run,
'making sure the right number of arguments are passed in somehow.
'It'd also be nice if there was a way to automatically unpack arglisti
Application.Run flist(i) arglisti(1), arglisti(2), arglisti(3), ....
Next i
End Sub
Because the number of arguments changes for each function call, what is the acceptable way to make sure the right number of inputs are input into Application.Run?
The equivalent Python code would be
funclist = ['bob', 'joe', 'etc']
arglists = [[1,2,3],[1,2],[1,2,3,4,5], etc]
for args, funcs in zip(arglists, funclist):
func1 = eval(funcs)
output = func1(*args)

in VBA you use ParamArray to enter option inputs to functions.
See Pearson Material

There are two ways in which a routine can change the number of arguments that has to be provided to it:
declare some of the trailing arguments as Optional
declare the last argument as ParamArray
A single routine can use either or both.
An Optional parameter may have a strict type (e.g. Optional s As String), but then it will be impossible to detect whether it was passed. If you don't pass a value for such argument, the correct flavour of "blank" will be used, which is indistinguishable from passing that blank value manually.
So, having Public Sub Bob(Optional S As String), you cannot detect from inside of Bob whether it was called as Bob or as Bob vbNullString.
An optional parameter may have a default value, which suffers from the same problem. So, having Public Sub Bob(Optional S As String = "Default Value"), you cannot detect if Bob was called as Bob or as Bob "Default Value".
To be able to truly detect whether an optional parameter was passed, they have to be typed as Variant. Then a special function, IsMissing, can be used inside the routine to detect if a parameter was passed.
Public Sub Bob(Optional a, Optional b, Optional c, Optional d)
Debug.Print IsMissing(a), IsMissing(b), IsMissing(c), IsMissing(d)
End Sub
Bob 1, , 3 ' Prints False, True, False, True
ParamArray can only be the last argument, and it allows an infinite* number of arguments to be passed starting from this position. All these arguments arrive packed in a single Variant array (no option for static typing here).
The IsMissing function does not work on the ParamArray argument (always returns False). The way to know how many arguments were passed is to compare UBound(args) with LBound(args). Note that this only tells you how many argument "slots" were used, but some of them can be in fact missing!
Public Sub BobArray(ParamArray a())
Dim i As Long
For i = LBound(a) To UBound(a)
Debug.Print IsMissing(a(i)), ;
Next
Debug.Print
End Sub
BobArray ' Prints empty line (the For loop is not entered due to UBound < LBound)
Sheet1.BobArray 1, 2, 3 ' Prints False, False, False
Sheet1.BobArray 1, , 3 ' Prints False, True, False
Note that you cannot pass "missing" value for the trailing arguments of the ParamArray, i.e. this is illegal:
Sheet1.BobArray 1, , 3, ' Does not compile
However, you can work around this using the trick described below.
An interesting use case that you touch in your question is preparing an array of all arguments in advance, passing it to the function, filling all the arguments "placeholders", but still expecting the function to detect that some of the arguments are missing (not passed).
Normally this is not possible, because if anything is passed (even "blank" values, such as Empty, Null, Nothing of vbNullString), then it still counts as passed, and IsMissing() will return False.
Fortunately, the special Missing value is nothing but a specially constructed Variant, and even without knowing how to construct that value manually, we can trick the compiler to give it away:
Public Function GetMissingValue(Optional ByVal IgnoreMe As Variant) As Variant
If IsMissing(IgnoreMe) Then
GetMissingValue = IgnoreMe
Else
Err.Raise 5, , "I told you to ignore me, didn't I"
End If
End Function
Dim missing As Variant
missing = GetMissingValue()
Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = "!"
Bob arglist1(1), arglist1(2), arglist1(3), arglist1(4) ' Prints False, True, True, False
Now, we can work around the inability to pass "missing" to the trailing "slots" of ParamArray:
Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = missing
BobArray arglist1(1), arglist1(2), arglist1(3), arglist1(4) ' Prints False, True, True, True
Note, however, that this workaround will only work if you call BobArray directly. If you use Application.Run, it will not work because the Run method will discard any trailing "missing" arguments before passing them onto the called routine:
Dim arglist1(1 To 4) As Variant
arglist1(1) = 42
arglist1(2) = missing
arglist1(3) = missing
arglist1(4) = missing
Application.Run "BobArray", arglist1(1), arglist1(2), arglist1(3), arglist1(4)
' Prints False, because only one argument is passed

Further to #GSerg's very comprehensive answer (I don't have enough reputation just to comment), the 'special' value assigned to a Missing argument has the 'appearance' of being an Error value - it converts to "Error 448" (Named argument not found) using CStr(), and responds to IsError() as TRUE. However, an attempt to preset the argument using CvErr(448) before passing to a procedure (in the hope that it will be recognised as Missing) fails, perhaps because the value is 'not quite' the same as the Error value in some way.
#GSerg suggested a method of 'recording' the value actually passed by the compiler when an argument is missing and using that to preset a dummy argument prior to passing to the procedure needing to be fooled. This method, indeed, does work and I have simply extended #GSerg's function to replace his error message (if it is inadvertently called with an argument) by a recursive call without an argument which ensures a successful outcome either way. Usage is simply to preset the dummy variable(s) before passing to a procedure (where it/they will then be treated as missing): Dummy_Var = Missing().
Public Function Missing(Optional ByVal X As Variant) As Variant
If IsMissing(X) Then 'correctly called
Missing = X
Else 'bad user call
Missing = Missing() 'recursive call (no arg!)
End If
End Function
I have just done a quick trial with Application.Run. Early embedded 'missing' arguments (ie, followed by 'normal' ones) appear to be successfully registered as 'missing' in the called procedure. So, too, however, are final trailing 'missing' arguments - whether actually passed by the Run method, or truncated (as suggested by #GSerg), but still filled in by the compiler as genuinely missing.
Interestingly, and usefully (to a niche market), additional 'missing' arguments (beyond those defined by the procedure) appear to be tolerated by the compiler without generating the 'Wrong number of arguments' message associated with extra 'normal' arguments. This opens up the possibility of procedure calls using Application.Run (when a variable number of arguments is desired) being implemented by a single universal call (with up to 30 arguments if necessary) padded out with fake 'missing' arguments instead of having to provided several alternative calls of different lengths and/or argument configurations to cope with exact procedure definitions.

So addressing the question of optionally using arguments it looks like my question in Calling vba macro from python with unknown number of arguments, check it out accordingly.
Hence:
Using Python:
def run_vba_macro(str_path, str_modulename, str_macroname, **kwargs):
if os.path.exists(str_path):
xl=win32com.client.DispatchEx("Excel.Application")
wb=xl.Workbooks.Open(str_path, ReadOnly=0)
xl.Visible = True
if kwargs:
params_for_excel = list(kwargs.values())
xl.Application.Run(os.path.basename(str_path)+"!"+str_modulename+'.'+str_macroname,
*params_for_excel,)
else:
xl.Application.Run(os.path.basename(str_path)
+"!"+str_modulename
+'.'+str_macroname)
wb.Close(SaveChanges=0)
xl.Application.Quit()
del xl
#example
kwargs={'str_file':r'blablab'}
run_vba_macro(r'D:\arch_v14.xlsm',
str_modulename="Module1",
str_macroname='macro1',
**kwargs)
#other example
kwargs={'arg1':1,'arg2':2}
run_vba_macro(r'D:\arch_v14.xlsm',
str_modulename="Module1",
str_macroname='macro_other',
**kwargs)
Using VBA:
Sub macro1(ParamArray args() as Variant)
MsgBox("success the str_file argument was passed as =" & args(0))
End Sub
Sub macro_other(ParamArray args() as Variant)
MsgBox("success the arguments have passed as =" & str(args(0)) & " and " & str(args(1)))
End Sub
Also another use case only using VBA is here for reference. It is a question that has not been answered and is around for long, although recently it was updated by the community server automatically with some good ideas related links accordingly.
Here is an answer you can do it if you use this:
Sub pass_one()
Call flexible("a")
End Sub
Sub pass_other()
Call flexible("a", 2)
End Sub
Sub flexible(ParamArray args() As Variant)
Dim i As Long
MsgBox ("I have received " & _
Str(UBound(args) + 1) & _
" parameters.")
For i = 0 To UBound(args)
MsgBox (TypeName(args(i)))
Next i
End Sub
Only for developers that also use Python:
If you are using Python's kwargs, simply starr expression and pass a Python tuple.
Here it is (it is related with my question in Calling vba macro from python with unknown number of arguments)
Cheers.

Related

VBA: How to pass For Loop iterator to sub? [duplicate]

I've just had an irritating 30 minutes on a "compiler error" in VBA (Access 2003) caused by my use of parenthesis around the arguments I'm passing to a Sub I defined.
I've been searching to find a decent article/tutorial/instruction as to when parenthesis are necessary/appropriate/inappropriate/forbidden, but can't find any clear guidelines.
There is perfect logic to the Parentheses Rule in VB(A), and it goes like this.
If a procedure (function or sub) is called with arguments, and the call is on a line with other statements or keywords, the arguments must be enclosed in parentheses. This to distinguish the arguments belonging to the procedure call from the rest of the line. So:
1: If CheckConditions(A, B, C) = DONT_PROCEED Then Exit Sub
is a valid line; the call to CheckConditions needs the parentheses to indicate what other bits of the line are its arguments. Conversely, this would produce a syntax error:
2: If CheckConditions A, B, C = DONT_PROCEED Then Exit Sub
Because it is impossible to parse.
With a procedure call as the only statement on the line, parentheses aren't needed because it is clear that the arguments belong to the procedure call:
3: SaveNewValues Value1, Value2, Value3
While this results in a syntax error (for sound reasons discussed below):
4: SaveNewValues(Value1, Value2, Value3)
To avoid confusion about parentheses or no parentheses (in fact, to avoid the Parentheses Rule entirely), it is always a good idea to use the Call keyword for calls like these; that ensures that the procedure call is not the only statement on the line, thus requiring parentheses:
5: Call SaveNewValues(Value1, Value2, Value3)
So if you get in the habit of preceding self-contained procedure calls with the Call keyword, you can forget the Parentheses Rule, because you can then always enclose your arguments in parentheses.
The matter is confused by the additional role parentheses play in VB(A) (and many other languages): they also indicate evaluation precedence for expressions. If you use parentheses in any other context but to enclose procedure call arguments, VB(A) will attempt to evaluate the expression in the parentheses to a resulting simple value.
Thus, in example 4, where parentheses are illegal for enclosing the arguments, VB(A) will instead attempt to evaluate the expression in the parentheses. Since (Value1, Value 2, Value3) is not an expression that can be evaluated, a syntax error ensues.
This also explains why calls with a variable passed ByRef act as if called ByVal if the argument is enclosed in parentheses. In the example above, where function p is called with ByRef parameter a, there is a big difference between these two calls to p:
6: p a
And
7: p(a)
As discussed above, 6 is the correct syntax: the call is alone on its line, so parentheses should not be used to enclose the arguments.
In 7, the argument is enclosed in parentheses anyway, prompting VB(A) to evaluate the enclosed expression to a simple value. Which of course is the very definition of passing ByVal. The parentheses ensure that instead of a pointer to a, the value of a is passed, and a is left unmodified.
This also explains why the parentheses rule doesn't always seem to hold sway. Clearest example is a MsgBox call:
8: MsgBox "Hello World!"
And
9: MsgBox ("Hello World!")
Are both correct, even though the parentheses rule dictates that 9 should be wrong. It is, of course, but all that happens is that VB(A) evaluates the expression in the parentheses. And the string literal evaluates to the exact same string literal, so that the actual call made is 8. In other words: calls to single-argument procedures with constant or string literal arguments have the identical result with or without parentheses. (This is why even my MsgBox calls are preceded by the Call keyword.)
Finally, this explains odd Type Mismatch errors and weird behavior when passing Object arguments. Let's say your application has a HighlightContent procedure that takes a TextBox as argument (and, you'll never guess, highlights it contents). You call this to select all text in the textbox. You can call this procedure in three syntactically correct ways:
10: HighlightContent txtName
11: HighlightContent (txtName)
12: Call HighlightContent(txtName)
Let's say your user has entered "John" in the textbox and your application calls HighlightContent. What will happen, which call will work?
10 and 12 are correct; the name John will be highlighted in the textbox. But 11 is syntactically correct, but will result in a compile or runtime error. Why? Because the parentheses are out of place. That will prompt VB(A) to attempt an evaluation of the expression in the parentheses. And the result of the evaluation of an object will most often be the value of its default property; .Text, in this case. So calling the procedure like 11 will not pass the TextBox object to the procedure, but a string value "John". Resulting in a Type Mismatch.
From Here:
Using the VBScript Call Statement to Call a Subroutine
The use of Call statement is optional when you wish to call a subroutine. The purpose of the Call statement when used with a Sub is to allow you to enclose the argument list in parentheses. However, if a subroutine does not pass any arguments, then you still should not use parentheses when calling a Sub using the Call statement.
Call MySubroutine
If a subroutine has arguments, you must use parentheses when using the Call statement. If there is more than one argument, you must separate the arguments with commas.
Call MySubroutine(intUsageFee, intTimeInHours, "DevGuru")
Calling the Function
There are two possible ways to call a function. You may either call the function directly, by name only, or you may call it by using the VBScript Call statement.
Calling a Function by Name
When calling a function directly by name and when there is no assignment to a returned value, all of the following are legal syntax:
MyFunction
MyFunction()
MyFunction intUsageFee, intTimeInHours, "DevGuru"
If you want a returned value, you can assign the function to a variable. Note that if there is one or more arguments, you must use the parentheses.
returnval = MyFunction
returnval = MyFunction()
returnval = MyFunction(intUsageFee, intTimeInHours, "DevGuru")
I just found some weird behavior calling a function with / without parentheses. Google took me here.
sub test()
dim a as double
a = 1#
p(a) 'this won't change a's value
Debug.Print a '1
p a ' this is expected behavior
Debug.Print a '2
Call p(a) 'this is also valid
Debug.Print a '3
end sub
Function p(a as Double) 'default is byref
a = a + 1
end function
My conclusion is that you have to use either Call or omitting the parentheses when calling a function with only one parameter, otherwise the parameter isn't passed by reference (it's still get called, as I checked already).
I just spent 10 minutes figuring out an "types incompatible" exception while calling a Sub which takes 1 argument via
CallMe(argument)
As it turns out, this is invalid, googling lead me here and finally
Call CallMe(argument)
or
CallMe argument
did the trick. So you must not use the brackets when calling a sub without the call-statement which only takes 1 argument.
When you use
Call MySub you should use parentheses around parameters, but if you omit Call, you don't need parentheses.
1 - By default, do not use parentheses when calling procedures or functions:
MsgBox "Hello World"
2 - If you are calling a function, and are interested in its result, then you must enclose its arguments with parentheses:
Dim s As String
Dim l As Long
s = "Hello World"
l = Len(s)
3 - If you want to use the call keyword with a procedure, then you must enclose the arguments with parentheses (e.g. when you want to assign the result in a variable or to use the function in an expression):
Call MsgBox("Hello World")
4 - If you want to force a ByRef argument (the default) to be passed ByVal, then enclose the ByRef argument with parentheses:
Sub Test
Dim text As String
text = "Hello World"
ChangeArgument((text))
MsgBox text
End Sub
Sub ChangeArgument(ByRef s As String)
s = "Changed"
End Sub
This displays "Hello World"
Well this was asked long ago but I just faced this problem and I found this question which I feel hasn't been fully answered yet. Hope I shed some light over this issue so that it serves for newcomers.
As I have seen previous answers mainly focus on the fact that whenever you use the "Call" statement you must enclose the arguments within parenthesis. Although this is true 1 it is definitely not the main source triggering this "strange" syntax errors.
The key point has been briefly noted by Cristopher. I'll just reference the documentation and further explain a little.
Ref Docs 2
So the main point is that the parenthesis determine whether you are interested in the return value of the function/sub/method/statement you are calling or not, that is, whether it must be returned to store it on a variable or not.
Having said that one may run into several problems
Calling with parenthesis a procedure that doesn't return a value 3.
Sub no_value_return(x as Integer)
Dim dummy as Integer
dummy = x
End Sub
'Error
no_value_return(1)
'No error
no_value_return 1
Calling with parenthesis a procedure that returns a value but not assigning it to a variable
Function value_return(ByVal x as Integer)
Dim value_return as Integer
value_return = x*2
End Function
'Error:
value_return(1)
'No error
Dim result as Integer
result = value_return(1)
Some additional examples
'Error - No value returned since no parenthesis were specified
Dim result as Integer
result = value_return 1
'No error - Special case
Dim result as Variant
result = value_return 1
'The reason for this is that variant is the only data type that accepts
'the special value "Empty"
'No error - You can perfectly ignore the returned value even if it exists
value_return 1
1 https://learn.microsoft.com/en-us/office/vba/language/concepts/getting-started/calling-sub-and-function-procedures
2 https://learn.microsoft.com/en-us/office/vba/language/concepts/getting-started/using-parentheses-in-code
3 Note this isn't aplicable for function procedures or built-in functions since those must always return a value
I use another logic to differ when to use brackets or not. If you function doesn't return a value (void type in C-liked languages), you don't need the parentheses. And it is always true for subs because returning value is the main difference between sub and function. Otherwise you have to use parentheses.

How does ByRef to ByVal by parentheses work, when the ByRef is a Range with a Worksheet reference?

Apparently, parentheses in VBA function arguments turn ByRefs to ByVals,
And Range Copy, whose only available variable is a Destination Range, needs that destination to be a ByRef?
So, how is this working? Shouldn't the Cell referenced with Worksheet Specified, being a ByRef in parentheses, be converted into a ByVal and cause the Copy to fail?
Range("A1").Copy (Worksheets("Sheet1").Range("E5"))
Apparently, parentheses in VBA function arguments turn ByRefs to ByVals.
Well, no, not exactly.
The parentheses make the parameter into an expression that gets evaluated and then its value is passed on.
You will see this when you call a Sub that has two parameters:
mySub 1, 2 ' this is OK
mySub (1, 2) ' this makes VB complain
The second example makes from (1, 2) an expression; however, the expression is invalid so VB will complain.
If you call a function without using the return value, then do not use parentheses:
myFunction 1, 2 ' this is OK
myFunction (1, 2) ' this is an error
i = myFunction (1, 2) ' here the parentheses are required.
A couple of things:
Copy doesn't care how you pass the destination Range, as long as it's a Range object. So, for example:
Range("A1").Copy ByVal Range("B1")
will work just fine.
The parentheses try to evaluate what's inside them as an expression. So in your first case, what you actually end up passing is the value of the range, not the range itself. The weird part is actually that the evaluation returns the Range object properly when you add the Worksheets() call. This has something to do with late binding as best I can tell because that Worksheets call returns an Object type, not a Worksheet. If you were to use a worksheet code name (e.g. Sheet1.Range("A1")) instead, you'd have the same problem as you had originally. Really need someone like Mathieu to explain the inner workings of that.

VBA: Usage of parentheses for a method

What's the right way to call method when it comes to using or omitting parentheses? If I understand the results of my google search correctly: you have to use parentheses when assigning the return value of a method (or function) to a variable. Below are some examples:
wbData.Sheets.Add '-> works
Set wsData = wbData.Sheets.Add '-> works
wbData.Sheets.Add(Before:=wbData.Sheets(wbData.Sheets.Count)) '-> syntax error
Set wsData = wbData.Sheets.Add(Before:=wbData.Sheets(wbData.Sheets.Count)) '-> works
wbData.Sheets.Add Before:=wbData.Worksheets(wbData.Worksheets.Count) '-> works
Set wsData = wbData.Sheets.Add Before:=wbData.Worksheets(wbData.Worksheets.Count) '-> syntax error
Just to make sure I get the VBA logic: #3 gives me an error because the parentheses to VBA means the value (= the new worksheet) gets returned, but there's no variable to assign it to? And #6 is the opposite case?
Even if my attempt at an explanation were correct, could someone explain to me why the example on the official help page is not working for me:
ActiveWorkbook.Sheets.Add(Before:=Worksheets(Worksheets.Count))
This gives me a syntax error, same as #3 in my list above. At this I'm just confused.
The "official help page" is on GitHub, it's actively maintained, and multiple changes are being merged every day. If there's an error in an example, open an issue for it, or better, submit a fix yourself!
The example is wrong, the parentheses either shouldn't be there, or the expression should be on the right-hand side of a Set assignment to some object variable.
you have to use parentheses when assigning the return value of a method (or function) to a variable
Correct.
When you don't capture the return value, you don't put the parentheses. If you do, the VBE gives you a hint. If you copied the example from the docs, it would look like this in the editor:
ActiveWorkbook.Sheets.Add (Before:=Worksheets(Worksheets.Count))
Note the space. If you captured the return value:
Set newSheet = ActiveWorkbook.Sheets.Add(Before:=Worksheets(Worksheets.Count))
No space.
Just to make sure I get the VBA logic: #3 gives me an error because the parentheses to VBA means the value (= the new worksheet) gets returned, but there's no variable to assign it to? And #6 is the opposite case?
There's more to it than that. Consider a simpler example:
MsgBox "hi", vbOkCancel
If we wanted to capture or otherwise use the return value, we would need the parentheses:
If MsgBox("hi", vbOkCancel) = vbOk Then
If we added parentheses without capturing/using the return value, we would have this:
MsgBox ("hi", vbOkCancel)
So what does this space mean?
To the VBA compiler, it means "this isn't the argument list, it's the first argument, and this is a value expression: evaluate it, and send the result ByVal to the invoked procedure". The problem, of course, is that ("hi", vbOkCancel) isn't an expression, and can't be evaluated, and we have a compile error.
So back to the docs example: Before:=Worksheets(Worksheets.Count) isn't a legal expression either - it's an argument list consisting of one named argument... but syntactically it's not the argument list: parenthesized, it's an expression that, if it could be evaluated, would be passed to the first argument of the parameter list, ByVal - like so:
ActiveWorkbook.Sheets.Add Argument1:=(the result of the expression)
The ByVal nature of the parenthesized argument is basically an accident: when VBA evaluates the expression, it gets a value... but that value is up in the air, there's no local reference to it - so even though the invoked procedure is accepting a ByRef argument, since the caller isn't holding a reference to that argument, it's discarded - effectively producing the exacty same result as if the function took the parameter ByVal.
Confusing? This should help:
Public Sub Test()
Dim foo As Long
DoSomething (foo) ' evaluates the expression, passes the result of that expression
Debug.Print foo ' prints 0
DoSomething foo ' passes a reference to the local variable
Debug.Print foo ' prints 42
End Sub
Private Sub DoSomething(ByRef value As Long)
value = 42
End Sub
Does the method return a value you need? Use parentheses (but optional if not passing any arguments to the method unless you're using the return value in the same line).
Eg - below RowRange returns a Range object, but you can't then index directly into it using (2,1), since that is interpreted as passing arguments to RowRange (which doesn't take any)
s = myPivotTable.RowRange(2, 1).Value 'fails with "too many parameters"
Adding the parentheses clean this up:
s = myPivotTable.RowRange()(2, 1).Value 'OK
Using Call ? Use parentheses. But Call is typically considered as deprecated.
Anything else? Parentheses not required, and may produce unexpected outcomes by causing arguments to be evaluated before being passed.
One thing to watch out for is when the Vb editor puts a space between the method name and the opening parenthesis - when that happens it's a sign you might not need the parentheses at all.

VBA Calling function with / without Parenthesis [duplicate]

I am getting the 800A0414 error in lines 7 and 12 of this script:
Module Module1
Dim p
Sub Main()
CreateObject("Wscript.Shell").Run("program.bat", 0, True)
p = Process.GetProcessesByName("program")
If p.Count > 0 Then
WScript.Sleep(300000)
Else
CreateObject("Wscript.Shell").Run("program clean up.bat", 0, True)
End If
End Sub
Private Function WScript() As Object
Throw New NotImplementedException
End Function
End Module
I am trying to run a batch script, that starts a process, then wait until the process terminates, then run another batch script. I also do not want any command boxes being shown. If their is a easier way please let me know.
Thanks for your help
When you enclose a procedure's argument list in parentheses, you must use the Call keyword:
Call CreateObject("WScript.Shell").Run("program.bat", 0, True)
If you omit the Call keyword, you must also drop parentheses:
CreateObject("WScript.Shell").Run "program.bat", 0, True
To complete what's been said before:
When Call keyword is used to call a procedure (i.e. sub or function) the arguments must be enclosed in parentheses, except when the procedure has no arguments in which case the parentheses are optional. For example all the statements:
Call test()
Call test
Call test(1,2)
are valid, but not this one:
Call test 1
When calling a procedure without using the Call keyword, the parentheses can only be used when either the procedure has zero or one argument or the procedure has a return value (i.e. is a function) and its value is used in the same statement. For example all the statements:
test()
test(1)
test(1,2)
a = test
a = test(1,2)
a = test(test(1,2),2)
are valid, except the third one which has more than one argument. In case it's not clear, the inner call of "test" in the last statement is valid because its return value is used as an argument to another call.
Note that whenever parentheses is used in this text, it is meant to imply the possible comma-separated values as well.
Seems to me this is a VB.NET, not VBScript code.
You have Shell function in VB.NET (and other methods).
Anyway, Run returns any error code returned by the program, and if you
store that result in a variable, you can use parentheses in this case.
Dim lResult As Long
lResult = CreateObject("Wscript.Shell").Run("program.bat", 0, True)
The rest was answered by #Helen.

Function Overloading and UDF in Excel VBA

I'm using Excel VBA to a write a UDF. I would like to overload my own UDF with a couple of different versions so that different arguments will call different functions.
As VBA doesn't seem to support this, could anyone suggest a good, non-messy way of achieving the same goal? Should I be using Optional arguments or is there a better way?
Declare your arguments as Optional Variants, then you can test to see if they're missing using IsMissing() or check their type using TypeName(), as shown in the following example:
Public Function Foo(Optional v As Variant) As Variant
If IsMissing(v) Then
Foo = "Missing argument"
ElseIf TypeName(v) = "String" Then
Foo = v & " plus one"
Else
Foo = v + 1
End If
End Function
This can be called from a worksheet as =FOO(), =FOO(number), or =FOO("string").
If you can distinguish by parameter count, then something like this would work:
Public Function Morph(ParamArray Args())
Select Case UBound(Args)
Case -1 '' nothing supplied
Morph = Morph_NoParams()
Case 0
Morph = Morph_One_Param(Args(0))
Case 1
Morph = Two_Param_Morph(Args(0), Args(1))
Case Else
Morph = CVErr(xlErrRef)
End Select
End Function
Private Function Morph_NoParams()
Morph_NoParams = "I'm parameterless"
End Function
Private Function Morph_One_Param(arg)
Morph_One_Param = "I has a parameter, it's " & arg
End Function
Private Function Two_Param_Morph(arg0, arg1)
Two_Param_Morph = "I is in 2-params and they is " & arg0 & "," & arg1
End Function
If the only way to distinguish the function is by types, then you're effectively going to have to do what C++ and other languages with overridden functions do, which is to call by signature. I'd suggest making the call look something like this:
Public Function MorphBySig(ParamArray args())
Dim sig As String
Dim idx As Long
Dim MorphInstance As MorphClass
For idx = LBound(args) To UBound(args)
sig = sig & TypeName(args(idx))
Next
Set MorphInstance = New MorphClass
MorphBySig = CallByName(MorphInstance, "Morph_" & sig, VbMethod, args)
End Function
and creating a class with a number of methods that match the signatures you expect. You'll probably need some error-handling though, and be warned that the types that are recognizable are limited: dates are TypeName Double, for example.
VBA is messy. I'm not sure there is an easy way to do fake overloads:
In the past I've either used lots of Optionals, or used varied functions. For instance
Foo_DescriptiveName1()
Foo_DescriptiveName2()
I'd say go with Optional arguments that have sensible defaults unless the argument list is going to get stupid, then create separate functions to call for your cases.
You mighta also want to consider using a variant data type for your arguments list and then figure out what's what type using the TypeOf statement, and then call the appropriate functions when you figure out what's what...

Resources