问题
I am writing a function that will find all cells in a column in a specified sheet named "Usage" which will have multiple cells matching what I am finding. Problem is that when I try to call FindNext, VBA stops working without throwing any errors. But if I change the function into a subroutine, it works perfectly fine as the Debug shows all 10 cell addresses found. But I need to use a function because I will be returning a value based on the the found results. My function stops right after I use FindNext at the Debug line when I'm stepping through using F8 - There are at least 10 values that match the item being passed in. Completely confused as to why it works as a subroutine but not a function.
Function FindAllCells(item As String) As String
Dim searchRange As Range
Dim foundItem As Range
Dim firstCellAddress As String
Set searchRange = Sheets("Usage").Range("A:A")
Set foundItem = searchRange.Find(What:=item, LookAt:=xlWhole)
If foundItem Is Nothing Then
Exit Function
End If
firstCellAddress = foundItem.Address
Do
Set foundItem = searchRange.FindNext(foundItem)
Debug.Print foundItem.Address
Loop While firstCellAddress <> foundItem.Address
FindAllCells = foundItem.Offset(0,2)
End Function
回答1:
I needed a UDF earlier today that could display every match inside a single cell. I remembered writing a function that could do this and I came back here with the intent of reusing my code.
Unfortunately for me, it was specific to the OPs question and not flexible enough for my current needs.
My loss is your gain!
So now, I've completely rewritten the function to accept almost every parameter in Range.Find. To the best if my knowledge, Excel enumerators like xlByColums are not available from the worksheet. There is no way I'm going to remember all of them any time soon so I've made things easier by converting most parameters to Boolean. searchByColumn, findPartialMatch, reverseSearchDirection,caseSensitive, and lookAtFormat are all set via True/False but the constant value is still needed for xlFindLookIn to specify comments/formulas/values.
And that's not all...
The output doesn't have to be the matched item. It can be the address of the matched item or an offset. Imagine if VLOOKUP or INDEXMATCH put everything they matched into a single cell, that's basically what the offset parameters allow. And for those times when it matters, the output can include the total number of matches.
But wait, there's more!
A small but hugely significant option, the delimiter can be set from the worksheet via parameter.
Suffice it to say, this function can be used in a multitude of ways and I will inevitably be coming back at some point to reuse it.
Be aware that I used an infinite loop with two different conditional exit points, the logic should be clear and easy to follow but it's something to watch out for if you start editing the code.
Public Function FindAllMatches(ByVal searchFor As String, ByVal searchRange As Range, _
Optional ByVal columnOffset As Long, _
Optional ByVal rowOffset As Long, _
Optional ByVal countMatches As Boolean, _
Optional ByVal showAddresses As Boolean, _
Optional ByVal delimiter As String, _
Optional ByVal findPartialMatch As Boolean = True, _
Optional ByVal searchByColumn As Boolean = False, _
Optional ByVal reverseDirection As Boolean = False, _
Optional ByVal caseSensitive As Boolean = False, _
Optional ByVal lookAtFormat As Boolean = False, _
Optional ByVal lookInside As XlFindLookIn = xlValues _
) As String
Dim firstMatchCellAddress As String
Dim searchResult As Range
Dim returnString As String
Dim matchCount As Long
Dim includePartial As Single: includePartial = -findPartialMatch + 1
Dim previousDirection As Single: previousDirection = -reverseDirection + 1
Dim searchAxis As Single: searchAxis = -searchByColumn + 1
If Not CBool(Len(delimiter)) Then delimiter = Chr(44)
Set searchResult = searchRange
Do
Set searchResult = searchRange.Find(What:=searchFor, After:=searchResult.Cells(searchResult.Cells.Count), LookIn:=lookInside, _
LookAt:=includePartial, SearchOrder:=searchAxis, SearchDirection:=previousDirection, _
MatchCase:=caseSensitive, SearchFormat:=lookAtFormat)
Select Case True
Case searchResult Is Nothing
Exit Do
Case firstMatchCellAddress = searchResult.Address
Exit Do
Case CBool(Len(returnString))
If showAddresses Then
returnString = returnString & delimiter & searchResult.Offset(rowOffset, columnOffset).Address
Else
returnString = returnString & delimiter & searchResult.Offset(rowOffset, columnOffset)
End If
Case Else
If showAddresses Then
returnString = searchResult.Offset(rowOffset, columnOffset).Address
Else
returnString = searchResult.Offset(rowOffset, columnOffset)
End If
firstMatchCellAddress = searchResult.Address
End Select
matchCount = matchCount + 1
Loop While True
If countMatches Then
If CBool(matchCount) Then
returnString = matchCount & delimiter & returnString
Else
returnString = 0
End If
End If
FindAllMatches = returnString
End Function
回答2:
The issue is that .FindNext does not work in a UDF (called from an Excel formula).
You can work around that by using another .Find instead.
Couple of other points:
- I'd pass in the search Range too, to make it a bit more flexible
- Some
Findparameters, if not specified, take there values from the last use ofFind, whether by VBA or the user. See here
Function FindAllCells(searchRange As Range, item As String) As String
Dim foundItem As Range
Dim firstCellAddress As String
Set foundItem = searchRange.Find(What:=item, After:=searchRange.Cells(searchRange.Cells.Count), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows)
If foundItem Is Nothing Then Exit Function
firstCellAddress = foundItem.Address
Do
Set foundItem = searchRange.Find(What:=item, After:=foundItem, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows)
Debug.Print foundItem.Address
Loop While firstCellAddress <> foundItem.Address
FindAllCells = foundItem.Offset(0, 2)
End Function
Called like
=FindAllCells(Usage!$A:$A,"item")
That said, given some of your comments on your larger goal, I think you are in for a world of hurt
来源:https://stackoverflow.com/questions/57666720/excel-vba-function-stops-after-using-findnext-works-in-subroutine