Pavel Skuhrovec
Pavel Skuhrovec

Reputation: 21

VBA - 2010 - How to invert a filter

I am wondering about a simple solution for reversing the filtered values. Although it seems to me to be an easy task, I have not had a success while researching on the internet.

The situation and problem: I have a table with multiple columns, and lot of rows (exact amount does not matter obviously) and I want to see what was not filtered in exactly one column. The problem is that I normally need to do a lot of clicking

For example - in the database of projects I have filtered the ones worth over 500 000 €, which are mine and are coming from a specific country. By one click I would like to see which ones are below 500 000 €) but are still mine and coming from a specific country.

Possible solutions which came to my mind:

Does anybody has an idea how to approach this situation? I am able to try the VBA on my own so I would be happy if you can point me in the right direction. Of course I would welcome your thoughts in code too.

Upvotes: 1

Views: 1936

Answers (1)

Dick Kusleika
Dick Kusleika

Reputation: 33165

Here's an idea to toggle a numeric filter. It won't work with all numeric filters, but most of them. For instance, it won't work with Between, because that uses Criteria1 and Criteria2. But you could expand the code to account for that.

Also, it only really works on numeric filters. It will work on some text filters, but only if one criteria is applied.

Sub InvertNumericFilter()

    Dim lFilter As Long
    Dim lo As ListObject
    Dim fltr As Filter
    Dim aOper As Variant, aOpp As Variant
    Dim i As Long

    'aOpp is the opposite of the corresponding
    'operator in aOper
    aOper = Split("<> <= >= = < >")
    aOpp = Split("= > < <> >= <=")

    'Find which column you're in
    Set lo = ActiveCell.ListObject
    lFilter = ActiveCell.Column - lo.DataBodyRange.Column + 1
    Set fltr = lo.AutoFilter.Filters(lFilter)

    'if the first characters of the criteria are in aOper
    'then swap them for aOpp
    For i = LBound(aOper) To UBound(aOper)
        If Left(fltr.Criteria1, Len(aOper(i))) = aOper(i) Then
            lo.DataBodyRange.AutoFilter lFilter, Replace$(fltr.Criteria1, aOper(i), aOpp(i))
            Exit For
        End If
    Next i

End Sub

Your example happened to be inverting a number, but if you want it to be universal (apply to nonnumerics), it would get a lot more complicated.

Update

This will invert value lists, but it makes some assumptions. For one, if you only have two values, it's not a value list, it's an xlOr operator. If you're using xlOr on some other type of field, it might cause problems.

Sub InvertFilter()

    Dim lFilter As Long
    Dim lo As ListObject
    Dim fltr As Filter
    Dim aOper As Variant, aOpp As Variant
    Dim i As Long, j As Long
    Dim dc As Scripting.Dictionary
    Dim vaValues As Variant

    'Find which column you're in
    Set lo = ActiveCell.ListObject
    lFilter = ActiveCell.Column - lo.DataBodyRange.Column + 1
    Set fltr = lo.AutoFilter.Filters(lFilter)

    'lists of values or just two values
    If fltr.Operator = xlFilterValues Or fltr.Operator = xlOr Then
        'get all the possible values and put in a dictionary
        vaValues = lo.ListColumns(lFilter).DataBodyRange.Value
        Set dc = New Scripting.Dictionary
        For i = LBound(vaValues, 1) To UBound(vaValues, 1)
            If Not dc.Exists("=" & vaValues(i, 1)) Then
                dc.Add "=" & vaValues(i, 1), "=" & vaValues(i, 1)
            End If
        Next i

        'If it's more than two values
        If IsArray(fltr.Criteria1) Then
            'remove from dictionary
            For i = LBound(fltr.Criteria1) To UBound(fltr.Criteria1)
                If dc.Exists(fltr.Criteria1(i)) Then
                    dc.Remove fltr.Criteria1(i)
                End If
            Next i
        Else
            dc.Remove fltr.Criteria1
            dc.Remove fltr.Criteria2
        End If
        'reapply filter
        lo.DataBodyRange.AutoFilter lFilter, dc.Keys, xlFilterValues
    ElseIf fltr.Operator = 0 Then

        'aOpp is the opposite of the corresponding
        'operator in aOper
        aOper = Split("<> <= >= = < >")
        aOpp = Split("= > < <> >= <=")

        'if the first characters of the criteria are in aOper
        'then swap them for aOpp
        For i = LBound(aOper) To UBound(aOper)
            If Left(fltr.Criteria1, Len(aOper(i))) = aOper(i) Then
                lo.DataBodyRange.AutoFilter lFilter, Replace$(fltr.Criteria1, aOper(i), aOpp(i))
                Exit For
            End If
        Next i
    End If

End Sub

Upvotes: 1

Related Questions