Background:
For many times I have applied AutoFilter
and never really asked myself why it works the way it does sometimes. Working with the results of filtered data can be confusing at times, especially when SpecialCells
comes into play.
Let me elaborate with the below scenario:
Test data:
| Header1 | Header2 |
|---------|---------|
| 50 | |
| 10 | |
| 30 | |
| 40 | |
| 20 | |
Code 1 - Plain AutoFilter
:
With Sheets("Sheet1").Range("A1:B6")
.AutoFilter 1, ">50"
.Columns(2).Value = "Check"
.AutoFilter
End With
This will work (even without the use of SpecialCells(12)
), but will populate B1
.
Code 2 - Using .Offset
:
To prevent the above behaviour we can implement Offset
like so:
With Sheets("Sheet1").Range("A1:B6")
.AutoFilter 1, ">50"
.Columns(2).Offset(1).Value = "Check"
.AutoFilter
End With
However, this will now populate the row below our data, cell B7
.
Code 3 - Using .Resize
:
To prevent .Offset
to populate B7
we must now include a .Resize
:
With Sheets("Sheet1").Range("A1:B6")
.AutoFilter 1, ">50"
.Columns(2).Offset(1).Resize(5, 1).Value = "Check"
.AutoFilter
End With
Allthough now we both prevented B1
and B7
to be populated we got B2:B6
populated, the AutoFilter
mechanism appears to be "broken". I tried to show it with the below screenshots. The middle one is when filtered on ">30"
and the right one when filtered on ">50"
. As I see it, this will have to do with the fact that the referenced range now consists of zero visible cells.
Code 4 - Using .SpecialCells
:
The normal thing for me to do here would to Count
the visible cells first (including the headers in the range to prevent an 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
Question:
As you can see, I went from .Columns(2).Value = "Check"
all the way to If .SpecialCells(12).Count > 2 Then .Columns(2).Offset(1).Resize(5, 1).Value = "Check"
, just to prevent B1
to be overwritten.
Apparently, AutoFilter
mechanism does work very well in the first scenario to detect visible rows itself, but to prevent the header to be overwritten I had to implement:
Am I overcomplicating things here and would there be a shorter route? Also, why does a whole range of invisible cells get populated once no cells are visible. It would work well when there is actually some data filtered. What mechanism does this (see code 3)?
The, not so very elegant (IMO), option I came up with is to rewrite 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
SpecialCells()
because changing the column header will modify dependent formulas. – DayakAutofilter
is behaving as designed (you could argue that the design is flawed.. but that's a whole different argument). Problem is when you apply the filter,Header
row will always be visible so using.Columns(2).Value = "Check"
will cause issues as you've found out. On the other hand, if after applying the filter, you use asub
range and use the same statement, it works as expected (bysub
range i mean:With Sheets("Sheet1").Range("A2:B6")
.. Note thatsub
range starts from A2) – Bismuthic