0
votes

I know I can filter a range in Excel with VBA under use of AutoFilter e.g.:

Sub name()
ActiveSheet.Range("$A$12:$Y$74").AutoFilter Field:=22, Criteria1:="String"
End Sub)

... that work's perfectly fine. However, I am struggling to make it a bit more sophisticated. Does anyone have an idea how the following example can be implemented in VBA?

What I would like to do is to filter for checkmarked items from a List Box with Operator:=xlAnd between each checkmarked list item.

Example: If I checkmark String1 and String2 in the following List Box the AutoFilter function should return all rows that contain String1 and String2. In case of the table below, this would be row 2 and row 4.

enter image description here

| 1 | String1                   |
| 2 | String2, String1          |
| 3 | String2                   |
| 4 | String1, String2, String3 |
| 5 | String3                   |
| 6 | String1                   |
| 7 | String3, String1          |
4
I would add a hidden helper column that returns true if the terms are found in the column of interest, while false if not. you can then use the autofilter to hide rows returned as false. Tie that in with the Listbox change event and you should be able to get an autoupdating table. - Tragamor
@Jonas any reason why you have not assigned the bounty yet? - EEM

4 Answers

2
votes

Haven't tested it, but in theory, this works with autofilter too:

Sub name()
ActiveSheet.Range("$A$12:$Y$74").AutoFilter Field:=22, Criteria1:="*String1*", _
Operator:=xlOr, Criteria2:="*String2*"
End Sub)

If you can modify the String1 and String2 to include * either in the code or list box, I think this should work to find those scenarios.

1
votes

If you have multiple values to Filter by, what I would do is add them values to an Array and then use the values from the Array to filter the range, such as below:

Sub Autofiler_Array()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
'declare and set the worksheet you are working with
Dim myarray As Variant
myarray = Array("String1", "String2", "String3")
'declare and assign values to Array

If ws.FilterMode Then ws.Range("$A$12:$Y$74").AutoFilter
'if Worksheet already is Filtered, then remove Autofilter
ws.Range("$A$12:$Y$74").AutoFilter Field:=22, Criteria1:=myarray, Operator:=xlFilterValues
'Autofilter with Array Values on Column 22 of the applicable range
End Sub

UPDATE:

After reading your comments and updated question, I believe the following will achieve your desired results, instead of using the AutoFilter, the code below will loop through your rows, check if the cell contains all values from the Array, if not hiding them rows:

Sub Auto_Filter()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
'declare and set the worksheet you are working with
Dim arrWords  As Variant
arrWords = Array("String1", "String2")
'declare and assign values to Array
ws.Cells.EntireRow.Hidden = False
'unhide all rows
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
'get last row with data in Column A

For i = 2 To LastRow
'loop through rows
    For Each aWord In arrWords 'loop through Array values
        If Not InStr(ws.Cells(i, 22).Value, aWord) > 0 Then
            ws.Rows(i).EntireRow.Hidden = True
            'if values from Array not found in cell, then hide row
        End If
    Next
Next i
End Sub
1
votes

AutoFilter a range using an Array

Requirement: Filter a range in order to show all the rows that contain all the items in an array.
i.e. for the Array = (“String1”, “String2”, “String3”, “String4”, “String5” )
The AutoFilter should include all the rows that contain “String1”, “String2”, “String3”, “String4” and “String5” in any position.
This should the equivalent to being able to perform something like this as a custom AutoFlter:

.AutoFilter Field:=1, _
    Criteria1:=sCriteria1, Operator:=xlAnd, _
    Criteria2:=sCriteria2, Operator:=xlAnd, _
    Criteria3:=sCriteria3, Operator:=xlAnd, _
    Criteria4:=sCriteria4, Operator:=xlAnd, _
    Criteria5:=sCriteria5, Operator:=xlAnd, _
    …, _
    CriteriaN:=sCriteriaN

Solution: This proposed solution:
1. Process the array values (each two) in order to generate an array of filtered ranges
2. Obtains the intersection of filtered ranges array
3. Hides all the rows in the target range and unhides all the rows in the intersection range
4. Creates an array with all the values in step 4
5. Filters the target range applying the array generated in step 4

