【问题标题】:Pass arguments to a worksheet function as an array in VBA将参数作为 VBA 中的数组传递给工作表函数
【发布时间】:2023-03-05 07:50:01
【问题描述】:

我编写了一个调用 SUMIFS 工作表函数的 VBA 函数。它工作得很好,但我想摆脱 Select 语句。这要求 Criteria rangeCriteria 参数对(名义上数量不受限制)作为一个数组传递给 SUMIFS,并在函数中组装。

这是我当前的代码。

Function SUMIFS(SumRng As Range, _
                ParamArray Ifs() As Variant) As Double
    ' each element of Ifs is an array of 3 elements:
    '   0 = Criteria range, 1 = Operator, 2 = Criterium

    Const Symbols   As String = "=,<>,>,<,<=,>="
    
    Dim Symb()      As String
    Dim Tmp         As Variant
    Dim i           As Long             ' Ifs index
    
    Symb = Split(Symbols, ",")
    For i = LBound(Ifs) To UBound(Ifs)
        Tmp = Ifs(i)(1)
        If VarType(Ifs(i)(2)) = vbDate Then
            Ifs(i)(1) = Format(Ifs(i)(2), Ifs(i)(0).Cells(1).NumberFormat)
        Else
            Ifs(i)(1) = Ifs(i)(2)
        End If
        If Val(Tmp) Then Ifs(i)(1) = Symb(Tmp) & Ifs(i)(1)
    Next i
    
    Select Case UBound(Ifs)
        Case 0
            SUMIFS = WorksheetFunction.SUMIFS(SumRng, Ifs(0)(0), Ifs(0)(1))
        Case 1
            SUMIFS = WorksheetFunction.SUMIFS(SumRng, Ifs(0)(0), Ifs(0)(1), _
                                                      Ifs(1)(0), Ifs(1)(1))
        Case 2
            SUMIFS = WorksheetFunction.SUMIFS(SumRng, Ifs(0)(0), Ifs(0)(1), _
                                                      Ifs(1)(0), Ifs(1)(1), _
                                                      Ifs(2)(0), Ifs(2)(1))
        Case 3
            SUMIFS = WorksheetFunction.SUMIFS(SumRng, Ifs(0)(0), Ifs(0)(1), _
                                                      Ifs(1)(0), Ifs(1)(1), _
                                                      Ifs(2)(0), Ifs(2)(1), _
                                                      Ifs(3)(0), Ifs(3)(1))
        Case 4
            SUMIFS = WorksheetFunction.SUMIFS(SumRng, Ifs(0)(0), Ifs(0)(1), _
                                                      Ifs(1)(0), Ifs(1)(1), _
                                                      Ifs(2)(0), Ifs(2)(1), _
                                                      Ifs(3)(0), Ifs(3)(1), _
                                                      Ifs(4)(0), Ifs(4)(1))
    End Select
End Function

请注意,运算符作为 0 到 5 之间的数字(枚举)传递给此函数,它指定 Symb() 的元素之一。

如您所见,此过程中有 4 个不同的函数调用,如果使用 5 个标准对调用该函数,它将失败。同时,这4个调用之间的差异是微小而系统的,一分钟内可以添加第五个。

我正在寻找一种将组装的参数数组传递给工作表函数的单个调用的方法。我知道这可以通过创建相应的工作表函数字符串并使用Evaluate 函数来实现,但是当我在 VBA 中编写工作表函数时,它们变得混乱,这意味着它们难以维护和发展。我喜欢上面代码的清晰结构,并且不想为了提高一点效率而牺牲它,这意味着我愿意接受更高效率或令人愉悦的设计的论点,无论哪一种都可以提供。

2021 年 1 月 20 日编辑

我以为我有一个 @GSerg 想法的解决方案,但函数调用只接受 2 组标准。这似乎没有意义,因为第三个标准的创建方式与第二个标准相同。我想知道我是否对一个简单的缺陷视而不见。请看一下。

Private Sub Test_SUMIFS()
    Dim SumRng As Range
    
    Set SumRng = Range("A2:A11")
    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"))
    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"), _
                               Array(Range("C2:C11"), 0, 10))
'    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"), _
'                               Array(Range("C2:C11"), 0, 10), _
'                               Array(Range("D2:D11"), 0, "Z"))
End Sub

