我一直在尝试编写MaxIf用户定义函数。代码完美地作为sub运行,但Do Loop作为一个没有错误的函数失败。我作为一个功能逐步介绍它,但没有发现任何线索。
Public Function udfMaxIf(criteria As Range, criteria_range As Range, max_range As Range)
Dim dblValues() As String
Dim lngMax As Long
Dim lngX As Long, intLastRow As Integer
Dim strSearch As String
Dim rngCriteria As Range, strFirst As String, strLast As String
strSearch = criteria.Value
'# setting after = to last row in range forces the Find to start from, and including, the first line of the range. Else it starts from top
'# row but doesn't search it until last.
With criteria_range
intLastRow = .Rows.Count
Set rngCriteria = .Find(strSearch, after:=criteria_range.Cells(intLastRow, 1), LookIn:=xlValues, searchorder:=xlByRows, searchdirection:=xlNext)
End With
If Not rngCriteria Is Nothing Then
'# set the value of the first appearance of rngCriteria to array(0)
ReDim dblValues(0)
strFirst = rngCriteria.Address
strLast = criteria_range.Find(strSearch, LookIn:=xlValues, searchorder:=xlByRows, searchdirection:=xlPrevious).Address
dblValues(0) = Cells(rngCriteria.Row, max_range.Column).Value
'# add subsequent values to the array but break when it reaches last row. If this was only value it would have been picked up above.
Do Until rngCriteria.Address = strLast
Set rngCriteria = criteria_range.FindNext(rngCriteria)
ReDim Preserve dblValues(UBound(dblValues) + 1)
dblValues(UBound(dblValues)) = Cells(rngCriteria.Row, max_range.Column).Value
Loop
End If
On Error GoTo UBound_handler:
lngMax = dblValues(0)
For lngX = 0 To UBound(dblValues)
If dblValues(lngX) > lngMax Then
lngMax = dblValues(lngX)
End If
Next lngX
On Error Resume Next
udfMaxIf = lngMax
Exit Function
UBound_handler:
If Err.Number = 9 Then
MsgBox "Criteria not found in criteria range", vbInformation
Else:
MsgBox Err.Number & ": " & Err.Description
End If
Exit Function
End Function
方法可以改进,但我更关心的是弄清楚为什么它不能起作用。感谢。
答案 0 :(得分:2)
就个人而言,我更喜欢迭代数组,因为我发现它比其他方法更可靠,更快。像这样的东西可以作为MaxIf UDF使用:
Public Function MaxIf(ByVal rCriteria As Range, ByVal sCriteria As String, ByVal rMax As Range) As Variant
Dim aCrit As Variant
Dim aValues As Variant
Dim i As Long, j As Long
Dim dMax As Double
aCrit = rCriteria.Value
aValues = rMax.Value
If rCriteria.Rows.Count & "," & rCriteria.Columns.Count <> rMax.Rows.Count & "," & rMax.Columns.Count Then
MaxIf = CVErr(xlErrRef)
Exit Function
End If
If Not IsArray(aCrit) Then
If LCase(aCrit) Like LCase(sCriteria) Then MaxIf = aValues Else MaxIf = 0
Else
dMax = -10 ^ 308
For i = 1 To UBound(aCrit, 1)
For j = 1 To UBound(aCrit, 2)
If LCase(aCrit(i, j)) Like LCase(sCriteria) Then
If IsNumeric(aValues(i, j)) Then
If aValues(i, j) > dMax Then dMax = aValues(i, j)
End If
End If
Next j
Next i
If dMax > -10 ^ 308 Then MaxIf = dMax Else MaxIf = 0
End If
End Function
答案 1 :(得分:1)
dblValues(0) = Cells(rngCriteria.Row, max_range.Column).Value
这样的行可能会有问题,因为你没有将Cells()
限定为特定的工作表,所以它将默认为Activesheet的任何内容(除非你在表单模块中有这个代码)
答案 2 :(得分:1)
示例数据从单元格A1开始,单元格G2中的值为“A”,将UDF称为=foo(G2, A1:D6)
A B C D
E F G H
I J A B
C D E F
G H I J
A B C D
这是一个简单的例子,你可以作为函数或子函数运行。
Public Function foo(criteria As Range, criteria_range As Range)
Dim rngCriteria As Range
Dim intLastRow As Long
Dim strSearch$, strFirst$, strLast$
Dim ret
strSearch = criteria.Value
With criteria_range
intLastRow = .Rows.Count
Set rngCriteria = .Find(strSearch, after:=criteria_range.Cells(intLastRow, 1), LookIn:=xlValues, searchOrder:=xlByRows, searchdirection:=xlNext)
End With
If Not rngCriteria Is Nothing Then
strFirst = rngCriteria.Address
strLast = criteria_range.Find(strSearch, LookIn:=xlValues, searchOrder:=xlByRows, searchdirection:=xlPrevious).Address
Do Until rngCriteria.Address = strLast
Set rngCriteria = criteria_range.FindNext(rngCriteria)
Loop
End If
ret = rngCriteria.Address
foo = ret '// Comment this line if calling as Sub
Debug.Print ret
End Function
单步执行代码,当从工作表中作为UDF运行时,您将观察到Do
循环在第二次传递时无声地失败,但是当从Sub
调用时,它将无错误地执行VBE即刻疼痛。
UDF有一些限制(在这篇知识库文章中解释得不是很好):
https://support.microsoft.com/en-us/kb/170787
快速谷歌search suggests that Find
是禁止使用的方法之一,尽管似乎并非如此。像你这样的问题&amp;我观察到FindNext
没有返回任何内容,而Do
循环依赖于非Nothing
来查询Address
属性。如果FindNext
被禁止,我会指望该表达式出现错误/无声失败,所以可能这是一个错误。
由于Find
未导致错误,因此可以重新使用UDF以仅在循环中使用Find
方法,而不是FindNext
方法。
Public Function newfoo(criteria As Range, criteria_range As Range)
Dim rngCriteria As Range
Dim intLastRow As Long
Dim strSearch$, strFirst$, strLast$
Dim ret
strSearch = criteria.Value
With criteria_range
intLastRow = .Rows.Count
Set rngCriteria = .Find(strSearch, After:=criteria_range.Cells(intLastRow, 1), LookIn:=xlValues, searchOrder:=xlByRows, searchdirection:=xlNext)
End With
If Not rngCriteria Is Nothing Then
ret = rngCriteria.Address
strFirst = rngCriteria.Address
strLast = criteria_range.Find(strSearch, LookIn:=xlValues, searchOrder:=xlByRows, searchdirection:=xlPrevious).Address
Do
Set rngCriteria = criteria_range.Find(strSearch, After:=rngCriteria, LookIn:=xlValues, searchOrder:=xlByRows, searchdirection:=xlNext)
If Not rngCriteria Is Nothing Then
ret = ret & "|" & rngCriteria.Address
If rngCriteria.Address = strLast Then
Exit Do
End If
End If
Loop
End If
Debug.Print ret
foo = ret
End Function
或者您可以重新配置FindNext
个参数。 (的未测试强>)
答案 3 :(得分:-1)
感谢所有的想法和建议。之前我不知道UDF以不同的方式工作到常规潜艇。
最终我使用@tigeravatar数组公式扩展到多MAXIF,并提供以下帮助: MaxIF with multi-criteria