The advantages of this procedure are:
 It does not loop through each of the rows of the target range.
 Returns an AutoFilter, so additional filters can be applied to other fields without losing the Array AutoFilter.

Procedure:

Function Range_ƒFilter_ByArray_Contains(aCriteria As Variant, rTrg As Range, sMsg As String) As Boolean
Returns as boolean Filters a Target range (rTrg) applying all the values in the Criteria array (aCriteria), returning also a message (sMsg) in case of Error.

Function Range_ƒFilter_ByArray_Contains(aCriteria As Variant, _
    rTrg As Range, sMsg As String) As Boolean
Dim blAfByAry As Boolean
Dim arAFs() As Range
Dim ws As Worksheet
Dim bDim As Byte
Dim sCriteria1 As String, sCriteria2 As String
Dim rAFs As Range, aAFcontains As Variant
Dim b As Byte

    Rem Validate Input
    If (rTrg Is Nothing) Then sMsg = "Target range is invalid": GoTo Exit_Err
    If Not (IsArray(aCriteria)) Then sMsg = "aCriteria is not an array": GoTo Exit_Err
    On Error Resume Next
    aCriteria = WorksheetFunction.Index(aCriteria, 0, 0)
    If Err.Number <> 0 Then GoTo Exit_Err
    bDim = UBound(aCriteria, 2)
    If Err.Number = 0 Then sMsg = "aCriteria is not a single dimension array": GoTo Exit_Err
    On Error GoTo Exit_Err

    With Application
        .EnableEvents = False
        .DisplayAlerts = False
        .ScreenUpdating = False
        .Calculation = xlCalculationManual
    End With

    With rTrg

        Rem Clear AutoFilter
        With .Worksheet
            On Error Resume Next
            If Not .AutoFilter Is Nothing Then .AutoFilter.Range.AutoFilter
            On Error GoTo 0
        End With

        Rem Dimensioning AutoFilters Range Array
        bDim = UBound(aCriteria)
        blAfByAry = bDim > 2
        If blAfByAry Then
            If WorksheetFunction.IsOdd(bDim) Then bDim = 1 + bDim
            bDim = (bDim / 2)
            ReDim Preserve arAFs(1 To bDim)
        End If

        For b = 1 To UBound(aCriteria) Step 2

            Rem Apply AutoFilter Criterias (2 each time)
            sCriteria1 = aCriteria(b)
            Select Case b
            Case UBound(aCriteria)
                .AutoFilter Field:=1, Criteria1:=sCriteria1
            Case Else
                sCriteria2 = aCriteria(1 + b)
                .AutoFilter Field:=1, Criteria1:=sCriteria1, _
                    Operator:=xlAnd, Criteria2:=sCriteria2
            End Select

            Rem Set AutoFilter Range Item
            If blAfByAry Then Set arAFs((1 + b) / 2) = rTrg.SpecialCells(xlCellTypeVisible)

    Next: End With

    If blAfByAry Then

        Rem Set AutoFilters Range
        Set rAFs = arAFs(1)
        For b = 2 To UBound(arAFs)
            Set rAFs = Application.Intersect(rAFs, arAFs(b))
        Next

        With rTrg

            Rem Clear AutoFilter
            rTrg.AutoFilter

            Rem Apply AutoFilters Range
            .EntireRow.Hidden = True
            rAFs.EntireRow.Hidden = False

            With ThisWorkbook

                Rem Set AutoFilter Array Criteria
                Set ws = .Sheets.Add(After:=.Sheets(.Sheets.Count))
                With ws
                    rAFs.Copy
                    .Cells(1).PasteSpecial
                    aAFcontains = .Cells(1).CurrentRegion.Value2
                    aAFcontains = WorksheetFunction.Transpose(aAFcontains)
                    ws.Delete

            End With: End With

            Rem Apply AutoFilter Array Criteria
            rTrg.AutoFilter Field:=1, _
                Criteria1:=aAFcontains, Operator:=xlFilterValues

    End With: End If

    Range_ƒFilter_ByArray_Contains = True

