Introduction
On a Windows Vista PC, I tried to search my hard disk for a file that contains a certain string. This was not possible using Windows Explorer. So, I decided to write my own file searcher. And, here it is...
What It Does
You must enter a search directory, so the program knows where to search for files and directories. If you check the "Include subdirectories" checkbox, the program will recursively search in all subdirectories of the search directory. The filename specified can be something like "*.wav;*.mp3;Christma??ree.*". The program will list all files and directories matching these filenames.
You can also use some restrictions to restrict the found items. Each restriction must be activated by checking the appropriate checkbox, and the necessary parameter for this restriction can be entered to the right of the checkbox.
- "Files newer than" will only list the items that have a
LastWriteTime
greater than the parameter. - "Files older than" will only list the items that have a
LastWriteTime
less than the parameter. - "Files containing the string" will only list the items that contain the
string
parameter you entered. The program will convert this string
parameter into a byte sequence, using either ASCII or Unicode encoding (depending on what you select), and then search each file for an occurrence of this byte sequence.
Click the "Start" button to start the search. The found items will be listed below. If the search takes too much time and you want to cancel it, you can click the "Stop" button to stop the search.
If you double-click an item that represents a file, the program will open this file within the application that is associated with the file's extension.
If you right-click an item and select "Open Containing Folder", the program will open the folder containing the file or directory in Windows Explorer.
If you want to write the search results to a text file, enter a delimeter that shall be used to separate items in the text file, and then click the button "Write results to text file...".
Using the Code
This application consists of two main parts:
- The
MainWindow
class does all the user interface stuff. - The
Searcher
class provides the business logic for searching FileSystemInfo
objects.
When the user clicks the "Start" button, the method Searcher.Start
is executed. It starts a new thread called SearchThread
. This thread searches for files and directories, matching the parameters that were entered by the user. If it finds a matching FileSystemInfo
object, it raises an asynchronous FoundInfo
event, so that the MainWindow
can extract the FileSystemInfo
object from the FoundInfoEventArgs
, and update its results list. When the thread ends, it sets the m_thread
member to null
. The Searcher.Start
method checks if m_thread
is null
every time it is executed, so there can never be more than one thread running at the same time.
When the user clicks the "Stop" button, the method Searcher.Stop
is executed. It sets the m_stop
member to true
, so that the SearchThread
can recognize this change and stop itself as soon as possible. Notice that this operation is thread-safe, because a boolean variable needs only one operation step to be set.
Important: In the Searcher_FoundInfo
event handler, the MainWindow
uses its Invoke
method to invoke the this_FoundInfo
method through a delegate. This way, the MainWindow
makes sure that the code for updating the results list is executed in the MainWindow
's thread, and not in the Searcher
's thread. Calling the this_FoundInfo
method directly would cause the application to crash, because the Searcher_FoundInfo
event handler is not synchronized to the GUI controls.
private delegate void FoundInfoSyncHandler(FoundInfoEventArgs e);
private FoundInfoSyncHandler FoundInfo;
...
private void MainWindow_Load(object sender, EventArgs e)
{
...
this.FoundInfo += new FoundInfoSyncHandler(this_FoundInfo);
...
}
...
private void Searcher_FoundInfo(FoundInfoEventArgs e)
{
if (!m_closing)
{
this.Invoke(FoundInfo, new object[] { e });
}
}
private void this_FoundInfo(FoundInfoEventArgs e)
{
CreateResultsListItem(e.Info);
}
The CreateResultsListItem
method creates and adds a new ListViewItem
to the results list, which shows the data contained in the FilesystemInfo
object. A FileSystemInfo
object can be either be a FileInfo
or a DirectoryInfo
, depending on what the Searcher
has found. The is
operator can be used to decide what kind of object it is. If it's a FileInfo
object, the list shall display the file's size in KB:
ListViewItem.ListViewSubItem lvsi = new ListViewItem.ListViewSubItem();
if (info is FileInfo)
{
lvsi.Text = GetBytesStringKB(((FileInfo)info).Length);
}
else
{
lvsi.Text = "";
}
When the Searcher
's thread ends, it raises a ThreadEnded
event, so the MainWindow
recognizes when the search has ended. The Searcher_ThreadEnded
event handler uses the Invoke
method the same way as the Searcher_FoundInfo
event handler:
private delegate void ThreadEndedSyncHandler(ThreadEndedEventArgs e);
private ThreadEndedSyncHandler ThreadEnded;
...
private void MainWindow_Load(object sender, EventArgs e)
{
...
this.ThreadEnded += new ThreadEndedSyncHandler(this_ThreadEnded);
...
}
...
private void Searcher_ThreadEnded(ThreadEndedEventArgs e)
{
if (!m_closing)
{
this.Invoke(ThreadEnded, new object[] { e });
}
}
private void this_ThreadEnded(ThreadEndedEventArgs e)
{
EnableButtons();
if (!e.Success)
{
MessageBox.Show(e.ErrorMsg,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation);
}
}
Points of Interest
You can use this code as a simple example for a multithreading application.
History
- April 6, 2009
- April 8, 2009
- Change: Uses the
Form.Invoke
method instead of a System.Timers.Timer
- New feature: "Open Containing Folder"
- April 24, 2009
- Bug-fix: Solved a dead lock bug that sometimes occured when the user clicks the "Stop" button.
- New feature: Supports multiple file names, separated by ";". Example: "*.wav;*.mp3".
- September 29, 2012
- Upgraded solution to Visual Studio 2012.
- New feature: Write the search results to a text file.