【问题标题】:AutoFilter - Use of SpecialCells自动筛选 - 使用 SpecialCells
【发布时间】:2020-05-30 17:14:36
【问题描述】:

背景:

我多次申请AutoFilter 并且从未真正问过自己为什么它有时会以这种方式工作。处理过滤数据的结果有时会让人感到困惑,尤其是当SpecialCells 发挥作用时。

让我详细说明以下场景:


测试数据:

| Header1 | Header2 |
|---------|---------|
| 50      |         |
| 10      |         |
| 30      |         |
| 40      |         |
| 20      |         |

代码 1 - 普通 AutoFilter:

With Sheets("Sheet1").Range("A1:B6")
    .AutoFilter 1, ">50"
        .Columns(2).Value = "Check"
    .AutoFilter
End With

这会起作用(即使不使用SpecialCells(12)),但会填充B1


代码 2 - 使用 .Offset

为了防止上述行为,我们可以像这样实现Offset

With Sheets("Sheet1").Range("A1:B6")
    .AutoFilter 1, ">50"
        .Columns(2).Offset(1).Value = "Check"
    .AutoFilter
End With

但是,现在这将填充我们数据下方的行,单元格B7


代码 3 - 使用 .Resize

为了防止.Offset 填充B7,我们现在必须包含.Resize

With Sheets("Sheet1").Range("A1:B6")
    .AutoFilter 1, ">50"
        .Columns(2).Offset(1).Resize(5, 1).Value = "Check"
    .AutoFilter
End With

尽管现在我们都阻止了B1B7 被填充,但B2:B6 被填充了,AutoFilter 机制似乎“损坏”了。我试图用下面的截图来展示它。中间一个是在">30" 上过滤时,右边是在">50" 上过滤时。在我看来,这与引用范围现在由零个可见单元格组成的事实有关。


代码 4 - 使用 .SpecialCells

我在这里做的正常事情是首先Count可见单元格(包括范围内的标题以防止error 1004)。

With Sheets("Sheet1").Range("A1:B6")
    .AutoFilter 1, ">50"
        If .SpecialCells(12).Count > 2 Then .Columns(2).Offset(1).Resize(5, 1).Value = "Check"
    .AutoFilter
End With


问题:

如你所见,我从.Columns(2).Value = "Check"一直到If .SpecialCells(12).Count > 2 Then .Columns(2).Offset(1).Resize(5, 1).Value = "Check",只是为了防止B1被覆盖。

显然,AutoFilter 机制在第一种情况下确实可以很好地检测可见行本身,但为了防止标题被覆盖,我必须实现:

我是不是把事情复杂化了,会有更短的路线吗?此外,为什么一旦没有单元格可见,就会填充整个范围的不可见单元格。当实际上过滤了一些数据时,它会很好地工作。这是什么机制(见代码3)?

我想出的不太优雅(IMO)的选项是重写B1

With Sheets("Sheet1").Range("A1:B6")
    .AutoFilter 1, ">50"
        Var = .Cells(1, 2): .Columns(2).Value = "Check": .Cells(1, 2) = Var
    .AutoFilter
End With

【问题讨论】:

  • 您是否尝试在第 2 列中的每个可见单元格中添加检查?
  • @TinMan,对于这个确实可以做的练习。但其背后的问题是在 AutoFilter 之后找到最好的方法,并考虑到根据我上面的说明可能存在不可见的单元格。
  • 你是对的。我会使用SpecialCells(),因为更改列标题会修改相关公式。
  • 不确定这是否有帮助,但我发现这篇非常有用的帖子回复:getting visible cell row count
  • 感谢@Zac 的指点,我会通读一遍。

标签: excel vba autofilter


【解决方案1】:

每当 Excel 在工作表上创建筛选列表时,它都会在名称管理器的后台创建一个隐藏的命名范围。如果您调用名称管理器,此范围通常不可见。使用以下代码使您隐藏的命名范围在名称管理器中可见(在使用它之前,在范围上设置过滤器):

