Getting Array Data from a Filtered List in VBA

Getting data from an Excel sheet into an array is usually best accomplished with a statement that looks something like this:

vMyArray = Sheet1.UsedRange.Value

I thought it would be keen to fill an array from a filtered list, so I coded

Sub ArrFilteredList()
   
    Dim vArr As Variant
   
    vArr = Sheet1.UsedRange.SpecialCells(xlCellTypeVisible).Value
   
    Stop
   
End Sub

on this list

The SpecialCells returns a range of only those cells that are visible, i.e. unfiltered in this case. The problem, it turns out, is that this method doesn’t work with noncontiguous ranges and that’s just what SpecialCells returns. I put the Stop in there so I could check the Locals Window.

It filled from the first Area of the range, then stopped. I confirmed that it was the lack of continutity of the range with this code

Sub ArrNonContiguous()
   
    Dim vArr As Variant
   
    vArr = Union(Sheet1.Range("A1:C1"), Sheet1.Range("A4:C6")).Value
   
    Stop
   
End Sub

which returned similar results. So I’m stuck iterating through the range, I guess. But then my array is backward; column, row instead row, column because I can’t change the first element of an array with Redim Preserve.

Sub ArrFilteredList2()
   
    Dim rRow As Range
    Dim aArr() As String
    Dim i As Long
    Dim lCount As Long
   
    ReDim aArr(1 To 3, 1 To Sheet1.UsedRange.Rows.Count)
    lCount = 0
   
    For Each rRow In Sheet1.UsedRange.Rows
        If rRow.Hidden = False Then
            lCount = lCount + 1
            For i = 1 To 3
                aArr(i, lCount) = rRow.Cells(i).Value
            Next i
        End If
    Next rRow
   
    ReDim Preserve aArr(1 To 3, 1 To lCount)
   
    Stop
   
End Sub

Is there a better way?

7 Comments

  1. Jim Cone says:

    ‘Uses a variant array
    ‘Uses the range that is auto filtered
    ‘Transposes the filled array
    ‘Me thinks you may have something else in mind?

    Sub ArrFilteredList2()

    Dim rRow As Range
    Dim vArr As Variant
    Dim i As Long
    Dim lCount As Long
    Dim af As AutoFilter
    Dim rng As Range

    Set af = ActiveSheet.AutoFilter
    Set rng = af.Range
    Set rng = rng.Offset(1, 0).Resize(rng.Rows.Count - 1, rng.Columns.Count)
    ReDim vArr(1 To 3, 1 To rng.Rows.Count)

    For Each rRow In rng.Rows
    If rRow.Hidden = False Then
    lCount = lCount + 1
    For i = 1 To 3
    vArr(i, lCount) = rRow.Cells(i).Value
    Next i
    End If
    Next rRow

    ReDim Preserve vArr(1 To 3, 1 To lCount)
    vArr = Application.Transpose(vArr)
    ‘Stop
    MsgBox UBound(vArr, 1) & vbCr & UBound(vArr, 2)
    End Sub

  2. geoff says:

    Are you committed to using an autofilter? If not, an advanced filter extracting to a new range would do the trick. Or, a SQL query perhaps?

  3. hans schraven says:

    The simplest way:
    copy the cells to an empty space
    read the data there

    With Sheets(1).usedrange
      .autofilter 1,"test"
      .offset(1).copy sheets(1).cells(1,27)
      .autofilter
    End with
    sq=sheets(1).cells(1,27).currentregion
    sheets(1).cells(1,27).currentregion.clearcontents
  4. hans schraven says:

    I forgot

    With Sheets(1).usedrange
      .autofilter 1,"test"
      .offset(1).specialcells(xlcelltypevisible).copy sheets(1).cells(1,27)
      .autofilter
    End with
    sq=sheets(1).cells(1,27).currentregion
    sheets(1).cells(1,27).currentregion.clearcontents
  5. AlexJ says:

    I might be tempted to read the full (unfiltered) range into an array (to keep the number of sheet read actions down) and then put each row into a collection element, filtering out unwanted elements during the process.

    I tend to do my manipulations on the collection elements, then write to an array to load onto the sheet, but you could load the array from the collection before manipulation.

    I DO like geoff’s SQL idea, though.

  6. Devo says:

    This worked for me…

    ””’

    Sub Arr_Visible_Cells()

    Dim rRow As Range
    Dim aArr() As Variant
    Dim i As Long
    Dim lCount As Long
    Dim CellCount As Variant
    Dim Range_To_Get As Variant

    CellCount = Sheets(1).UsedRange.Rows.Count
    Range_To_Get = Sheets(1).UsedRange.Address

    ReDim aArr(1 To 3, 1 To CellCount)

    lCount = 1
    i = 1
    For Each rRow In Sheets(1).Range(Range_To_Get)
        If lCount = 4 Then
        i = i + 1
        lCount = 1
        End If
            If rRow.Rows.Hidden = False Then
             aArr(lCount, i) = rRow
            Else
             GoTo Devo:
            End If
    lCount = lCount + 1
    Devo:
    Next

    ReDim Preserve aArr(1 To 3, 1 To i)

    aArr = Application.Transpose(aArr)
    End Sub

    Good Luck

    Devo

  7. gruff999 says:

    If AutoFiltering is enabled, and an AutoFilter is in effect, AutoFilter.Range appears to be a pseudo UsedRange which includes all cells in scope for auto-filtering. The range it refers to doesn`t change with different filtered row counts.

    I used Intersect, feeding in xlVisible and AutoFilter.Range to get what I needed to create a chart from the filtered rows.

    (You have to adjust AutoFilter.Range to remove the first row).

    Not thoroughly tested as I`m referring back to an old bit of code.

    Any good?

Posting code or formulas in your comment? Use <code> tags!

  • <code lang="vb">Block of code goes here</code>
  • <code lang="vb" inline="true">Inline code goes here</code>
  • <code>Formula goes here</code>

Leave a Reply