自动筛选-使用SpecialCells

时间:2019-11-16 09:56:09

标签: excel vba autofilter

背景:

我已经多次申请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

enter image description here enter image description here


代码2-使用.Offset

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

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

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

enter image description here enter image description here


代码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:B6AutoFilter的机制似乎已被破坏。我试图用下面的截图展示它。中间的一个在">30"上过滤,右边的一个在">50"上过滤。如我所见,这与以下事实有关:所引用的范围现在由零个可见单元格组成。

enter image description here enter image description here enter image description here


代码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

enter image description here enter image description here


问题:

如您所见,我从.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

8 个答案:

答案 0 :(得分:4)

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

Dim nvar As Name
For Each n In ActiveWorkbook.Names
    n.Visible = TrueRange
NextWith nSheets("Sheet1")

在英文版的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进行了测试。它按预期执行。

答案 1 :(得分:1)

问题显然是由于处理表中的隐藏行而引起的,因此处理此问题的最简单方法是创建一个表体范围,您可以操纵该表体范围并查看可见单元格。

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

  

例如

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)是否为隐藏行(以及其他是否为隐藏行)

如果可见,它将按预期工作;实际上,无论是否存在隐藏的行(如果存在可见行),在其顶部进行调整大小最终都会选择可见的单元格。但是,如果所有行都被隐藏,则“偏移”范围仍处于“活动”状态,因此在调整其大小而没有可见的单元格可以包含该范围时,最终将选择所有单元格。

答案 2 :(得分:1)

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

  • 将自动过滤应用于某个范围
  • 选择该范围的第二列(包括第一行)
  • 过滤掉所有内容(因此只有第一行可见)
  • 输入新值,然后按 Ctrl + Enter 插入整个范围(仅影响第二列的第一行) enter image description here

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

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

答案 3 :(得分:1)

Range.AutoFilter method (Excel)的效果符合预期,将过滤条件应用于某个范围。 Range.SpecialCells method (Excel)在返回 union范围以及在其应用范围内遇到的可见单元格时,它的性能也达到了预期。

using Newtonsoft.Json; public class Item { public int Id { get; set; } [JsonProperty("ServiceName")] public string ItemName { get; set; } } 方法应用于整个范围SpecialCells时会产生意外结果,因为标头可见,然后将其包含在结果范围中。

由于Range("A1:B6")方法可能会返回一个并集范围(几个区域),建议将其应用于“目标” SpecialCells,我们需要使用值“ Check”进行更新,即Range

我们还需要使用Column(2)来管理On Error statement不返回可见行的情况。

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

AutoFilter

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

答案 4 :(得分:1)

这里有很多聪明的方法。矿山有些陈旧,但似乎可以正常工作(我已经使用问题中提供的表格对其进行了测试)

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

答案 5 :(得分:1)

如果要在过滤后更改范围的数据主体,则应使用原始范围偏移1行的 Intersection < / strong>(省去标题)和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

答案 6 :(得分:1)

我认为(在受限测试下,第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

答案 7 :(得分:0)

这是将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