Exit_Err:

    With Err
        If .Number <> 0 Then sMsg = "Error: " & .Number & vbLf & vbTab & .Description
    End With

    With Application
        .Calculation = xlCalculationAutomatic
        .ScreenUpdating = True
        .DisplayAlerts = True
        .EnableEvents = True
    End With

    End Function

The procedure should be used like this:

Set rTrg = ThisWorkbook.Worksheets(kWsh).Range(kRng)
If Not (Range_ƒFilter_ByArray_Contains(aCriteria, rTrg, sMsg)) Then
    MsgBox sMsg, vbCritical, "Range_ƒFilter_ByArray_Contains"
End If

Note: This solution only handles the xlAnd operator in line with the original OP's question, nevertheless it can be easily modified to include work also with the xlOr operator.

0
votes

Here's my solution which is inspired by Xabier's answer. It features two "scenarios".

1) Display rows in which the string in the cell under scrutinty contains either String1 or String2

2) Display rows in which the string in the cell under scrutinty contains String1 and String2

Sub AoP()

StartRow = 13
EndRow = 73
TargetColumn = 19 '(R)

LengthListBox = (ActiveSheet.ListBox1.ListCount - 1) ' Number of ListBox entries

ReDim TestXYZ(LengthListBox) As Integer 'Permanent list of checkmarked ListBox entries as ones and zeros
ReDim CheckList(LengthListBox) As String 'Permanent list of checkmarked ListBox entries as strings
ReDim Matches(LengthListBox) As Integer 'Temporary list of matches between search criteria and cell content

'''''''''''''''''''''''''''''''''''''''''''''''''
' Create arrays with information on the ListBox
'''''''''''''''''''''''''''''''''''''''''''''''''

For i = 0 To LengthListBox 'For 0 to length of ListBox
    If ActiveSheet.ListBox1.Selected(i) Then 'Loop
        TestXYZ(i) = 1 ' Checkmarked = 1
        CheckList(i) = ActiveSheet.ListBox1.List(i)
    Else
        TestXYZ(i) = 0 ' Not checkmarked = 0
    End If
Next i

'''''''''''''''''''''''''''''''''''''''''''''''''
' Hide rows that do not match a specific criteria
'''''''''''''''''''''''''''''''''''''''''''''''''

'If OR is selected as an operator
If ActiveSheet.CheckBox_AoP_Or.Value = True Then ' If "Or" is selected as an operator
    For i = StartRow To EndRow 'For each row
        ActiveSheet.Rows(i).EntireRow.Hidden = True 'Hide all rows ifnot
        For j = 0 To LengthListBox 'For 0 to length of ListBox
            If Len(CheckList(j)) > 0 Then
                If InStr(1, ActiveSheet.Cells(i, TargetColumn).Value, CheckList(j), vbTextCompare) > 0 Then 'If the cell contains the checked ListBox string
                    ActiveSheet.Rows(i).EntireRow.Hidden = False 'Unhide the row
                End If
            End If
        Next j
    Next i
'If OR is NOT selected as an operate (behave like AND)
Else ' If "Or" is NOT selected as an operator
    For i = StartRow To EndRow 'For each row
        ActiveSheet.Rows(i).EntireRow.Hidden = True 'Hide all rows ifnot
        For k = 0 To LengthListBox 'Makes sure that the matches are set to zero
            Matches(k) = 0
        Next k
        For j = 0 To LengthListBox 'Parse through all list box entries
            If TestXYZ(j) = 1 Then ' If they have been checkmarked
                If InStr(1, ActiveSheet.Cells(i, TargetColumn).Value, CheckList(j), vbTextCompare) > 0 Then ' ... and if they are contained in the string
                    Matches(j) = 1 ' Contained = 1
                Else
                    Matches(j) = 0 ' Not contained = 0
                End If
            End If
        Next j
        If Excel.WorksheetFunction.Sum(TestXYZ) = Excel.WorksheetFunction.Sum(Matches) Then 'If all are contained (all are matched so the sum of 1 is equal)
            ActiveSheet.Rows(i).EntireRow.Hidden = False '... then unhide
        End If
    Next i
End If

End Sub