Subroutine will not compile - vba

I'm using Access VBA, and I keep getting
Compile error: Argument not optional
whenever I try to pass a collection into a function. What is going on?
Private Sub btnTest_Click()
Dim GarbageLanguages As New Collection
GarbageLanguages.Add "VBA"
PrintCollectionCount (GarbageLanguages) '<-- error happens here
End Sub
Public Sub PrintCollectionCount(c As Collection)
Debug.Print c.Count
End Sub

Short Answer
Remove the parentheses from the following line:
PrintCollectionCount (GarbageLanguages)
Long Answer
For better or worse (mostly worse), VBA has both functions and subroutines:
Function - expression that must return a value
Subroutine - statement that cannot return a value
Unfortunately, using each of them requires slightly different syntax. Suprisingly, this is not a valid subroutine call:
Subroutine(arguments)
Instead, you need to use one of these two options:
Call Subroutine(arguments)
Subroutine arguments
It's even more unfortunate that when you use the wrong syntax, all you get is extremely cryptic error messages. Finally, it's also hard to get used to not using parenthesis because single arguments that are primitive types instead of objects actually work fine:
Subroutine(SomeString) ' works
Subroutine(SomeInteger) ' works
Subroutine(SomeObject) ' does not work
Subroutine(SomeString, SomeInteger) ' does not work
Aside from memorizing the awful error messages, you can try to train yourself to look out for whenever a space gets automatically inserted after the subroutine's name. This:
Subroutine(argument)
gets changed to this:
Subroutine (argument) '<-- RED FLAG

Related

Is it possible to work with return types of Windows API function calls in Visual Basic? 3 questions [duplicate]

I've not seen this in other languages but I see it a lot in VBA (which I just started working with). Suppose you have a table in Word and wish to set the rows to a certain height. If you do this
tbl.Rows.SetHeight InchesToPoints(1), wdRowHeightExactly
the table's rows indeed are set to 72 points or 1 inch in height. However, if you surround the arguments in parentheses, something I did instinctively, VBA gives an error -- expected:=.
I can solve this by using a throw-away variable, like this
x = tbl.Rows.SetHeight (InchesToPoints(1), wdRowHeightExactly)
or, of course, I can simply not surround the arguments in parentheses.
Microsoft's documentation on the SetHeight method doesn't mention any return value, but in any case, this behavior is extensive throughout VBA. It's not specific to the SetHeight method.
My questions: What is this called? Should I use a throw-away variable or throw away the parentheses? What's the logic from Microsoft's point of view? Are there consequences to using one or the other, consequences I can't imagine (because they are unknown unknowns)?
Definitely don't introduce a "throw-away variable", especially if it's not declared, and especially if what you're invoking is a Sub, a procedure that doesn't return any value. Well you can, if you don't mind a compile-time error:
Expected Function or variable.
Now...
this behavior is extensive throughout VBA. It's not specific to the SetHeight method.
#Yoe3k put it nicely:
As for what it is called, I would guess "correct syntax" is the most appropriate word.
That's the whole answer: it's not about SetHeight, it's about how VBA's implicit procedure/member call syntax works. The explicit Call syntax has been obsolete since the wonderful advent of implicit calls, about a quarter of a century ago. So splattering Call keywords left & right and all over your code will, indeed, keep you the parentheses... if you hold them so dear.
But the "logic" of the implicit call syntax isn't all that complicated, really.
What follows is what I wrote on Documentation.SO about VBA and parentheses, hope it helps.
This is confusing. Why not just always use parentheses?
Parentheses are used to enclose the arguments of function calls. Using them for procedure calls can cause unexpected problems.
Because they can introduce bugs, both at run-time by passing a possibly unintended value to the procedure, and at compile-time by simply being invalid syntax.
Run-time
Redundant parentheses can introduce bugs. Given a procedure that takes an object reference as a parameter...
Sub DoSomething(ByRef target As Range)
End Sub
...and called with parentheses:
DoSomething (Application.ActiveCell) 'raises an error at runtime
This will raise an "Object Required" runtime error #424. Other errors are possible in other circumstances: here the Application.ActiveCell Range object reference is being evaluated and passed by value regardless of the procedure's signature specifying that target would be passed ByRef. The actual value passed ByVal to DoSomething in the above snippet, is Application.ActiveCell.Value.
Parentheses force VBA to evaluate the value of the bracketed expression, and pass the result ByVal to the called procedure. When the type of the evaluated result mismatches the procedure's expected type and cannot be implicitly converted, a runtime error is raised.
Compile-time
This code will fail to compile:
MsgBox ("Invalid Code!", vbCritical)
Because the expression ("Invalid Code!", vbCritical) cannot be evaluated to a value.
This would compile and work:
MsgBox ("Invalid Code!"), (vbCritical)
But would definitely look silly. Avoid redundant parentheses.
The most serious consequence of using parentheses inappropriately can best be demonstrated by code such as:
Sub Test()
Dim r As Range
Set r = Range("A1")
TestSub r
TestSub (r)
End Sub
Sub TestSub(parm As Range)
MsgBox parm.Address
End Sub
In that code TestSub r correctly passes a range object to TestSub. However, placing parentheses around the r, i.e. TestSub (r) causes VBA to evaluate r using its Value property and is therefore equivalent to TestSub r.Value. This then gives an error as it is passing a Variant (maybe a Variant/Double with a value of 123.45) to a subroutine that is expecting a Range.
It is basically just incorrect syntax to enclose parameters to a Subroutine within parentheses. They should only be used when a Function is returning a value.
P.S. I apologise that my example is Excel VBA. I hadn't noticed that the question was Word VBA, plus I could knock up an Excel example quicker than I could research Word VBA enough to give an example in it. The principle is the same in both though.