Dim nvar As Name
For Each n In ActiveWorkbook.Names
    n.Visible = True
Next

在英文版的 Excel 中,隐藏的过滤范围称为_FilterDatabase。我的解决方案是将此隐藏范围与 SpeciallCells(12) 结合使用来解决问题。

更新 我的最终答案没有使用隐藏的命名范围,但我将留下该信息,因为它是发现过程的一部分...

Sub test1()
Dim var As Range
Dim i As Long, ans As Long
With Sheets("Sheet1").Range("A1:C1")
    .Range("B2:B6").Clear
    .AutoFilter
    .AutoFilter 1, ">50"
        Set var = Sheet1.AutoFilter.Range
        Set var = Intersect(var.SpecialCells(12), var.Offset(1, 0))
        If Not (var Is Nothing) Then
            For i = 1 To var.Areas.Count
                var.Areas(i).Offset(0, 1).Resize(var.Areas(i).Rows.Count, 1).Value = "Check"
            Next i
        End If
    .AutoFilter
End With
End Sub

我用 >30 和 >50 对其进行了测试。它按预期执行。

【讨论】:

  • 这是惊人的信息。感谢您的贡献。不知道秘密隐藏命名范围。现在会检查出来。此外,关于为什么首先出现问题的任何线索(这实际上是我所追求的)++
  • 不幸的是,我不知道为什么会发生这种情况。我的猜测是 Excel 需要有一个范围才能返回。在没有结果的情况下,它存储所有结果。再次,这是一个猜测。既然我已经看到了这个问题,我也很想知道真正的原因。
  • 只是你提出了隐藏的命名范围这一事实已经是很好的信息。此外,我认为您不需要设置var,而是可以使用正常范围对象。使它与我使用的逻辑几乎完全相同。再次感谢您=)
  • 您对变量的看法是正确的。我这样做是为了缩短代码并使其更具可读性。当我第一次了解隐藏的命名范围时,我也很兴奋 :-) 谢谢!
  • 不知道这个隐藏的命名范围。很好的资料++
【解决方案2】:

问题显然源于处理表格中的隐藏行,因此处理此问题的最简单方法是创建一个表格主体范围,您可以操作和查看可见单元格。

如果你想标记可见行,它比隐藏行更容易,否则你需要创建一个虚拟变量,取消隐藏,填充空白然后删除虚拟变量

例如

Sub AutoFilterTable()

    Dim SrcRange As Range: Set SrcRange = Sheets("Sheet1").Range("A1:B6")
    Dim BodyRange As Range: Set BodyRange = Application.Intersect(SrcRange, SrcRange.Offset(1, 0))

    With SrcRange
        BodyRange.Columns(2).ClearContents
        .AutoFilter 1, ">30"
        On Error Resume Next
        BodyRange.Columns(2).SpecialCells(xlCellTypeVisible) = "Check"
        .AutoFilter
    End With

End Sub

使用虚拟变量

Sub AutoFilterTable()

    Dim SrcRange As Range: Set SrcRange = Sheets("Sheet1").Range("A1:B6")
    Dim BodyRange As Range: Set BodyRange = Application.Intersect(SrcRange, SrcRange.Offset(1, 0))

    With SrcRange
        BodyRange.Columns(2).ClearContents
        .AutoFilter 1, ">30"
        On Error Resume Next
        BodyRange.Columns(2).SpecialCells(xlCellTypeVisible) = "Dummy"
        .AutoFilter
        BodyRange.Columns(2).SpecialCells(xlCellTypeBlanks) = "Check"
        BodyRange.Columns(2).Replace "Dummy", ""
    End With

End Sub

然后是代码 3 的问题:这取决于 .Columns(2).Offset(1) 是否为隐藏行(以及其他行是否隐藏)

如果可见,那么它将按预期工作;实际上,如果存在可见的行,则无论是否隐藏,在它们顶部调整大小最终都会选择可见单元格。如果所有行都被隐藏,偏移范围仍然是“活动的”,所以当调整它的大小而没有可见单元格包含该范围时,它最终会选择所有单元格。

