Option Strict
Option Explicit
Imports System
Imports System.IO
Imports System.Threading
Imports System.Windows.Forms
Namespace Microsoft.Samples.DirectorySearcher
' <summary>
' This class is a Windows Forms control that implements a simple directory searcher.
' You provide, through code, a search string and it will search directories on
' a background thread, populating its list box with matches.
' </summary>
Public Class DirectorySearcher
Inherits Control
' Define a special delegate that handles marshaling
' lists of file names from the background directory search
' thread to the thread that contains the list box.
Delegate Sub FileListDelegate(files() As String, startIndex As Integer, count As Integer)
Private _listBox As ListBox
Private _searchCriteria As String
Private _searching As Boolean
Private _deferSearch As Boolean
Private _searchThread As Thread
Private _fileListDelegate As FileListDelegate
Private _onSearchComplete As EventHandler
Public Sub New()
_listBox = New ListBox()
_listBox.Dock = DockStyle.Fill
Controls.Add(_listBox)
_fileListDelegate = New FileListDelegate(AddressOf AddFiles)
_onSearchComplete = New EventHandler(AddressOf OnSearchComplete)
End Sub
Public Property SearchCriteria() As String
Get
Return _searchCriteria
End Get
Set
' If currently searching, abort
' the search and restart it after
' setting the new criteria.
'
Dim wasSearching As Boolean = Searching
If wasSearching Then
StopSearch()
End If
_listBox.Items.Clear()
_searchCriteria = value
If wasSearching Then
BeginSearch()
End If
End Set
End Property
Public ReadOnly Property Searching() As Boolean
Get
Return _searching
End Get
End Property
Public Event SearchComplete As EventHandler
' <summary>
' This method is called from the background thread. It is called through
' a BeginInvoke call so that it is always marshaled to the thread that
' owns the list box control.
' </summary>
' <param name="files"></param>
' <param name="startIndex"></param>
' <param name="count"></param>
Private Sub AddFiles(files() As String, startIndex As Integer, count As Integer)
While count > 0
count -= 1
_listBox.Items.Add(files((startIndex + count)))
End While
End Sub
Public Sub BeginSearch()
' Create the search thread, which
' will begin the search.
' If already searching, do nothing.
'
If Searching Then
Return
End If
' Start the search if the handle has
' been created. Otherwise, defer it until the
' handle has been created.
If IsHandleCreated Then
_searchThread = New Thread(New ThreadStart(AddressOf ThreadProcedure))
_searching = True
_searchThread.Start()
Else
_deferSearch = True
End If
End Sub
Protected Overrides Sub OnHandleDestroyed(e As EventArgs)
' If the handle is being destroyed and you are not
' recreating it, then abort the search.
If Not RecreatingHandle Then
StopSearch()
End If
MyBase.OnHandleDestroyed(e)
End Sub
Protected Overrides Sub OnHandleCreated(e As EventArgs)
MyBase.OnHandleCreated(e)
If _deferSearch Then
_deferSearch = False
BeginSearch()
End If
End Sub
' <summary>
' This method is called by the background thread when it has
' finished the search.
' </summary>
' <param name="sender"></param>
' <param name="e"></param>
Private Sub OnSearchComplete(sender As Object, e As EventArgs)
RaiseEvent SearchComplete(sender, e)
End Sub
Public Sub StopSearch()
If Not _searching Then
Return
End If
If _searchThread.IsAlive Then
_searchThread.Abort()
_searchThread.Join()
End If
_searchThread = Nothing
_searching = False
End Sub
' <summary>
' Recurses the given path, adding all files on that path to
' the list box. After it finishes with the files, it
' calls itself once for each directory on the path.
' </summary>
' <param name="searchPath"></param>
Private Sub RecurseDirectory(searchPath As String)
' Split searchPath into a directory and a wildcard specification.
'
Dim directoryPath As String = Path.GetDirectoryName(searchPath)
Dim search As String = Path.GetFileName(searchPath)
' If a directory or search criteria are not specified, then return.
'
If directoryPath Is Nothing Or search Is Nothing Then
Return
End If
Dim files() As String
' File systems like NTFS that have
' access permissions might result in exceptions
' when looking into directories without permission.
' Catch those exceptions and return.
Try
files = Directory.GetFiles(directoryPath, search)
Catch e As UnauthorizedAccessException
Return
Catch e As DirectoryNotFoundException
Return
End Try
' Perform a BeginInvoke call to the list box
' in order to marshal to the correct thread. It is not
' very efficient to perform this marshal once for every
' file, so batch up multiple file calls into one
' marshal invocation.
Dim startingIndex As Integer = 0
While startingIndex < files.Length
' Batch up 20 files at once, unless at the
' end.
'
Dim count As Integer = 20
If count + startingIndex >= files.Length Then
count = files.Length - startingIndex
End If
' Begin the cross-thread call. Because you are passing
' immutable objects into this invoke method, you do not have to
' wait for it to finish. If these were complex objects, you would
' have to either create new instances of them or
' wait for the thread to process this invoke before modifying
' the objects.
Dim r As IAsyncResult = BeginInvoke(_fileListDelegate, New Object() {files, startingIndex, count})
startingIndex += count
End While
' Now that you have finished the files in this directory, recurse
' for each subdirectory.
Dim directories As String() = Directory.GetDirectories(directoryPath)
Dim d As String
For Each d In directories
RecurseDirectory(Path.Combine(d, search))
Next d
End Sub
'/ <summary>
'/ This is the actual thread procedure. This method runs in a background
'/ thread to scan directories. When finished, it simply exits.
'/ </summary>
Private Sub ThreadProcedure()
' Get the search string. Individual
' field assigns are atomic in .NET, so you do not
' need to use any thread synchronization to grab
' the string value here.
Try
Dim localSearch As String = SearchCriteria
' Now, search the file system.
'
RecurseDirectory(localSearch)
Finally
' You are done with the search, so update.
'
_searching = False
' Raise an event that notifies the user that
' the search has terminated.
' You do not have to do this through a marshaled call, but
' marshaling is recommended for the following reason:
' Users of this control do not know that it is
' multithreaded, so they expect its events to
' come back on the same thread as the control.
BeginInvoke(_onSearchComplete, New Object() {Me, EventArgs.Empty})
End Try
End Sub
End Class
End Namespace