What is behind this difference in parentheses effect in VBA?

I've not seen this in other languages but I see it a lot in VBA (which I just started working with). Suppose you have a table in Word and wish to set the rows to a certain height. If you do this
tbl.Rows.SetHeight InchesToPoints(1), wdRowHeightExactly
the table's rows indeed are set to 72 points or 1 inch in height. However, if you surround the arguments in parentheses, something I did instinctively, VBA gives an error -- expected:=.
I can solve this by using a throw-away variable, like this
x = tbl.Rows.SetHeight (InchesToPoints(1), wdRowHeightExactly)
or, of course, I can simply not surround the arguments in parentheses.
Microsoft's documentation on the SetHeight method doesn't mention any return value, but in any case, this behavior is extensive throughout VBA. It's not specific to the SetHeight method.
My questions: What is this called? Should I use a throw-away variable or throw away the parentheses? What's the logic from Microsoft's point of view? Are there consequences to using one or the other, consequences I can't imagine (because they are unknown unknowns)?
Definitely don't introduce a "throw-away variable", especially if it's not declared, and especially if what you're invoking is a Sub, a procedure that doesn't return any value. Well you can, if you don't mind a compile-time error:
Expected Function or variable.
Now...
this behavior is extensive throughout VBA. It's not specific to the SetHeight method.
#Yoe3k put it nicely:
As for what it is called, I would guess "correct syntax" is the most appropriate word.
That's the whole answer: it's not about SetHeight, it's about how VBA's implicit procedure/member call syntax works. The explicit Call syntax has been obsolete since the wonderful advent of implicit calls, about a quarter of a century ago. So splattering Call keywords left & right and all over your code will, indeed, keep you the parentheses... if you hold them so dear.
But the "logic" of the implicit call syntax isn't all that complicated, really.
What follows is what I wrote on Documentation.SO about VBA and parentheses, hope it helps.
This is confusing. Why not just always use parentheses?
Parentheses are used to enclose the arguments of function calls. Using them for procedure calls can cause unexpected problems.
Because they can introduce bugs, both at run-time by passing a possibly unintended value to the procedure, and at compile-time by simply being invalid syntax.
Run-time
Redundant parentheses can introduce bugs. Given a procedure that takes an object reference as a parameter...
Sub DoSomething(ByRef target As Range)
End Sub
...and called with parentheses:
DoSomething (Application.ActiveCell) 'raises an error at runtime
This will raise an "Object Required" runtime error #424. Other errors are possible in other circumstances: here the Application.ActiveCell Range object reference is being evaluated and passed by value regardless of the procedure's signature specifying that target would be passed ByRef. The actual value passed ByVal to DoSomething in the above snippet, is Application.ActiveCell.Value.
Parentheses force VBA to evaluate the value of the bracketed expression, and pass the result ByVal to the called procedure. When the type of the evaluated result mismatches the procedure's expected type and cannot be implicitly converted, a runtime error is raised.
Compile-time
This code will fail to compile:
MsgBox ("Invalid Code!", vbCritical)
Because the expression ("Invalid Code!", vbCritical) cannot be evaluated to a value.
This would compile and work:
MsgBox ("Invalid Code!"), (vbCritical)
But would definitely look silly. Avoid redundant parentheses.
The most serious consequence of using parentheses inappropriately can best be demonstrated by code such as:
Sub Test()
Dim r As Range
Set r = Range("A1")
TestSub r
TestSub (r)
End Sub
Sub TestSub(parm As Range)
MsgBox parm.Address
End Sub
In that code TestSub r correctly passes a range object to TestSub. However, placing parentheses around the r, i.e. TestSub (r) causes VBA to evaluate r using its Value property and is therefore equivalent to TestSub r.Value. This then gives an error as it is passing a Variant (maybe a Variant/Double with a value of 123.45) to a subroutine that is expecting a Range.
It is basically just incorrect syntax to enclose parameters to a Subroutine within parentheses. They should only be used when a Function is returning a value.
P.S. I apologise that my example is Excel VBA. I hadn't noticed that the question was Word VBA, plus I could knock up an Excel example quicker than I could research Word VBA enough to give an example in it. The principle is the same in both though.

