Visual Basic: Drive Searcher
Finding files with Visual Basic
A few times a month, I will see people asking in the forums on how to search the hard drive for a file. In the past, I would normally refer them to an old thread where I had provided a link to a file scan utility I had written. I got to thinking about it and I thought it was high time that I take that example and turn it into a re-usable class that could be easily integrated into someone else's search interface.
Source Code
Please be aware that I will be providing a complete example project on how to use the 'DriveSearcher' class, but the interface only shows how to use the DriveSearcher class.
I would also like to note that if you wish to see the comments for the code, that you will need to download the entire sample project from the MSDN Gallery, as this article is more of a supporting explanation for what the DriveSearcher does.
Download
You can download the sample project at the following link:
Please be sure to vote 5 stars while you're there!
Introducing the DriveSearcher Class
The drive searcher class automatically runs on its own thread. This means that your application will remain responsive while the drive searcher performs its search routines. When calling the DriveSearcher.SearchDrive Subroutine, you will notice that this routine is a shared routine. This means that you need not construct an instance of the DriveSearcher class. This class will create its own internal instances each time the DriveSearcher.SearchDrive method is called. We will get more into that later.
search parameters
In order to search a drive, you need only call the DriveSearcher.SearchDrive subroutine. Before you are able to call that subroutine, you need to be able to pass the required parameter to the sub(ByVal params As SearchParamaters).
What is the Object Type SearchParamters?
This is a special class that was created to complement the use of DriveSearcher. This object reduces the size of the parameter for the DriveSearcher.SearchDrive method. This object also contains all of the fields that are required to instantiate a search and to handle the events of the search.
Let us examine the SearchParamaters Class:
01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class SearchParamaters
05. Inherits EventArgs
06. Private _DriveRootPath As String
07. Private _FindFile As String
08. Private _Invoker As Control
09. Public FileFoundEventHandler As DriveSearcher.FileFoundEventHandler
10. Public SearchStatusChangedEventhandler As DriveSearcher.StatusChangedEventHandler
11. Public ReadOnly Property DriveRootPath As String
12. Get
13. Return _DriveRootPath
14. End Get
15. End Property
16. Public ReadOnly Property FindFile As String
17. Get
18. Return _FindFile
19. End Get
20. End Property
21. Public ReadOnly Property Invoker As Control
22. Get
23. Return _Invoker
24. End Get
25. End Property
26. Sub New(ByVal DriveRootPath As String, _
27. ByVal FindFile As String, _
28. ByVal Invoker As Control, _
29. Optional ByVal FileFoundEventHandler As DriveSearcher.FileFoundEventHandler = Nothing, _
30. Optional ByVal SearchStatusChangedEventhandler As DriveSearcher.StatusChangedEventHandler = Nothing)
31. If GetDrives.IndexOf(DriveRootPath.ToLower) < 0 Then
32. Throw New ArgumentException("The specified Drive Root Path is invalid.", "DriveRootPath")
33. End If
34. If Invoker Is Nothing Then Throw New ArgumentException("You must specify an invoker.", "Invoker")
35. Me._DriveRootPath = DriveRootPath
36. Me._FindFile = FindFile
37. Me._Invoker = Invoker
38. Me.FileFoundEventHandler = FileFoundEventHandler
39. Me.SearchStatusChangedEventhandler = SearchStatusChangedEventhandler
40. End Sub
41. Private Function GetDrives() As List(Of String)
42. Dim drives As IO.DriveInfo() = IO.DriveInfo.GetDrives()
43. Dim availableDrives As New List(Of String)
44. For Each drive As IO.DriveInfo In drives
45. Dim d As String = drive.RootDirectory.FullName
46. If d.Length = 3 Then
47. availableDrives.Add(d.ToLower)
48. End If
49. Next
50. Return availableDrives
51. End Function
52.End Class
First I will note that the first 3 lines are dealing with options. This is the recommended way to set your options in Visual Basic when possible. Also note that many people seem to have varying perceptions of what "when possible" means, especially when it comes to Option Strict...
The Following fields are a part of the SearchParameters Class:
- DriveRootPath
- Example = "C:\
- FindFile
- Examples:
- *.*
- *partialnam*.ext
- ?ileNa?e.ext
- ????.*
- exactfilename.ext
- Note how this uses the same search conventions as windows.
- Examples:
- Invoker
- As this is a multithreaded routine, there are delegates involved. Sooner or later, these delegates need to map back to your UI thread. When they do, they will need to know what your user UI thread is. That is where this field comes into play... More on this later.
- FileFoundEventHandler
- This is a delegate sub that you will assign using "AddressOf" that points to a subroutine located within your UI. This sub routine will handle each FileFound event that is raised, and what to do with the found file path, internally, using the Invoker.Invoke method, the DriveSearcher will invoke your FileFoundEventHander whenever a new location of a file that matches your query is discovered.
- SearchStatusChangedEventHandler
- This delegate sub works kind of like how the FileFoundEventHandler works, in that it is invoked internally from within the DriveSearcher when the status of the search changes.
- GetDrives(Function)
- This function is used exclusively(in this class) to ensure that you have not provided an unavailable drive letter.
- New (Constructor)
- Required Parameters(The DriveSearcher cannot work without this information)
- DriveRootPath
- FindFile
- Invoker
- Optional Parameters(The DriveSearcher can work without this information, but it's pointless...)
- FileFoundEventHandler
- SearchStatusChangedEventHandler
- Required Parameters(The DriveSearcher cannot work without this information)
I have made the above optional (and writable) for the following reasons:
Allow you to change or assign the event handlers at a time that is not the same time as the time that the parameters were created.
So that's pretty much it for the search parameters class. But wait, there were a couple of event handlers that you probably never heard of, considering that they're not part of Visual Basic or Visual Studio.
FileFoundEventHandler
This is our delegate sub for when a file is discovered, this is used for multi-threading.
This is declared as follows, and is located in our DriveSearcher class:
Public Delegate Sub FileFoundEventHandler(ByVal e As FileFoundEventArgs)
Pretty simple and straight forward, right?
Well look, part of its signature is "e as FileFoundEventArgs"
What are those? That's another class that is written just for the DriveSearcher class.
Here is that class:
01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class FileFoundEventArgs
05. Inherits EventArgs
06. Private _SearchQuery As String
07. Private _CurrentFoundFile As String
08. Private _AllFoundFiles As List(Of String)
09. Public ReadOnly Property SearchQuery As String
10. Get
11. Return _SearchQuery
12. End Get
13. End Property
14. Public ReadOnly Property FoundFile As String
15. Get
16. Return _CurrentFoundFile
17. End Get
18. End Property
19. Public ReadOnly Property AllFoundFiles As String()
20. Get
21. Return _AllFoundFiles.ToArray
22. End Get
23. End Property
24. Sub New(ByVal SearchQuery As String, ByVal FoundFile As String, ByVal AllFoundFiles As List(Of String))
25. Me._SearchQuery = SearchQuery
26. Me._CurrentFoundFile = FoundFile
27. Me._AllFoundFiles = AllFoundFiles
28. End Sub
29.End Class
The FileFoundEventArgs class contains the following members:
- SearchQuery
- This is your original search query.
- CurrentFoundFile
- This is the path of the file found that caused this event to be raised
- AllFoundFiles
- This is a list of all of the results for the current search query.
When FileFoundEventHandler is invoked, then a new instance of the FileFoundEventArgs class will be exposed to your FileFoundEventHandler, at that time, you can process those results however you want to.
StatusChangedEventHandler
This is our delegate sub for when the status of the search changes, this is used for multi-threading. This is declared as follows, and is located in our DriveSearcher class:
Public Delegate Sub StatusChangedEventHandler(ByVal e As SearchStatusEventArgs)
Again, pretty simple and straight forward, right?
This method exposes an object called SearchStatusEventArgs
Again, this is something that is written specifically for the DriveSearcher class, so lets do a quick overview:
Here is the code:
01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Public Class SearchStatusEventArgs
05. Inherits EventArgs
06. Private _SearchStatus As Status
07. Private _FilesComparedSoFar As Integer
08. Private _DirectoriesSearchedSoFar As Integer
09. Private _UnsearchableDirectories As Integer
10. Public ReadOnly Property SearchStatus As Status
11. Get
12. Return _SearchStatus
13. End Get
14. End Property
15. Public ReadOnly Property FilesComparedSoFar As Integer
16. Get
17. Return _FilesComparedSoFar
18. End Get
19. End Property
20. Public ReadOnly Property DirectoriesSearchedSoFar As Integer
21. Get
22. Return _DirectoriesSearchedSoFar
23. End Get
24. End Property
25. Public ReadOnly Property UnsearchableDirectories As Integer
26. Get
27. Return _UnsearchableDirectories
28. End Get
29. End Property
30. Public Sub New(ByVal NewStatus As Status, ByVal FilesSearched As Integer, ByVal DirectoriesSearchedSoFar As Integer, ByVal UnsearchableDirectories As Integer)
31. Me._SearchStatus = NewStatus
32. Me._FilesComparedSoFar = FilesSearched
33. Me._DirectoriesSearchedSoFar = DirectoriesSearchedSoFar
34. Me._UnsearchableDirectories = UnsearchableDirectories
35. End Sub
36.End Class
The SearchStatusEventArgs class contains the following members:
SearchStatus
This is an instance of the following Enum
Public Enum Status
NotStarted
Initializing
GettingSubDirectories
GettingFiles
ComparingFileNames
Complete
End Enum
FilesComparedSoFar
This integer is incremented each time the search query is compared to a file name
DirectoriesSearchedSoFar
This integer is incremented each time a list of files from a directory is searched.
UnsearchableDirectories
This integer is incremented each time an "Access denied" error occurs when attempting to read the contents of a directory.
DriveSearcher Class
Now you have read about the supporting classes that are needed in order to use the DriveSearcher class.
Now we will do a quick overview of the DriveSearcher class and how it works
01.Option Strict On
02.Option Explicit On
03.Option Infer Off
04.Imports System.Threading
05.Imports System.IO
06.Public Class DriveSearcher
07. Private Property QueuedDirectories As New List(Of String)
08. Private Property Results As New List(Of String)
09. Private Property SearchString As String
10. Private Property InvokingControl As Control
11. Private Property FileFound As FileFoundEventHandler
12. Private Property SearchStatus As StatusChangedEventHandler
13. Private Property FileSearchCount As Integer
14. Private Property DirectoryCount As Integer
15. Private Property UnsearchableDirectories As Integer
16. Public Delegate Sub FileFoundEventHandler(ByVal e As FileFoundEventArgs)
17. Public Delegate Sub StatusChangedEventHandler(ByVal e As SearchStatusEventArgs)
18. Private Sub SetStatus(ByVal Status As Status)
19. If Not Me.SearchStatus Is Nothing Then
20. Me.InvokingControl.Invoke(SearchStatus, _
21. New SearchStatusEventArgs(Status, FileSearchCount, DirectoryCount, UnsearchableDirectories))
22. End If
23. End Sub
24. Private Sub SearchMain()
25. Do
26. SetStatus(Status.GettingSubDirectories)
27. Dim nextDirectory As String = QueuedDirectories(0)
28. Select Case True
29. Case ChildDirectories(nextDirectory).GetType = GetType(Boolean)
30. UnsearchableDirectories += 1 'increment the error counter
31. Case ChildDirectories(nextDirectory).GetType = GetType(String())
32. For Each Directory As String In CType(ChildDirectories(nextDirectory), String())
33. DirectoryCount += 1
34. QueuedDirectories.Add(Directory)
35. Next
36. End Select
37. SetStatus(Status.ComparingFileNames)
38. Dim TryResult As Object = FilesInDirectory(nextDirectory)
39. Select Case True
40. Case TryResult.GetType = GetType(Boolean)
41. 'We already incremented for this access denied directory earlier
42. Case TryResult.GetType = GetType(String())
43. For Each f As String In CType(TryResult, String())
44. FileSearchCount += 1
45. Dim filename As String = System.IO.Path.GetFileName(f)
46. If filename.ToLower Like SearchString.ToLower Then
47. Results.Add(f)
48. InvokingControl.Invoke(FileFound, New FileFoundEventArgs(Me.SearchString, f, Results))
49. End If
50. Next
51. End Select
52. QueuedDirectories.RemoveAt(0)
53. Loop Until QueuedDirectories.Count = 0
54. SetStatus(Status.Complete)
55. End Sub
56. Private Function FilesInDirectory(ByVal Folder As String) As Object
57. SetStatus(Status.GettingFiles)
58. Try : Return Directory.GetFiles(Folder, "*.*", SearchOption.TopDirectoryOnly)
59. Catch : Return False
60. End Try
61. End Function
62. Private Function ChildDirectories(ByVal Folder As String) As Object
63. Try : Return Directory.GetDirectories(Folder, "*", SearchOption.TopDirectoryOnly)
64. Catch : Return False
65. End Try
66. End Function
67. Public Shared Sub SearchDrive(ByVal params As SearchParamaters)
68. Dim searcher As New DriveSearcher
69. With searcher
70. .SearchStatus = params.SearchStatusChangedEventhandler
71. .InvokingControl = params.Invoker
72. .FileFound = params.FileFoundEventHandler
73. .QueuedDirectories.Clear()
74. .QueuedDirectories.Add(params.DriveRootPath)
75. .SearchString = params.FindFile
76. End With
77. If Not params.SearchStatusChangedEventhandler Is Nothing Then
78. params.Invoker.Invoke(params.SearchStatusChangedEventhandler, _
79. New SearchStatusEventArgs(Status.Initializing, 0, 0, 0))
80. End If
81. Dim searchThread As New Thread(AddressOf searcher.SearchMain)
82. searchThread.Start()
83. End Sub
84. Public Shared Function GetDrives() As List(Of String)
85. Dim drives As IO.DriveInfo() = IO.DriveInfo.GetDrives()
86. Dim availableDrives As New List(Of String)
87. For Each drive As IO.DriveInfo In drives
88. Dim d As String = drive.RootDirectory.FullName
89. If d.Length = 3 Then
90. availableDrives.Add(d)
91. End If
92. Next
93. Return availableDrives
94. End Function
95.End Class
First, we will go over the properties/variables of the DriveSearcher class
- QueuedDirectories
- This is a list of directories that still need to be searched for the query and other for directories to search
- Results
- This list contains all of the results from the search
- InvokingControl
- This is the main form that will be processing the events for the DriveSearcher
- FileFound
- This is the FileFoundEventDelegate assigned to handle the implied File Found Event
- SearchString
- This is the original search string
- SearchStatus
- This is the StatusChangedEventHandler assigned to handle the implied Status Changed event
- FileSearchCount
- This is the counter that is incremented each time a filename is compared to the query
- DirectoryCount
- This counter is incremented each directory that has been searched
- Unsearchable Directories
- This counter is incremented for each directory that is found to be inaccessible
DriveSearcher Methods
- SetStatus
- This sub will invoke the StatusChangedEventHandler that the user-provided and in the process new SearchStatusEventArgs will be created. This invokes the user-supplied code for the StatusChangedEventHandler
- SearchMain
- This is the main search loop, One loop cycle will:
- Add all subdirectories of the current directory to the queue
- Compare all files of the current directory to the search string
- Increment the counters
- Invoke event handlers
- Remove the current directory from the queue before starting a new cycle
- This is the main search loop, One loop cycle will:
- FilesInDirectory
- This sub will return the following
- A boolean value(false) if the directory is "Access denied"
- A list of files located in that directory
- This sub will return the following
- ChildDirectories
- This sub will return the following
- A boolean value(false) if the directory is "Access denied"
- A list of directories located in that directory
- This sub will return the following
- SearchDrive
- This method
- Declares a non-shared DriveSearcher object
- Applies the parameters to the DriveSearcher object
- Invokes the StatusChangedEventHandler, to reflect that the search is initializing
- Creates a new thread with an entry point of searchMain
- Starts the search thread
- This method
- GetDrives
- Returns a list of valid 'DriveRootPaths', these are valid strings that can be passed as the "DriveRootPath" parameter for the constructor of the object called "SearchParameters".
Resources
- MSDN - http://msdn.microsoft.com/en-US/
- MSDN Forums - http://social.msdn.microsoft.com/Forums/en-US/categories