How can this vba search be modified to include subfolders?

RusselJ

Board Regular
Joined
Aug 5, 2013
Messages
155
Hi all

My VBA code currently lists all *.xls files in a directory and displays these in a form listbox.

How can this be modified so that it will also search in subdirectories?

Code:
Private Sub UserForm_Initialize()
On Error Resume Next
    Dim FileList(), i   As Long, X, n   As Long, fName As String
    FilePath = "C:\Users\Russel\Documents\Projects\" 
    fName = Dir(FilePath & "*" & SearchBox.Value & "*.xls")
    i = 1
    Do While fName <> ""
        ReDim Preserve FileList(1 To i)
        FileList(i) = fName
        i = i + 1
        fName = Dir()
    Loop
    ReDim Preserve FileList(1 To i - 1)
    With Me.ListBox1
        .Clear
        .List = FileList
    End With
End Sub


Many thanks for your help,

Russel
 

Excel Facts

What do {} around a formula in the formula bar mean?
{Formula} means the formula was entered using Ctrl+Shift+Enter signifying an old-style array formula.
I realize this may not be the MOST efficient way to do this, but it will require no additional VBA references to be added and I was challenging myself to find a way to do this with only DIR() functionality. That being said, I still needed a bit of help from this post https://social.msdn.microsoft.com/F...a2941a13d324/searching-folders-and-subfolders

But in the end, I have something that I believe will do exactly what you are looking for. It looks for any matches in the specified directory and any subdirectories (even 2nd, 3rd, ...Nth level)

Code:
Dim FolderList As Variant
Private Sub UserForm_Initialize()
On Error Resume Next
    Dim FileList() As String, FolderList() As String
    Dim i As Long, n As Long, j As Long
    Dim x As Variant    'not sure what this was for
    Dim fName As String, fldrName As String, FilePath As String
    FilePath = "C:\Users\Russel\Documents\Projects\"
    ReDim FolderList(1 To 1) As String
    FolderList(1) = FilePath
    
    listDir FilePath, 1 'loads array FolderList with folders/subfolders
    i = 1
    For j = 1 To UBound(FolderList)
        fName = Dir(FolderList(j) & "*" & SearchBox.Value & "*.xls*")
        Do While fName <> ""
            ReDim Preserve FileList(1 To i) As String
            FileList(i) = fName
            i = i + 1
            fName = Dir()
        Loop
    Next j
    'ReDim Preserve FileList(1 To i - 1) ***this shouldn't be necessary
    With Me.ListBox1
        .Clear
        .List = FileList
    End With
End Sub
Public Sub listDir(strPath As String, lngSheet As Long)
Dim strFn As String
Dim strDirList() As String
Dim lngArrayMax, x As Long
lngArrayMax = 0
strFn = Dir(strPath & "*.*", 23)
While strFn <> ""
    If strFn <> "." And strFn <> ".." Then
        If (GetAttr(strPath & strFn) And vbDirectory) = vbDirectory Then
            If IsEmpty(FolderList) Then
                ReDim FolderList(1 To 1) As String
            Else
                ReDim Preserve FolderList(1 To UBound(FolderList) + 1) As String
            End If
            FolderList(UBound(FolderList)) = strPath & strFn & "\"
            lngArrayMax = lngArrayMax + 1
            ReDim Preserve strDirList(lngArrayMax)
            strDirList(lngArrayMax) = strPath & strFn & "\"
        End If
    End If
    strFn = Dir()
Wend
If lngArrayMax <> 0 Then
    For x = 1 To lngArrayMax
        Call listDir(strDirList(x), lngSheet)
    Next
End If
End Sub
 
Upvote 0
Hi Biocide

Thank you for taking the time to help!

I am getting a compile error Invalid Redim at line:
ReDim FolderList(1 To 1) As String

I don't know whether it matters that I am using Excel 2003?

Would appreciate your thoughts?

Thanks again

Russel
 
Upvote 0
My mistake. I went through a couple iterations of ways to do this before I got the one that worked and I left an artifact of the FolderList variable in the local declaration section.
Try this code instead.

Code:
Public FolderList As Variant
Private Sub UserForm_Initialize()
On Error Resume Next
    Dim FileList() As String
    Dim i As Long, n As Long, j As Long
    Dim x As Variant    'not sure what this was for
    Dim fName As String, fldrName As String, FilePath As String
    FilePath = "C:\Users\Russel\Documents\Projects\"
    ReDim FolderList(1 To 1) As String
    FolderList(1) = FilePath
    
    listDir FilePath, 1 'loads array FolderList with folders/subfolders
    i = 1
    For j = 1 To UBound(FolderList)
        fName = Dir(FolderList(j) & "*" & SearchBox.Value & "*.xls*")
        Do While fName <> ""
            ReDim Preserve FileList(1 To i) As String
            FileList(i) = fName
            i = i + 1
            fName = Dir()
        Loop
    Next j
    'ReDim Preserve FileList(1 To i - 1) ***this shouldn't be necessary
    With Me.ListBox1
        .Clear
        .List = FileList
    End With