How do I pass a range obj variable to a sub in Excel VBA (2016) [duplicate]

This question already has an answer here:
Array argument must be ByRef
(1 answer)
Closed 6 years ago.
Given the following code:
I can not seem to successfully pass a Range Object Variable from one sub-function to another. I spent an entire day researching, and experimenting before I swallowed pride and came here.
Please read the comments below, and reply with any ideas you have regarding why the LAST two lines will not behave.
Public Sub doSomethingToRows(ROI As Range)
*'do Something with the cell values within the supplied range*
End Sub
'
Public Sub testDoAltRows()
Dim RegionOfInterest As Range 'is this an object or not?
'*The following yields: Class doesn't support Automation (Error 430)*
'*Set RegionOfInterest = New Worksheet 'this just gives an error*
Set RegionOfInterest = Worksheets("Sheet1").Range("A1")
RegionOfInterest.Value = 1234.56 '*okay, updates cell A1*
Set RegionOfInterest = Worksheets("Sheet1").Range("B5:D15")
RegionOfInterest.Columns(2).Value = "~~~~~~" '*okay*
'doSomethingToRows (RegionOfInterest) 'why do I get "OBJECT IS REQUIRED" error?
doSomethingToRows (Worksheets("Sheet1").Range("B5:C15")) 'but this executes okay
End Sub
From the msdn documentation of the Call keyword statement,
Remarks
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.
From a practical standpoint, even though Subs can be called with or without the "Call" keyword, it makes sense to pick one way and stick with it as part of your coding style. I agree with Comintern - it is my opinion, based on observation of modern VBA code, that using the "Call" keyword should be considered deprecated. Instead, invoke Subs without parenthesis around the argument list.
And now the answer to the important question:
Why does your code throw an error?
Take for example the following Subroutine:
Public Sub ShowSum(arg1 As Long, arg2 As Long)
MsgBox arg1 + arg2
End Sub
We have established that, if not using the Call keyword, Subs must be invoked like so:
ShowSum 45, 37
What happens if it were instead called like ShowSum(45, 37)? Well, you wouldn't even be able to compile as VBA immediately complains "Expected =". This is because the VBA parser sees the parenthesis and decides that this must be a Function call, and it therefore expects you to be handling the return value with an "=" assignment statement.
What about a Sub with only one argument? For example:
Public Sub ShowNum(arg1 As Long)
MsgBox arg1
End Sub
The correct way to call this Sub is ShowNum 45. But what if you typed this into the VBA IDE: ShowNum(45)? As soon as you move the cursor off of the line, you'll notice that VBA adds a space between the Sub name and the opening parenthesis, giving you a crucial clue as to how the line of code is actually being interpreted:
ShowNum (45)
VBA is not treating those parenthesis as if they surrounded the argument list - it is instead treating them as grouping parenthesis. MOST of the time, this wouldn't matter, but it does in the case of Objects which have a default member.
To see the problem this causes, try running the following:
Dim v As Variant
Set v = Range("A1")
Set v = (Range("A1")) '<--- type mismatch here
Notice that you get a "Type Mismatch" on the marked line. Now add those two statements to the watch window and look at the "Type" column:
+-------------+-----+--------------+
| Expression |Value| Type |
+-------------+-----+--------------+
|Range("A1") | |Object/Range |
|(Range("A1"))| |Variant/String|
+-------------+-----+--------------+
When you surround an Object with grouping parenthesis, its default property is evaluated - in the case of the Range object, it is the Value property.
So it's really just a coincidence that VBA allowed you to get away with "putting parenthesis around the argumentlist" - really, VBA just interprets this as grouping parenthesis and evaluates the value accordingly. You can see by trying the same thing on a Sub with multiple parameters that it is invalid in VBA to invoke a Sub with parenthesis around the argument list.
#PaulG
Try this:
Public Sub Main()
Debug.Print TypeName(Range("A1"))
Debug.Print TypeName((Range("A1")))
End Sub
okay, I knew after I posted this question I'd be struck by lighting and receive an answer.
When passing an object VARIABLE to a sub-function and wishing to use parentheses "()", one must use CALL! Thus the correction to my code sample is:
**CALL doSomethingToRows(RegionOfInterest)**
Thank you!
Maybe we're talking about different things, but here's an example to make it a bit clearer what I mean.
Option Explicit
Sub TestDisplay()
Dim r As Range
'Create some range object
Set r = Range("A1")
'Invoke with Call.
Call DisplaySomething(r)
'Invoke without Call.
DisplaySomething r
End Sub
Sub DisplaySomething(ByVal Data As Range)
Debug.Print "Hi my type is " & TypeName(Data)
End Sub
Both calls work perfectly. One with Call and the other without.
Edit:
#Conintern. Thanks for explaining that. I see what is meant now.
However, I still respectively disagree.
If I declare the following:
Function DisplaySomething(ByVal Data As String)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
and invoke it:
Debug.print DisplaySomething(Range("A1"))
I believe that Excel has been clever and converted to a string. It can do that by invoking the Default Parameter and can convert to a string.
However, as in the original parameter example, If I declare the following:
Function DisplaySomething(ByVal Data As Range)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
There is no call on the Default Parameter, however it is called, because Excel was able to resolve it to that type.
Function DisplaySomething(ByVal Data As Double)
DisplaySomething = "Hi my type is " & TypeName(Data)
End Function
will return a double because it was able to coerce to a double.
Indeed in those examples the Default was called.
But in this example we are defining as Range. No Default called there however it is invoked - brackets or no brackets.
I believe this is more to do with Excel and data coercion.
Similar to the following:
Public Function Test(ByVal i As String) As Integer
Test = i
End Function
and invoking with:
Debug.print Test("1")
BTW, yes I know this isn't an object without a Default parmeter. Im pointing out data coercion. Excel does its best to resolve it.
Could be wrong mind you...

