是这种情况:
我需要从函数中获取两个值:该函数需要一个输入参数才能起作用。
strTitleName:输入参数
sName:输出参数
sScope:输出参数
Function getScenarioName(strTitleName As String, sName As String, sScope As String)
activateSheet ("Test Scenarios")
Dim rng1 As Range
Dim strSearch As String
strSearch = strTitleName & "*"
Set rng1 = Range("B:B").Find(strSearch, , xlValues, xlWhole)
If Not rng1 Is Nothing Then
'getScenarioName = rng1.Offset(0, 0)
sName = rng1.Address
sScope = rng1.Offset(1, 1).Address
Debug.Print "sName=" & sName
Debug.Print "sScope=" & sScope
End If
如何在子例程中获取sName和sScope的值?
答案 0 :(得分:1)
您可以在函数内部创建一个数组来存储两个值。然后,返回数组。
例如:
'If strTitleName is your only argument, then:
Function getScenarioName(strTitleName As String) As Variant
Dim rng1 As Range
Dim strSearch As String
Dim result(1) As String
activateSheet ("Test Scenarios")
Set rng1 = Range("B:B").Find(strSearch, , xlValues, xlWhole)
strSearch = strTitleName & "*"
result(0) = ""
result(1) = ""
If Not rng1 Is Nothing Then
sName = rng1.Address
sScope = rng1.Offset(1, 1).Address
Debug.Print "sName=" & sName
Debug.Print "sScope=" & sScope
result(0) = "sName=" & sName
result(1) = "sScope=" & sScope
End If
getScenarioName = result
End Function
答案 1 :(得分:1)
这里使用的概念是ByRef
和ByVal
参数。
在VBA中,除非另有说明,否则参数将通过引用传递 ,这是...一个不幸的默认值:在大多数其他语言中,参数通过值传递 。 >
99%的时间,您不需要传递任何内容ByRef
,因此ByVal
是完美的,应该在99%的时间明确指定。
在这样的情况下,传递参数ByRef
很有用,当您需要返回两个或多个值并返回一个封装了返回值的类的实例时,会显得过分。
请记住,即使不声明返回类型,Function
过程也总是返回值。如果您未能声明返回类型,并且从不分配返回值,则该函数将返回Variant/Empty
,这将使API变得相当混乱且非惯用语。
以下是几个选项:
说您的签名看起来像这样:
Public Function GetScenarioName(ByVal title As String, ByRef outName As String, ByRef outScope As String) As Boolean
现在,您可以在函数成功执行时返回True
,在函数执行失败时返回False
(例如,如果rng1
恰好是Nothing
),然后分配outName
和outScope
参数。
由于它们是通过引用传递 的,因此调用代码可以看到新值-这样调用者将看起来像这样:
Dim scenarioTitle As String
scenarioTitle = "title"
Dim scenarioName As String, scenarioScope As String
If GetScenarioName(scenarioTitle, scenarioName, scenarioScope) Then
Debug.Print scenarioName, scenarioScope
Else
Debug.Print "No scenario was found for title '" & scenarioTitle & "'."
End If
发生的事情是该函数收到了scenarioTitle
变量的副本-该副本本质上是该函数本地的变量:如果将其重新分配给函数,调用者看不到更新的值,原始参数不受影响(这就是ByVal
是最安全的参数传递方式的原因)。
但是该函数还会收到对scenarioName
和scenarioScope
变量的引用-并且在分配给其outName
和outScope
参数时,则该引用所拥有的值会相应地进行更新-调用者可以看到更新后的值。
仍然利用ByRef
返回值,将成员封装在一个有凝聚力的单元中有时是一个好主意:VBA允许您创建用户定义的类型,在简单的情况下,您只需要扔掉一些值:
Public Type TScenario
Title As String
Name As String
Scope As String
'...
End Type
Public Function GetScenarioInfo(ByRef info As TScenario) As Boolean
现在此功能将以类似的方式工作,除了现在您不再需要在要添加参数的时候更改其签名:只需将新成员添加到TScenario
即可!
调用代码将执行以下操作:
Dim result As TScenario
result.Tite = "title"
If GetScenarioInfo(result) Then
Debug.Print result.Name, result.Scope
Else
Debug.Print "No scenario was found for title '" & result.Title & "'."
End If
或者,您可以使用完整的类模块来封装ScenarioInfo
-在这种情况下...
将所需的一切封装在自己的类模块中,可以为您带来最大的灵活性:现在,您的函数可以返回对象引用了!
Public Function GetScenarioName(ByVal title As String) As ScenrioInfo
现在,如果未找到任何情况,则该函数可以返回Nothing
,并且除了 input 之外,不需要任何其他参数:
Dim scenarioTitle As String
scenarioTitle = "title"
Dim result As ScenarioInfo
Set result = GetScenarioInfo(scenarioTitle)
If Not result Is Nothing Then
Debug.Print result.Name, result.Scope
Else
Debug.Print "No scenario was found for title '" & scenarioTitle & "'."
End If
这是IMO最干净的方法,但确实需要一些样板-即ScenarioInfo
类模块。可能的最简单的实现将只是公开读取/写入公共字段:
Option Explicit
Public Name As String
Public Scope As String
更详尽的实现可能涉及一个IScenarioInfo
接口,该接口仅公开Property Get
个成员,ScenarioInfo
类的Implements IScenarioInfo
类,VB_PredeclaredId
属性(即。隐藏...,并且使用Rubberduck VBIDE加载项)和公开的 factory方法更容易处理,该方法可以参数化对象的创建-将函数转换为如下所示:
If Not rng1 Is Nothing Then
Set GetScenarioInfo = ScenarioInfo.Create(rng1.Address, rng1.Offset(1,1).Address)
End If
如果您认为这是一种有趣的方法,则可以在我维护的Rubberduck News博客上进行阅读。
答案 2 :(得分:0)
使用@Freeflow对集合的建议,这是您的更新代码:
Function getScenarioName(strTitleName As String, sName As String, sScope As String) as Collection
activateSheet ("Test Scenarios")
Dim rng1 As Range
Dim strSearch As String
strSearch = strTitleName & "*"
Set rng1 = Range("B:B").Find(strSearch, , xlValues, xlWhole)
If Not rng1 Is Nothing Then
'getScenarioName = rng1.Offset(0, 0)
sName = rng1.Address
sScope = rng1.Offset(1, 1).Address
dim colToReturn as Collection
set colToReturn = New Collection
colToReturn.Add sName
colToReturn.Add sScope
Set getScenarioName = colToReturn
End If
End Function