-->

How do I pass a range obj variable to a sub in Exc

2019-06-08 23:21发布

问题:

This question already has an answer here:

  • Array argument must be ByRef 1 answer

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

回答1:

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


回答2:

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!



回答3:

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...