Presence of parentheses suddenly causes code to break

I had a line of VBA code that basically looked like this:
MyControls.Add(Factory.CreateMyControl(param1, param2))
Where Factory.CreateMyControl is just a sneaky way to allow the new instance of my class module being returned to have a constructor.
It was working without any issues for several weeks. Suddenly, it begins throwing the error object doesn't support this property or method which is baffling me because everything looks like it always has.
After stepping into and through the code, I finally narrowed it down to the line above, and found the issue. The issue was the pair of parentheses surrounding the parameter(s) for the Add function. When I changed the code to the following:
MyControls.Add Factory.CreateMyControl(param1, param2)
It worked just as it always had before the unexpected break.
I now understand that this is the basic syntax in VBA for calling Sub's with parameters: to simply include all parameters in a comma-separated fashion without any parentheses (unless you're setting a Function's return value to another variable or using it's value for some other purpose).
My real question is, why did this suddenly just stop working?
Is it a common occurrence using VBA in Office 2007 for code that once worked to break without warning?
Or could this have been caused by some kind of patch that happened without my knowledge?
With parentheses around, the Sub's parameters per default were passed ByVal instead of ByRef. Without parentheses around, the default is ByRef
Example:
Dim r As Range
Sub test(v As Variant)
MsgBox TypeName(v)
End Sub
Sub main()
Set r = Range("A1")
test r 'ByRef; TypeName is Range
test (r) 'ByVal; TypeName is not Range but the type of the content of A1
End Sub