【讨论】:

  • 感谢 Tragamor 的回复,但不幸的是这些代码不起作用。第一个将用"Check" 填充B3:B6,第二个将出错,因为在搜索">50" 时没有可见的单元格。我的问题的重点是AutoFilter 有一个非常有用的技巧,它可以在一个范围内看到可见的单元格。但是,零结果会取消此功能,甚至改为填充整个不可见范围!不过感谢您的贡献。您是否有关于假设最终会选择所有单元格的最后陈述的来源?再次感谢 +
  • 代码对我有用,并测试了多个等式,尽管您是正确的,当所有行都被隐藏时代码可能会出错等。“On Error Resume Next”的快速肮脏介绍涵盖了这些问题。不过,您可以检查范围是否为 Nothing。对于最后一个陈述,更多的是假设没有真正的证据支持它。源代码更多的是您上面的代码所做的,然后尝试对其进行合理化......
  • 再次感谢您的回复。 On Error 在这种情况下可能是“合法的”,但是与我目前得到的代码相比,提议的代码看起来并没有那么复杂。尽管如此,为思考而欢呼。我很感激 =)
【解决方案3】:

Range.AutoFilter method (Excel) 正在按预期执行,因为它正在将过滤条件应用于某个范围。 Range.SpecialCells method (Excel) 它也按预期执行,因为它返回了一个 联合范围,可见单元格在它所应用的范围内。

SpecialCells 方法应用于整个范围Range("A1:B6") 时会产生意外结果,因为标题是可见的,因此它包含在结果范围中。

由于SpecialCells 方法可能返回联合范围(多个区域),建议将其应用于“目标”Range,我们需要使用值“检查”进行更新,即Column(2) .

我们还需要使用On Error statement 来管理AutoFilter 没有返回可见行的情况。

以下过程应用两个过滤器更新各自的值。

Sub Range_AutoFilter()

    With ThisWorkbook.Sheets(1).Range("A1:B6")

        .AutoFilter 1, ">30"
        On Error Resume Next 
        .Cells(2, 2).Resize(-1 + .Rows.Count) _
            .SpecialCells(xlCellTypeVisible).Value2 = "Check >30"
        On Error GoTo 0
        .AutoFilter

        .AutoFilter 1, ">50"
        On Error Resume Next
        .Cells(2, 2).Resize(-1 + .Rows.Count, 1) _
            .SpecialCells(xlCellTypeVisible).Value2 = "Check >50"
        On Error GoTo 0
        .AutoFilter

    End With

    End Sub

这与其他答案没有什么不同,只是想添加关于由于应用方法的方式而导致的“意外结果”的解释。

【讨论】:

  • 您好 EEM,感谢您的贡献。我不确定AutoFilter 是否会按预期执行。您的观察是否基于这样一个事实,即自动过滤器背后的机制需要至少一个可见的细胞不会“破裂”?对我来说,预期的行为是,根据代码 3,它会识别出没有单元格被过滤,因此不需要输入任何值。根据我的代码 4,您的解释中有趣的是,您更喜欢 On error 语句而不是检查整个范围内的可见单元格。您会说这通常是更常见的做法吗?再次感谢您,++
  • 嗨@JvdV,并不是AutoFilter 需要至少一个可见单元格,更像是AutoFilter 不会影响(隐藏)标题。 AutoFilter 返回一个变体而不是一个范围。 AutoFilter 所做的是将行(不包括标题)的隐藏属性更改为 TRUE 用于不符合应用标准的行和 FALSE 用于匹配标准的行。在应用了 AutoFilter 的范围内尝试此操作,隐藏该范围的所有行(包括标题),然后刷新过滤器,您会注意到过滤后的行将显示,但标题将保持隐藏。
  • 关于代码 3 的行为,它通过执行.Columns(2).Offset(1).Resize(5, 1).Value = "Check" 行给出的指令按设计执行(即更新从第 2 列的第二行开始的范围,五个单元格down),该行不包含任何引用可见单元格的指令。
  • 我对 On Error 语句的看法与返回范围的 SpecialCells 方法一致,如果可见单元格为零,则范围为零,那么计算可见单元格将是多余的。跨度>
  • 请注意,AutoFiletr 的行为在应用于 ListObjects 时略有不同。