End Sub
Public Sub listDir(strPath As String, lngSheet As Long)
Dim strFn As String
Dim strDirList() As String
Dim lngArrayMax, x As Long
lngArrayMax = 0
strFn = Dir(strPath & "*.*", 23)
While strFn <> ""
    If strFn <> "." And strFn <> ".." Then
        If (GetAttr(strPath & strFn) And vbDirectory) = vbDirectory Then
            If IsEmpty(FolderList) Then
                ReDim FolderList(1 To 1) As String
            Else
                ReDim Preserve FolderList(1 To UBound(FolderList) + 1) As String
            End If
            FolderList(UBound(FolderList)) = strPath & strFn & "\"
            lngArrayMax = lngArrayMax + 1
            ReDim Preserve strDirList(lngArrayMax)
            strDirList(lngArrayMax) = strPath & strFn & "\"
        End If
    End If
    strFn = Dir()
Wend
If lngArrayMax <> 0 Then
    For x = 1 To lngArrayMax
        Call listDir(strDirList(x), lngSheet)
    Next
End If
End Sub

I actually created a dummy UserForm with your fields to test this and it appears to work on my local C: drive.
 
Upvote 0
Hey that is excellent Biocide, works a treat. Thank you for taking the time to help.

It has however now caused another script to fail.

The below code would open the selected file when clicked but this is assuming that the file is located in the FilePath which it won't necessarily be if it is in a subfolder.

Can you think of a workaround?

Code:
Private Sub Listbox1_Click()
'On Error Resume Next
    Dim i   As Long, wb As Workbook
    With Me.ListBox1
        For i = 0 To .ListCount - 1
            If .Selected(i) = True Then
                Set wb = Workbooks.Open(FilePath & .List(i), UpdateLinks:=0)
                wb.Activate
                Unload SearchForm
                Exit For
            End If
        Next
    End With
    
End Sub
 
Upvote 0
OK. That is not a major problem, but it will require some minor changes to your ListBox in the form.
To simplify the process, I am setting these new properties in the code, however, you can also adjust them directly in the ListBox properties of the form (there is really no advantage to one over the other, however, without me knowing your listbox width and such, it was easier for me to just do it programmatically)

Here is the updated code.

NOTE: I did notice I made a change to your Search parameters where my code accepts any .xls* workbook (.xls, .xlsx, .xlsm, etc). If you don't want this, you will need to remove the trailing asterisk.

Rich (BB code):
Public FolderList As Variant
Private Sub UserForm_Initialize()
On Error Resume Next
    Dim FileList() As String
    Dim i As Long, n As Long, j As Long
    Dim x As Variant    'not sure what this was for
    Dim fName As String, fldrName As String, FilePath As String
    FilePath = "C:\Users\Russel\Documents\Projects\"
    ReDim FolderList(1 To 1) As String
    FolderList(1) = FilePath
    
    listDir FilePath, 1 'loads array FolderList with folders/subfolders
    i = 1
    For j = 1 To UBound(FolderList)
        fName = Dir(FolderList(j) & "*" & SearchBox.Value & "*.xls*")
        Do While fName <> ""
            ReDim Preserve FileList(1 To 2, 1 To i) As String
            FileList(1, i) = FolderList(j) & fName
            FileList(2, i) = fName
            i = i + 1
            fName = Dir()
        Loop
    Next j
    
    'ReDim Preserve FileList(1 To i - 1) ***this shouldn't be necessary
    With Me.ListBox1
        .BoundColumn = 1
        .ColumnCount = 2
        .ColumnWidths = "0 pt; " & .Width & " pt"
        .Clear
        .List = Application.Transpose(FileList)
    End With
End Sub
Public Sub listDir(strPath As String, lngSheet As Long)
Dim strFn As String
Dim strDirList() As String
Dim lngArrayMax, x As Long
lngArrayMax = 0
strFn = Dir(strPath & "*.*", 23)
While strFn <> ""
    If strFn <> "." And strFn <> ".." Then
        If (GetAttr(strPath & strFn) And vbDirectory) = vbDirectory Then
            If IsEmpty(FolderList) Then
                ReDim FolderList(1 To 1) As String
            Else
                ReDim Preserve FolderList(1 To UBound(FolderList) + 1) As String
            End If
            FolderList(UBound(FolderList)) = strPath & strFn & "\"
            lngArrayMax = lngArrayMax + 1
            ReDim Preserve strDirList(lngArrayMax)
            strDirList(lngArrayMax) = strPath & strFn & "\"
        End If
    End If
    strFn = Dir()