Should I use Call keyword in VB/VBA?

I use the Call keyword when calling subs in VB/VBA. I know it's optional, but is it better to use it or leave it off? I've always thought it was more explicit, but maybe it's just noise.
Also, I read this on another forum: Using the Call keyword is faster because it knows that it is not going to return any values, so it doesn't need to set up any stackspace to make room for the return value.
Ah ha. I have long wondered about this and even reading a two inch thick book on VBA basically says don't use it unless you want to use the Find feature of the VBE to easily find calls in large projects.
But I just found another use.
We know that it's possible to concatenate lines of code with the colon character, for example:
Function Test(mode as Boolean)
if mode = True then x = x + 1 : Exit Sub
y = y - 1
End Sub
But if you do this with procedure calls at the beginning of a line, the VBE assumes that you're referring to a label and removes any indents, aligning the line to the left margin (even though the procedure is called as intended):
Function Test()
Function1 : Function2
End Function
Using the Call statement allows concatenation of procedure calls while maintaining your code indents:
Function Test()
Call Function1 : Call Function2
End Function
If you don't use the Call statement in the above example, the VBE will assume that "Function1" is an label and left align it in the code window, even though it won't cause an error.
For VB6, if there is any chance it will be converted to VB.NET, using Call means the syntax doesn't change. (Parentheses are required in VB.NET for method calls.) (I don't personally think this is worth the bother -- any .NET converter will at least be able to put in parentheses when required. I'm just listing it as a reason.)
Otherwise it is just syntactic sugar.
Note the Call keyword is likely not to be faster when calling some other method/function because a function returns its value anyway, and VB didn't need to create a local variable to receive it, even when Call is not used.
I always use Call in VBA. To me, it just looks cleaner. But, I agree, it's just syntactic sugar, which puts it squarely the realm of personal preference. I've come across probably a dozen full time VBA guys in the past few years, and not one of them used Call. This had the added advantage that I always knew which code was mine. :p
No, it'll just add 7 characters per call with no given benefit.
No one covered this important distinction: in some (common) situations, Call prevents parentheses around function (and sub) arguments from causing the arguments to be strictly interpreted as ByVal.
The big takeaway for you is that if you DO use parentheses around arguments to a routine, perhaps by rote or habit, even though they are not required, then you SHOULD USE Call to ensure that the routine's implicit or explicit ByRef is not disregarded in favor of ByVal; or, instead, you should use an "equal sign" assignment of the return value to prevent the disregard (in which case you would not use Call).
Again, that is to protect you from unfavorably getting ByVal from a routine. Conversely, of course, if you WANT ByVal interpretation regardless of the routine's declaration, then LEAVE OFF the Call (and use parentheses).
Rationale: summarizing "ByRef and ByVal Parameters"
If
1. there is an assignment of a function call retval, e. g.
iSum = myfunc(myArg)
or
2. "Call" is used, e. g.
call myFunc(myArg)
or
call mySub(myArg)
then the parentheses strictly delineate the calling argument list; the routine declaration determines ByVal or ByRef. OTHERWISE the parentheses force ByVal to be used by the routine - even though ByVal was not specified in the routine. Thus,
mySub(myArg) 'uses ByVal regardless of the routine's declaration, whereas
Call mySub(myArg) 'uses ByRef, unless routine declares ByVal
Also note that Call syntactically mandates use of parentheses. You can go
mySub myArg
but you can't go
call mySub myArg
but you CAN go
call mySub(myArg)
(and parentheses are syntactically required for assignment of Function return value)
NOTE however that ByVal on the routine declaration overrides all of this. And FYI, ByRef is always implied in the declaration if you are silent; thus TMK ByRef has no apparent value other than documentary.
Repeating from above: The big takeaway for you is that if you DO use parentheses around arguments to a routine, perhaps by rote or habit, even though they are not required, then you SHOULD USE Call to ensure that the routine's implicit or explicit ByRef is not disregarded in favor of ByVal; or, instead, you should use an "equal sign" assignment of the return value to prevent the disregard (in which case you would not use Call).
Again, that is to protect you from unfavorably getting ByVal from a routine. Conversely, of course, if you WANT ByVal interpretation regardless of the routine's declaration, then LEAVE OFF the Call (and use parentheses).
I use Call for all VBA development of common library functions that I possibly will use in VB.NET. This allows me to move code using copy and paste between all the flavors of VB. I do this to avoid the syntax errors that the code editor creates when it "formats" or "pretty prints" the pasted code. The only edits are usually Set statement inclusion/exclusion.
If you have no plans to move your VB/VBA code to VB.NET, then there is no need to use the Call statement.
The only case I found "call" is useful is quite an accident, about some special operators.
Dim c As IAsyncOperation(Of StartupTask) = StartupTask.GetAsync("Startup")
……
(Await c).Disable()
I got a syntax error for the second line, just like what you'll get with a "New" operator. I really don't want a new variable, which is too inelegant for me. So I tried:
DirectCast(Await c, StartupTask).Disable()
This is syntactically correct. But then the IDE hinted me that the "DirectCast" is unnecessary and gave a simplification. Yes, that is:
Call (Await c).Disable()
That's why I love VS2017 Preview. 😄
If you read the MSDN Support page for the Call Statement, for the specific case o VBA, at least, it does say that Call is optional, but what is very relevant about it and nobody seems to notice is this quoted line:
"If you use either Call syntax to call any intrinsic or user-defined function, the function's return value is discarded."
This is why Call is far from useless. Say you're writing Sub SupportTasks that does a lot of very relevant stuff for you Main Subs (for example, it imports data from a file to be used by different procedures). Now, notice that since SupportTasks is reading external data, there's always a fat chance this data will not come standard and the sub will not be able to fulfill its role. What do you do?
You could, for example, use boolean functions that return False if something goes wrong. Instead of calling a sub, call a function SupportTasks inside and If statement that will exit the Main sub if there's an anomaly:
If Not SupportTasks(SomeArgument) Then
Application.ScreenUpdating = True
Exit Sub
'Else continue the Main sub regularly without writing anything in here
End If
If you're wondering what the heck this has to do with Call, consider the following: in another sub, I call SupportTasks, but I do not need its returned boolean value (for instance, I'm certain an error won't occur). Well, if I don't put it in an If statement or assign the function to a useless variable, VBA will not compile and return me an error (procedure call invalid blah blah blah must assign value to something blah blah blah). That's where Call comes in to save the day!
Call SupportTasks(SomeArgument) '<< "Call Function" call doesn't return an error
If you still think it's useless, think of it as a resource to stay organized. Writing separate procedures for routines shared by many procedures makes your code shorter and more comprehensible, specially when you're writing really large applications. ERPs built out of Excel-Access integrations, for example, can be easier to operate, repair and customize if your IT dept slow to deliver/implement the real system...
To conclude, some internet wisdom:
Always write your code as if the person who will review it is a murderous psychopath who knows where you live.
Amen.
I'm 7 years late to the party, but I just happened to come across the Call keyword a few minutes ago while reading something on MSDN. In particular, it was used to do something I thought was impossible in VB.NET (as opposed to C#) -- which is related to #FCastro's answer.
Class Test
Public Sub DoSomething()
Console.WriteLine("doing something")
End Sub
End Class
Sub Main()
Call (New Test()).DoSomething()
End Sub
In the odd case you don't need the actual object instance but require one of its methods, you can use Call to save a line. Note that this is unnecessary when it's the right-hand side of an operation:
Class Test
Public Function GetSomething() As Integer
Return 0
End Function
End Class
Sub Main()
Dim x As Integer = (New Test()).GetSomething()
End Sub