【解决方案4】:

这里有很多聪明的方法。地雷有点过时但似乎有效(我已经用问题中提供的表格对其进行了测试)

Sub SetFilteredCell()

    Dim oWS As Worksheet: Set oWS = ThisWorkbook.Worksheets("Sheet4")               ' Change sheet reference
    Dim iLRow As Long: iLRow = oWS.Range("A" & oWS.Rows.Count).End(xlUp).Row        ' Presuming that first row is the header
    Dim oRng As Range: Set oRng = oWS.Range("A1:B" & iLRow)                         ' Set range here
    Dim rFilteredRng As Range
    Dim oCRng As Range

    ' Clear any existing filter
    oWS.AutoFilterMode = False

    ' Set autofilter
    oRng.AutoFilter Field:=1, Criteria1:=">20"

    ' Check if autofilter returned any rows
    If oWS.AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Cells.Count - 1 > 0 Then

        ' Set filtered range
        Set rFilteredRng = oRng.Cells.SpecialCells(xlCellTypeVisible)

        ' Loop through all filtered rows
        For Each oCRng In rFilteredRng.Rows

            ' Skipping first row as the presumption is that its the header row
            If oCRng.Row > 1 Then
                oWS.Cells(oCRng.Row, 2).Value = "Check"
            End If

        Next

    End If

    ' Clear filter
    oWS.AutoFilterMode = False

End Sub

参考:This question

【讨论】:

  • 感谢您的贡献。这里确实有很多聪明的答案,但到目前为止还没有一个真正回答这个问题,为什么AutoFilter 似乎不愿意通过其自身来捕获非零可见单元格(根据代码 3)而是填充所有单元格一个隐藏的范围。还不能把我的头绕过去。再次谢谢你。也适用于您在上面的评论部分中发布的链接。
  • 是的,我不知道答案。我周五做了一些研究,但无法找到答案。我会密切关注这篇文章,以防有人提出解释
【解决方案5】:

这与手动执行这些步骤时的行为完全相同:

  • 对范围应用自动过滤器
  • 选择该范围的第二列(包括顶行)
  • 过滤掉所有内容(因此只有顶行可见)
  • 键入一个新值并按Ctrl+Enter插入整个范围(仅影响第二列的顶行)

现在,如果您在输入前按向下箭头(与.Offset(1) 相同),则会选择下一个可见单元格(B7)。

如果您在应用自动过滤器之前手动选择范围B2:B6(因此所有单元格都被过滤掉),并使用 Ctrl+Enter 插入一个值,所有单元格将受到影响 - 我猜这是手动自动过滤器未处理的边缘情况(人们没有尝试仅向隐藏单元格插入值),即使在使用 VBA 自动过滤器时并不理想。