Wend
If lngArrayMax <> 0 Then
    For x = 1 To lngArrayMax
        Call listDir(strDirList(x), lngSheet)
    Next
End If
End Sub
Private Sub Listbox1_Click()
'On Error Resume Next
    Dim i   As Long, wb As Workbook
    With Me.ListBox1
        For i = 0 To .ListCount - 1
            If .Selected(i) = True Then
                Set wb = Workbooks.Open(.List(i), UpdateLinks:=0)
                wb.Activate
                Unload SearchForm
                Exit For
            End If
        Next
    End With
    
End Sub

The way this works is by making the listbox a two column field. The first column is the FullPath + FileName of the workbook and the second column is just the FileName
The first column is then set to a width of 0 so the only field that is visible on the form is the 2nd field (so it LOOKS identical to how it was before). Since the BoundColumn is the 1st column, however, any clicks on the FileName passes the value associated with it in the 1st column (FullPath + FileName) back.

If you have any questions about anything let me know.
 
Upvote 0
That is excellent, you have done great!


Really appreciate the time you have spent. I'd like to pick your brains on something else, totally understand if you don't have the time to help further but this is your project now and you understand it!


The textbox named searchbox filters the results in the listbox as a sort of search as you type filter.
It worked fine when there were a few excel file results but it struggles when there were lots.


I put your first code in the SearchBox_Change event to achieve this:


Code:
Private Sub SearchBox_Change()
On Error Resume Next
    Dim FileList() As String
    Dim i As Long, n As Long, j As Long
    Dim x As Variant    'not sure what this was for
    Dim fName As String, fldrName As String, FilePath As String
    FilePath = "C:\Users\Russel\Documents\Projects\"
    ReDim FolderList(1 To 1) As String
    FolderList(1) = FilePath
    
    listDir FilePath, 1 'loads array FolderList with folders/subfolders
    i = 1
    For j = 1 To UBound(FolderList)
        fName = Dir(FolderList(j) & "*" & SearchBox.Value & "*.xls*")
        Do While fName <> ""
            ReDim Preserve FileList(1 To i) As String
            FileList(i) = fName
            i = i + 1
            fName = Dir()
        Loop
    Next j
    'ReDim Preserve FileList(1 To i - 1) ***this shouldn't be necessary
    With Me.ListBox1
        .Clear
        .List = FileList
    End With
End Sub

Is there a better way of doing this so it would just filter the listbox results rather than actually searching for the files again?
I don't know whether it might need to store the results somewhere temporarily to achieve this and I can't do this in a worksheet because i'm developing it as an add-in.


Would really value any suggestions you had?


Russel
 
