Excel VBA - Function Stops after using FindNext (Works in Subroutine)

蓝咒 提交于 2021-01-29 06:36:05

问题


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:

  1. I'd pass in the search Range too, to make it a bit more flexible
  2. Some Find parameters, if not specified, take there values from the last use of Find, 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!