【讨论】:

  • 感谢Aprillion的回复,也感谢您的比较。不幸的是,我无法测试最后一条语句,因为我的 Excel(2019 年)不会让我取消全选并按确定。您是否在这个未处理的边缘案例中找到了任何可靠的来源? +
  • 您可以应用数字过滤器,例如“大于 50”,就像在 VBA 代码中一样。不幸的是,我的消息来源被总结为“我猜”。
  • 好的,谢谢 Aprillion。刚刚检查并可以确认手动过滤将返回与 VBA 对应项相同的结果。不幸的是,零结果会破坏AutoFilter 识别要填充哪些单元格的能力。 :(
【解决方案6】:

这是链接SpecialCells xlCellTypeConstantsxlCellTypeVisible 以修剪目标范围的另一个变体。

With Range("A1:B6")

    .Offset(1).Columns(2).ClearContents
    .AutoFilter 1, ">50", , , True

    On Error Resume Next
     .Offset(1).SpecialCells(xlCellTypeConstants).SpecialCells(xlCellTypeVisible).Columns(2) = "Checked"
    On Error GoTo 0

    .AutoFilter
End With

【讨论】:

    【解决方案7】:

    当你想改变过滤后范围的databody时,你应该使用原始范围偏移1行Intersection >(省略标题)和SpecialCells(xlCellTypeVisible),然后通过Areas

    线索在这个例子中:

    Option Explicit
    
    Sub MoreThan50()
        MoreThanValue "50"
    End Sub
    
    Private Sub MoreThanValue(Optional Amount As String = "")
        Dim oRng As Range, oRngArea As Range, oRngResult As Range
        Set oRng = Sheets(1).Range("A1:B6")
        ' Clear Previous data on 2nd column
        With Intersect(oRng, oRng.Offset(1))
            .Columns(2).ClearContents
        End With
        With oRng
            ' Apply AutoFilter
            .AutoFilter 1, ">" & IIf(Len(Amount) = 0, "50", Amount)
            ' Update 2nd Column of resulting data
            Set oRngResult = Intersect(oRng.Offset(1), .SpecialCells(xlCellTypeVisible))
            If Not oRngResult Is Nothing Then
                With oRngResult
                    If .Areas.Count > 0 Then
                        For Each oRngArea In .Areas
                            oRngArea.Columns(2).Value = "check"
                        Next
                    End If
                End With
                Set oRngResult = Nothing
            End If
            .AutoFilter
        End With
        Set oRng = Nothing
    End Sub
    

    【讨论】:

    • 感谢帕特里克的贡献,这是处理这种情况的另一种方式。这篇文章正在成为一个收集伟大想法的地方。我对这个问题的看法是AutoFilter 自己创建了一个要使用的范围。遗憾的是,当没有过滤结果时(不使用其他方法和属性),我们无法使用它。尽管没有回答 AutoFilter 的行为方式为何(根据代码 3),但我非常感谢您的洞察力。再次感谢,++
    • 如果你把范围变成表格(ListObject),你可以使用listobject的DataBodyRange,这样表头就不在你要修改的数据范围内了。
    【解决方案8】:

    我认为(通过有限测试,第 2 列中的单元格必须为空)您​​可以使用 .FindNext。 不需要 SpecialCells 和错误语句,

    我在你的代码 3 中添加了这个:

    With Sheets("Sheet1").Range("A1:B6")
        .AutoFilter 1, ">50"
           If Not .FindNext(.Cells(1)) Is Nothing Then .Columns(2).Offset(1).Resize(5, 1).Value = "Check"
        .AutoFilter
    End With
    

    编辑: 假设您的 FilterTable 旁边有一个空白列

    With Sheets("Sheet1").Range("A1:B6")
        .AutoFilter 1, ">30"
              If Not .Offset(, 1).FindNext() Is Nothing Then .Columns(2).Offset(1).Resize(5, 1).Value = "Check"
        .AutoFilter
    End With
    

    【讨论】:

    • 感谢您的贡献。这是测试过滤结果的另一种方法,但是我发现这不可靠。搜索 <30 后跟 <20 将跳过第二个结果。它还将引入.FindNext 而不是另一种方法。尽管如此,再次感谢++
    • 本例中的 FindNext 需要空单元格,我在上面添加了一些代码。它需要过滤表旁边的空白列。但这确实是另一种方法。 ++
    • 我几乎可以写一本书,用各种不同的方式来实现同一件事。然而,还没有人回答这个问题:为什么AutoFilter 会创建一个完全可用的范围,但是当没有过滤任何结果时,它会填充整个范围。因此,为什么我们需要所有这些属性/方法(根据所有答案以多种形式)来处理这个问题。
    • ^ 写这本书 :)
    • 我会说,如果过滤后没有行可见,excel 会假装/假定所有行都被隐藏(如在没有自动过滤选项的情况下隐藏,这将填充整个范围)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多