Function SUMIFS(SumRng As Range, _
                ParamArray Ifs() As Variant) As Double
    ' each element of Ifs is an array of 3 elements:
    '   0 = Criteria range, 1 = Operator, 2 = Criterium

    Const Symbols   As String = "=,<>,>,<,<=,>="
    
    Dim Symb()      As String
    Dim Fun()       As Variant          ' Converterd Ifs()
    Dim Tmp         As Variant
    Dim i           As Long             ' Ifs index
    
    ReDim Fun(2, 1)                     ' extend to a maximum of 14 if required
    Symb = Split(Symbols, ",")
    For i = LBound(Ifs) To 1
        If i > UBound(Ifs) Then
            Fun(i, 0) = SetMissing()
            Fun(i, 1) = SetMissing()
        Else
            Set Fun(i, 0) = Ifs(i)(0)
            Fun(i, 1) = Ifs(i)(2)
            Tmp = Ifs(i)(1)
            If VarType(Ifs(i)(2)) = vbDate Then
                Fun(i)(1) = Format(Ifs(i)(2), Ifs(i)(0).Cells(1).NumberFormat)
            Else
                Fun(i, 1) = Ifs(i)(2)
            End If
            If Val(Tmp) Then Fun(i)(1) = Symb(Tmp) & Fun(i)(1)
        End If
    Next i
    
    ' this function call works for both calls
    SUMIFS = WorksheetFunction.SUMIFS(SumRng, Fun(0, 0), Fun(0, 1), _
                                              Fun(1, 0), Fun(1, 1))
    ' this function call doesn't work
'    SUMIFS = WorksheetFunction.SUMIFS(SumRng, Fun(0, 0), Fun(0, 1), _
'                                              Fun(1, 0), Fun(1, 1), _
'                                              Fun(2, 0), Fun(2, 1))
End Function

Private Function SetMissing(Optional ByVal MissingValue As Variant) As Variant
    ' assign the value of "Missing" to an uninitialized variant
    
    If IsMissing(MissingValue) Then
        SetMissing = MissingValue
    Else
        Err.Raise 5, , "Wrong use of function: The parameter must be missing!"
    End If
End Function

如果您按原样运行代码,一旦在 ActiveSheet 中设置了测试范围,它就可以工作,但不能使用第二个工作表函数调用。

【问题讨论】:

  • 请问这个函数怎么调用? 'ParamArray()' 中有哪些元素?如果在锯齿状阵列上闻起来,但你如何构建它?使用精确的SumIfs 参数,也用逗号分隔?
  • @FaneDuru 从使用上看,一定是SUMIFS(Me.Range("A1:A10"), Array(Me.Range("B1:B10"), 2), Array(Me.Range("C1:C10"), 3))。但是,调用此函数的确切方式对于回答如何将其传递给SumIfs 的问题并不重要。
  • @FaneDuru 感谢您的回复。我有 2 天没有上网。所以,很抱歉回复晚了,但我不能补充 GSerg 已经说过的话:-)
  • 没问题。如果我会更专注地阅读这个问题,我不应该问这个问题......我最初理解传递变量的方式不起作用,并试图理解这是否是错误参数的问题。否则,参数只能采用 GSerg 建议的方式。

标签: excel vba


【解决方案1】:

参数对,名义上数量不限

实际上,您最多可以将 14 对范围传递给 SumIfs,因为只有 29 个参数。这些 Excel 函数通常限制为 30 个参数,添加第 15 对会将参数计数推至 31。因此,您可以为这 14 个案例硬编码 Select Case 并称之为 day,它不会增长。


另一种选择是手动传递 Missing value 以获取缺少的参数:

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 Args(0 To 13, 0 To 1) As Variant
Dim i As Long
  
For i = LBound(Ifs) To UBound(Ifs)
    Set Args(i, 0) = Ifs(i)(0)
    Args(i, 1) = Ifs(i)(1)
Next
  
For i = UBound(Ifs) + 1 To UBound(Args)
    Args(i, 0) = missing
    Args(i, 1) = missing
Next
  
SUMIFS = Application.WorksheetFunction.SUMIFS(SumRng, Args(0, 0), Args(0, 1), _
                                                      Args(1, 0), Args(1, 1), _
                                                      Args(2, 0), Args(2, 1), _
                                                      Args(3, 0), Args(3, 1), _
                                                      Args(4, 0), Args(4, 1), _
                                                      Args(5, 0), Args(5, 1), _
                                                      Args(6, 0), Args(6, 1), _
                                                      Args(7, 0), Args(7, 1), _
                                                      Args(8, 0), Args(8, 1), _
                                                      Args(9, 0), Args(9, 1), _
                                                      Args(10, 0), Args(10, 1), _
                                                      Args(11, 0), Args(11, 1), _
                                                      Args(12, 0), Args(12, 1), _
                                                      Args(13, 0), Args(13, 1))

