Excel VBA - 使用 FindNext 后功能停止(在子程序中工作)

Posted

技术标签:

【中文标题】Excel VBA - 使用 FindNext 后功能停止(在子程序中工作)【英文标题】:Excel VBA - Function Stops after using FindNext (Works in Subroutine) 【发布时间】:2019-08-27 01:41:33 【问题描述】:

我正在编写一个函数,该函数将在名为“Usage”的指定工作表的列中查找所有单元格,该工作表将有多个单元格与我找到的内容相匹配。问题是当我尝试调用 FindNext 时,VBA 停止工作而不会引发任何错误。 但是,如果我将函数更改为子例程,它会完美运行,因为 Debug 会显示找到的所有 10 个单元格地址。但是我需要使用一个函数,因为我将根据找到的结果返回一个值。当我使用 F8 单步执行时,在调试行使用 FindNext 后,我​​的函数立即停止 - 至少有 10 个值与传入的项目匹配。完全混淆了为什么它作为子例程而不是函数工作。

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  

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:

问题是 .FindNext 在 UDF 中不起作用(从 Excel 公式调用)。

您可以改用另一个 .Find 来解决此问题。

其他几点:

    我也会传入搜索范围,使其更加灵活 一些Find 参数,如果未指定,则从上次使用Find 中获取值,无论是由VBA 还是用户。 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

像这样称呼

=FindAllCells(Usage!$A:$A,"item")

也就是说,考虑到你的一些 cmets 对你更大的目标,我认为你是在一个受伤的世界

【讨论】:

【参考方案2】:

我今天早些时候需要一个可以在单个单元格中显示每个匹配项的 UDF。我记得写了一个可以做到这一点的函数,我回到这里是为了重用我的代码。

对我来说不幸的是,它是针对 OPs 问题的,对于我当前的需求不够灵活。

我的损失就是你的收获!

所以现在,我已经完全重写了函数以接受Range.Find 中的几乎所有参数。据我所知,工作表中没有像 xlByColums 这样的 Excel 枚举器。我不可能很快记住所有这些,所以我通过将大多数参数转换为布尔值使事情变得更容易。 searchByColumnfindPartialMatchreverseSearchDirectioncaseSensitivelookAtFormat 均通过 True/False 设置,但 xlFindLookIn 仍需要常量值来指定 cmets/formulas/values。

这还不是全部……

输出不必是匹配项。它可以是匹配项的地址或偏移量。想象一下,如果VLOOKUPINDEXMATCH 将它们匹配的所有内容放入一个单元格中,这基本上就是偏移参数所允许的。在重要的时候,输出可以包括匹配的总数。

但是等等,还有更多!

一个小但非常重要的选项,分隔符可以通过参数从工作表中设置。

我只想说,这个函数可以有多种使用方式,我不可避免地会在某个时候回来重用它。

请注意,我使用了具有两个不同条件退出点的无限循环,逻辑应该清晰易懂,但如果您开始编辑代码,则需要注意这一点。

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

【讨论】:

非常感谢。不知道 `.FindNext' 在 UDF 中不起作用。它现在可以完美地找到所有匹配项。 @user2382843 很高兴为您提供帮助。老实说,我也不知道,直到我尝试复制您的问题。值得庆幸的是,该问题已被社区充分记录,并且易于解决。

以上是关于Excel VBA - 使用 FindNext 后功能停止(在子程序中工作)的主要内容,如果未能解决你的问题,请参考以下文章

Excel VBA Range.FindNext v Range.Find - 我错过了啥?

下标超出范围 - 使用Excel VBA查找下一个

Excel VBA 比较两个表格的不同?

EXCEL VBA 求助

使用 VBA 读写后 Excel 日期更改(月份和日期交换)

Excel / VBA:粘贴数据后自动调整列宽