Introduction
This is a class I created to recursively search a directory and all sub-directories.
Background
I needed to search for all files in a chosen directory and sub-directories and display them in a list box. But I also needed my Form to be responsive and tell the user that it's working. So I built a class which accepts a BackgroundWorker
from a form and passes back detailed progress information to the form.
Using the Code
Basically, there are a bunch of counters, a "UserState
" struct to hold all status information to be passed back to the form, some properties, and the Search
method does all the heavy lifting.
There's also an EstimateNumSubDirectories
function which loops through all sub-directories up to 3 levels deep. This is so that the percent complete passed back to the BackgroundWorker.ReportProgress
method won't always be nearly 100%.
The Search Method
NOTE: This could be MUCH more easily done by using "System.IO.Directory.GetFiles(directoryToSearch, searchPattern, System.IO.SearchOption.AllDirectories)
". The SearchOption.AllDirectories
does all of the recursive stuff for you, but it doesn't send back update progress reports on a BackgroundWorker
so this didn't work out for me.
Basically it checks to see if the passed directory exists, makes sure the search pattern isn't empty, if a BackgroundWorker
is being used, it checks to see if you've cancelled processing, and if this is the first pass in the loop it runs the EstimateNumSubDirectories
method.
It then searches the directory passed to it for any files with the search pattern also passed to it, gets a list of sub-directories, and each sub-directory gets passed back into the method for another pass until there are no sub-directories left. So it will go all the way down a tree, and then keep going up and down the tree until no branches are left.
IMPORTANT: Since the EstimateNumSubDirectories
method has already got the number of directories found 3 levels deep, the Search
method only adds to the number of directories found if you're more than 3 levels deep.
Here's the code:
public void Search(string directoryToSearch, string searchPattern)
{
if (!System.IO.Directory.Exists(directoryToSearch))
{
MessageBox.Show("The directory: " + directoryToSearch + " doesn't exist.",
"Directory Not Found", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (searchPattern == string.Empty)
{
MessageBox.Show("You must enter a search pattern.", "No Search Pattern",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
return;
}
if (_bw != null && _bw.CancellationPending)
{
return;
}
if (DirectoriesFound == 0)
{
if (_bw != null && _bw.WorkerReportsProgress)
{
UserState us = new UserState(Operations.Estimating);
_bw.ReportProgress(bwPercentComplete, us);
}
EstimateNumSubDirectories(directoryToSearch);
_CurrentNumDirectoryLevels = 0;
if (_bw != null && _bw.WorkerReportsProgress)
{
UserState us = new UserState(Operations.Searching, directoryToSearch);
_bw.ReportProgress(bwPercentComplete, us);
}
}
try
{
foreach (string fileName in System.IO.Directory.GetFiles
(directoryToSearch, searchPattern))
{
_NumFilesFound++;
if (_bw != null && _bw.WorkerReportsProgress)
{
UserState us = new UserState
(Operations.Searching, directoryToSearch, fileName);
_bw.ReportProgress(bwPercentComplete, us);
}
else
{
_FoundFileNames.Add(fileName);
}
}
if (_CurrentNumDirectoryLevels > 0)
_NumDirectoriesProcessed++;
if (_bw != null && _bw.WorkerReportsProgress && _NumDirectoriesFound != 0)
{
bwPercentComplete = (int)Math.Round(((decimal)_NumDirectoriesProcessed /
(decimal)_NumDirectoriesFound) * 100);
UserState us = new UserState(Operations.Searching, directoryToSearch);
_bw.ReportProgress(bwPercentComplete, us);
}
string[] dirsFound = System.IO.Directory.GetDirectories(directoryToSearch);
if (_CurrentNumDirectoryLevels > _NumDirectoryLevelsToEstimate)
{
_NumDirectoriesFound += dirsFound.GetLength(0);
}
foreach (string subDir in dirsFound)
{
_CurrentNumDirectoryLevels++;
Search(subDir, searchPattern);
}
_CurrentNumDirectoryLevels -= 1;
}
catch (System.UnauthorizedAccessException)
{
_NumDirectoriesFound -= 1;
_CurrentNumDirectoryLevels -= 1;
}
}
The EstimateNumSubDirectories
method adds the number of sub-directories found under the directory passed but only 3 levels deep. The recursive part works the same as the Search
method in that it works its way down the tree passing each sub-directory back into the method:
private void EstimateNumSubDirectories(string directoryToSearch)
{
try
{
if (_CurrentNumDirectoryLevels <= _NumDirectoryLevelsToEstimate)
{
string[] dirsFound = System.IO.Directory.GetDirectories(directoryToSearch);
_NumDirectoriesFound += dirsFound.GetLength(0);
foreach (string subDir in dirsFound)
{
_CurrentNumDirectoryLevels++;
EstimateNumSubDirectories(subDir);
}
_CurrentNumDirectoryLevels -= 1;
}
else
{
_CurrentNumDirectoryLevels -= 1;
}
}
catch (System.UnauthorizedAccessException)
{
_CurrentNumDirectoryLevels -= 1;
}
}
The UserState
struct contains different status messages, the current operation (estimating or searching), the directory currently being processed, if a file was just found the file path, and 3 constructors:
public struct UserState
{
public string estimatingMessage;
public string searchingMessage;
public string directoryInProcess;
public Operations currentOperation;
public string foundFileName;
public UserState(Operations _currentOperation, string _directoryInProcess,
string _foundFileName)
{
estimatingMessage = "Estimating remaining time...";
searchingMessage = "Searching for documents...";
currentOperation = _currentOperation;
directoryInProcess = _directoryInProcess;
foundFileName = _foundFileName;
}
public UserState(Operations _currentOperation, string _directoryInProcess)
{
estimatingMessage = "Estimating remaining time...";
searchingMessage = "Searching for documents...";
currentOperation = _currentOperation;
directoryInProcess = _directoryInProcess;
foundFileName = string.Empty;
}
public UserState(Operations _currentOperation)
{
estimatingMessage = "Estimating remaining time...";
searchingMessage = "Searching for documents...";
currentOperation = _currentOperation;
directoryInProcess = string.Empty;
foundFileName = string.Empty;
}
}
To use the status updates back on the form, you can convert the UserState
back to the FileSearchClass.UserState
as shown below:
if (e.UserState != null && e.UserState is FileSearchClass.UserState)
{
FileSearchClass.UserState state = (FileSearchClass.UserState)e.UserState;
switch (state.currentOperation)
{
case FileSearchClass.Operations.Estimating:
this.xStatusLbl.Text = string.Format("Status: {0}", state.estimatingMessage);
break;
case FileSearchClass.Operations.Searching:
this.xStatusLbl.Text = string.Format("Status: {0}", state.searchingMessage);
if (state.foundFileName != string.Empty)
{
this.xFilesListBox.Items.Add(state.foundFileName);
this.xFilesListBox.Refresh();
}
break;
}
}
Points of Interest
Learning how to have a method loop back into itself was fun and could be useful in many scenarios. The main advantage to this solution is that it accepts a BackgroundWorker
and can send back progress updates to the form.
Another interesting point for me was to get a progress bar to work back on the form. But the progress bar would be nearly full all the time because for every directory you find, it gets processed right away. Using the estimation technique, you can get a good idea of how many directories you'll need to process and add to your estimation until you're finished.
History
- 2nd September, 2009: My first revision
- 30th June, 2010: Article updated