【讨论】:

  • 感谢您的回复,很抱歉我的回复晚了。这看起来与我正在寻找的完全一样 - 加上有关容量的额外信息 - 只要我正确理解重载参数可以作为“缺失”传递。我会在早上测试这个想法。
  • 我使用您的想法构建的解决方案中途卡住了。在我看来,我的解决方案与您的解决方案没有任何不同,或者是吗?请查看我的问题中的编辑。
  • @Variatus Args 必须始终为0 To 13。您总是将所有 29 个参数传递给 SUMIFS。在您的编辑中,您必须有ReDim Fun(13, 1),并且循环必须是For i = 0 to 13,而不是For i = LBound(Ifs) To 1
  • 是的,当然。谢谢你。在早晨的光线下 - 用你的指针 - 我立即看到了错误。它不是在亮点,而是在阵列中。但是,您关于必须提供所有 13 个参数的说法是不正确的。因此,我将发布另一个答案。
【解决方案2】:

下面的函数可以用一对到最多七对Criteria RangeCriterium来调用。这个限制可以扩展到 13 个,只需要在 Redim Fun(7, 1) 的代码中进行一次更改,并在函数调用中添加额外的参数。

Function SUMIFS(SumRng As Range, _
                ParamArray Ifs() As Variant) As Double
    ' each element of Ifs is an array of 3 elements:
    '   0 = Criteria range, 1 = Operator, 2 = Criterium

    Const Symbols   As String = "=,<>,>,<,<=,>="
    
    Dim Symb()      As String
    Dim Fun()       As Variant          ' Converterd Ifs()
    Dim Tmp         As Variant
    Dim i           As Long             ' Ifs index
    
    ReDim Fun(7, 1)                     ' extend to a maximum of 13 if required
    Symb = Split(Symbols, ",")
    For i = 0 To UBound(Fun)
        If i > UBound(Ifs) Then
            Fun(i, 0) = SetMissing()
            Fun(i, 1) = SetMissing()
        Else
            Set Fun(i, 0) = Ifs(i)(0)
            Fun(i, 1) = Ifs(i)(2)
            Tmp = Ifs(i)(1)
            If VarType(Ifs(i)(2)) = vbDate Then
                Fun(i)(1) = CLng(Ifs(i)(2))
            Else
                Fun(i, 1) = Ifs(i)(2)
            End If
            If Val(Tmp) Then Fun(i)(1) = Symb(Tmp) & Fun(i)(1)
        End If
    Next i
    
    SUMIFS = WorksheetFunction.SUMIFS(SumRng, Fun(0, 0), Fun(0, 1), _
                                              Fun(1, 0), Fun(1, 1), _
                                              Fun(2, 0), Fun(2, 1), _
                                              Fun(3, 0), Fun(3, 1), _
                                              Fun(4, 0), Fun(4, 1), _
                                              Fun(5, 0), Fun(5, 1), _
                                              Fun(6, 0), Fun(6, 1), _
                                              Fun(7, 0), Fun(7, 1)) ', _
'                                              Fun(8, 0), Fun(8, 1), _
'                                              Fun(9, 0), Fun(9, 1), _
'                                              Fun(10, 0), Fun(10, 1), _
'                                              Fun(11, 0), Fun(11, 1), _
'                                              Fun(12, 0), Fun(12, 1), _
'                                              Fun(13, 0), Fun(13, 1))
End Function

实际上,函数调用可以扩展为大于 Ubound(Fun) 但不大于 13 的参数数量,这在一定程度上验证了@GSerg 的断言,即总数(包括总和范围) 必须低于 30。

我重新设计了 GSerg 的支持函数,该函数将不存在的参数转换为“缺失”的参数,以符合我的理解方法。它的功能无法改进,但我更喜欢将其保密,因为它是这一功能的从属。

Private Function SetMissing(Optional ByVal MissingValue As Variant) As Variant
    ' assign an uninitialized variant to "MissingValue"
    
    If IsMissing(MissingValue) Then
        SetMissing = MissingValue
    Else
        Err.Raise 5, , "Wrong use of function: The parameter must be missing!"
    End If
End Function

以下是测试我的函数的过程,使用 1、2 和 3 标准调用它。请注意0 参数指定Const Symbols 中的第一个运算符。因此它不能是大于 5 的数字。

Private Sub Test_SUMIFS()
    Dim SumRng As Range
    
    Set SumRng = Range("A2:A11")
    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"))
    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"), _
                               Array(Range("C2:C11"), 0, 10))
    Debug.Print SUMIFS(SumRng, Array(Range("B2:B11"), 0, "A"), _
                               Array(Range("C2:C11"), 0, 10), _
                               Array(Range("D2:D11"), 0, "Z"))
End Sub

代码适用于下图所示的工作表。

【讨论】:

    猜你喜欢
    • 2022-11-16
    • 2012-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    相关资源
    最近更新 更多