Upvote 0
Actually, I love this type of stuff (why I'm here in the first place).
Again, this isn't very difficult, but requires some minor changes to what we were already doing. For instance, the FileList variable needs to be defined Globally (so it remains in memory even after the Sub finishes execution).

Also, I noticed an unexpected problem with my Application.Transpose section where it wasn't working when there was only a single file returned so I am am using .Column to set the ListBox values instead of .List

Rich (BB code):
Public FolderList As Variant
Public FileList As Variant  'setting this as global so it stays loaded.
Private Sub UserForm_Initialize()
On Error Resume Next
    ReDim FileList(1 To 2, 1 To 1) As String    'reinitialize FileList
    Dim i As Long, n As Long, j As Long
    Dim x As Variant    'not sure what this was for
    Dim fName As String, fldrName As String, FilePath As String
    FilePath = "C:\Users\Russel\Documents\Projects\"
    ReDim FolderList(1 To 1) As String          'reinitialize FolderList
    FolderList(1) = FilePath
    
    listDir FilePath, 1 'loads array FolderList with folders/subfolders
    i = 1
    For j = 1 To UBound(FolderList)
        fName = Dir(FolderList(j) & "*" & SearchBox.Value & "*.xls*")
        Do While fName <> ""
            ReDim Preserve FileList(1 To 2, 1 To i) As String
            FileList(1, i) = FolderList(j) & fName
            FileList(2, i) = fName
            i = i + 1
            fName = Dir()
        Loop
    Next j
    
    'ReDim Preserve FileList(1 To i - 1) ***this shouldn't be necessary
    With Me.ListBox1
        .BoundColumn = 1
        .ColumnCount = 2
        .ColumnWidths = "0 pt; " & .Width & " pt"
        .Clear
        .Column = FileList
    End With
End Sub

Public Sub listDir(strPath As String, lngSheet As Long)
Dim strFn As String
Dim strDirList() As String
Dim lngArrayMax, x As Long
lngArrayMax = 0
strFn = Dir(strPath & "*.*", 23)
While strFn <> ""
    If strFn <> "." And strFn <> ".." Then
        If (GetAttr(strPath & strFn) And vbDirectory) = vbDirectory Then
            If IsEmpty(FolderList) Then
                ReDim FolderList(1 To 1) As String
            Else
                ReDim Preserve FolderList(1 To UBound(FolderList) + 1) As String
            End If
            FolderList(UBound(FolderList)) = strPath & strFn & "\"
            lngArrayMax = lngArrayMax + 1
            ReDim Preserve strDirList(lngArrayMax)
            strDirList(lngArrayMax) = strPath & strFn & "\"
        End If
    End If
    strFn = Dir()
Wend
If lngArrayMax <> 0 Then
    For x = 1 To lngArrayMax
        Call listDir(strDirList(x), lngSheet)
    Next
End If
End Sub

Private Sub Listbox1_Click()
'On Error Resume Next
    Dim i   As Long, wb As Workbook
    With Me.ListBox1
        For i = 0 To .ListCount - 1
            If .Selected(i) = True Then
                Set wb = Workbooks.Open(.List(i), UpdateLinks:=0)
                wb.Activate
                Unload SearchForm
                Exit For
            End If
        Next
    End With
End Sub

Private Sub SearchBox_Change()
Dim filteredFileList As Variant
    For i = 1 To UBound(FileList, 2)
        'If FileList(2, i) Like "*" & SearchBox.Value & "*" Then     'Use this instead of the following row if you want CASE-SENSITIVE lookup
         If UCase(FileList(2,i) Like "*" & UCase(SearchBox.Value) & "*" Then  'Use this row for CASE-INSENSITIVE lookup
            If IsEmpty(filteredFileList) Then
                ReDim filteredFileList(1 To 2, 1 To 1) As String
            Else
                ReDim Preserve filteredFileList(1 To 2, 1 To UBound(filteredFileList, 2) + 1) As String
            End If
            
            filteredFileList(1, UBound(filteredFileList, 2)) = FileList(1, i)
            filteredFileList(2, UBound(filteredFileList, 2)) = FileList(2, i)
        End If
        
        
    Next i
    If IsEmpty(filteredFileList) Then
        Me.ListBox1.Clear
    Else
        With Me.ListBox1
            .BoundColumn = 1
            .ColumnCount = 2
            .ColumnWidths = "0 pt; " & .Width & " pt"
            .Clear
            .Column = filteredFileList
        End With
    End If
End Sub

BTW, I created a dummy form of this to test as I have been working on it, and I really like the natural feel of how this instantly refreshes the file list in the ListBox.
 
Last edited:
Upvote 0
You make it look so easy! I really need to develop my knowledge of VB. I understand some of the script just not everything.

I have one last request, just to make this work perfectly.

At the moment it is searching for *.xls* in the filepath C:\Users\Russel\Documents\Projects\ including any subfolders.

As well as searching for the excel worksheets *.xls*, I would also like the searchbox to search the names of the subfolders which contain the *.xls* files.

E.g. so if the user searches "test" it would find the following:

C:\Users\Russel\Documents\Projects\test1\work.xls
C:\Users\Russel\Documents\Projects\testing\result.xls
C:\Users\Russel\Documents\Projects\another\test.xls
C:\Users\Russel\Documents\Projects\test5.xls

But it should only display the name of any subfolders after the filepath, so based on the above example it would show the following results:

test1\work.xls
testing\result.xls
another\test.xls
test5.xls

So it shows the location of the file, just not the beginning C:\Users\Russel\Documents\Projects\

I would be extremely happy if that could work!

Thanks again for your time and expertise!

Russel
 
Upvote 0
Again, just a slight change since we already have all of the information we need.

In the UserForm_Initialize Sub change the line in red from the code snippet below

Rich (BB code):
        Do While fName <> ""
            ReDim Preserve FileList(1 To 2, 1 To i) As String
            FileList(1, i) = FolderList(j) & fName
            FileList(2, i) = Replace(FileList(1, i), FilePath, "")
            i = i + 1
            fName = Dir()
        Loop

One thing to note is that with this change, searched text will also lookup the foldername portion.
For instance, if the SearchBox value = "temp"

Then the listbox would contain both of the following results
Temp/MyWorkbook.xls
Other/MyTempWorkbook.xls

If you do not wish to include folder name in the search lookup, I can amend the SearchBox_Change Event code, but wanted to see if you even wanted this before I went that route.
 
Upvote 0

Forum statistics

Threads
1,203,201
Messages
6,054,088
Members
444,702
Latest member
patrickmg17

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top