 .NET 1.1Detecting File Changes with FileSystemWatcher
If you are developing a Windows Explorer-style application or integrating with a legacy or third-party system that can only provide information via files, you may need to monitor the files within a folder. The FileSystemWatcher class permits this.
FileSystemWatcher Class
The FileSystemWatcher class is found in the System.IO namespace. This is an interesting class that provides all of the functionality required to monitor a directory on a disk and identify when its contents change. It achieves this by linking directly to the file system notifications provided by the Windows operating system and raising events when items change.
The FileSystemWatcher class provides four events that are raised to indicate a change to a file or folder. The four events and the activities that they signify are:
- Changed. Raised when a file or a folder is modified.
- Created. Raised when a new file or folder is created.
- Deleted. Raised when an existing file or folder is deleted.
- Renamed. Raised when an existing file or folder is renamed.
Creating the File System Monitor Project
In this article we will create a new Windows Forms project to demonstrate the use of the FileSystemWatcher class. This will be a simple application that monitors a folder for file changes. Whenever a change is made, it will be logged to a list box within the application's main form.
To begin, create a new Windows Forms project named "FileSystemMonitor".
Creating the File Monitor Form
Rename the automatically generated form to "FileMonitorForm" and add the following controls. You may wish to organise these controls into groups in the form and add descriptive labels. After designing your form, it should look similar to the image beneath the table.
| Control Name | Type | Special Properties | Purpose |
|---|
| FolderInput | TextBox | Text = "c:\Demo" | Allows the user to input the path to the folder that they wish to monitor for changes. | | ChangeLogList | ListBox | | Logs all of the events that have occurred when a file or folder has been created, modified, deleted or renamed. | | MonitoringInput | CheckBox | Checked = False Text = "Monitoring Enabled" | Enables and disables monitoring of the selected folder. | | FileFilterInput | TextBox | Text = "*.*" | Permits filtering of the items that are monitored, based upon file and folder names. |

Adding the FileSystemWatcher
To permit monitoring of file changes we need a FileSystemWatcher object. The object will be configured by form events when the path to monitor and the filtering options are changed, and when monitoring is enabled and disabled. To allow this configuration, the object must have a variable scope that is visible to all of the form's methods. It will therefore be created within the class' code block.
FileSystemWatcher appears in the System.IO namespace so ensure that you have using System.IO; at the top of your form code before adding the following declaration to the FileMonitorForm class:
private FileSystemWatcher _watcher;
Preventing Cross-Thread Operations
By default, when the FileSystemWatcher object raises notification events, the delegate calls are made on a thread from the system thread pool. This will generally not be the same thread as that being used to control the form. As the demonstration application will require that the file changes be logged within a visual element of the form, using the allocated thread to modify the list box contents would result in a cross-threading operation and an IllegalOperationException being thrown.
To force the FileSystemWatcher to use the thread that the form is operating under and to avoid errors, the SynchronizingObject property can be used. This property will be set to the form's class so that the form object is used to marshal the event handler calls. To set this property, modify the form's constructor as follows:
public FileMonitorForm()
{
InitializeComponent();
_watcher = new FileSystemWatcher();
_watcher.SynchronizingObject = this;
}
Trapping the File Change Events
The FileSystemWatcher raises four events in response to changes in the monitored folder. These provide information for file and folder creation, modification, deletion and renaming. The first three of these are based on the FileSystemEventHandler delegate. This delegate includes a FileSystemEventArgs parameter that provides information relating to the path and name of the file or folder modified.
The Renamed event is based on the RenamedEventHandler delegate. This delegate provides a RenamedEventArgs object with information describing the activity that occurred. The RenamedEventArgs class is derived from FileSystemEventArgs but adds extra properties to hold the old name of the file or folder.
To attach to the four events in the sample application, add the following four lines to the end of the constructor:
_watcher.Changed += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Created += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Deleted += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Renamed += new RenamedEventHandler(LogFileSystemRenaming);
We now need to create two methods to process the notifications. The first method will handle the Changed, Created and Deleted events. In each case, the information from the event arguments will be logged in the list box. The list will display the time of the change, the name of the file affected and the type of change that occurred.
Add the following method to log the changes. The log text is created using the String.Format method with the {0:G} placeholder indicating that the current date and time will be formatted using the user's preferred short date format and long time format. The FullPath property returns the full path of the changed file. The ChangeType property returns a value from the WatcherChangeTypes enumeration that is automatically converted to a readable string by the Format method.
void LogFileSystemChanges(object sender, FileSystemEventArgs e)
{
string log = string.Format("{0:G} | {1} | {2}"
, DateTime.Now, e.FullPath, e.ChangeType);
ChangeLogList.Items.Add(log);
}
The second method to add will process the event raised when a file or folder is renamed. This method is similar to the previous one except that the change type is always "Renamed". We will log the old name and new path and name for the changed item.
Add the following method to log renaming:
void LogFileSystemRenaming(object sender, RenamedEventArgs e)
{
string log = string.Format("{0:G} | {1} | Renamed from {2}"
, DateTime.Now, e.FullPath, e.OldName);
ChangeLogList.Items.Add(log);
}
Enabling Monitoring
When a FileSystemWatcher object is created, the event raising capabilities are disabled. To enable the events, the EnableRaisingEvents property is set to true. Resetting the property to false at any time halts the raising of file change events. However, before the events are enabled, the path of the directory to monitor must be set in the Path property.
In the demonstration program we will link the EnableRaisingEvents property to the "MonitoringInput" checkbox. It will then be possible to switch the events on and off as required. Whenever event raising is enabled, the path to monitor will also be set. At the same time, we will enable or disable the textboxes so that changes can only be made when the events are disabled. Although this is not a requirement, it will make the sample program simpler to develop.
To enable the checkbox functionality, add the following method and link it to the checkbox's CheckChanged event:
private void MonitoringInput_CheckedChanged(object sender, EventArgs e)
{
_watcher.Path = FolderInput.Text;
_watcher.EnableRaisingEvents = MonitoringInput.Checked;
FolderInput.Enabled = FileFilterInput.Enabled = !MonitoringInput.Checked;
}
NB: You would normally include validation to ensure that the monitored folder exists. To keep the example simple for this article, the validation code is not shown.
Testing the Program
You can now test the program. Once the running form is visible, change the contents of the textbox containing the path to monitor to a directory that exists on your hard disk. Use the MonitoringInput checkbox to enable the FileSystemWatcher and try creating, modifying, deleting and renaming files and folders within this directory to see the events being logged.
You may notice that some changes to files cause more than one event to be raised, depending upon the application used to edit the files. This is important to note as in some circumstances you would not want to handle the duplicated events. Note also that changes to the contents of a folder within the monitored directory cause Changed events to be raised. At the moment though, changes to subfolders at a deeper level do not raise events.
Buffer Size and the Error Event
The FileSystemWatcher class works by capturing all of the relevant file and older changes and placing them into a buffer. This is then processed one change at a time until all of the notifications have been dealt with and the buffer is empty. By default, the internal buffer has a size of eight kilobytes (8192 bytes). Each event can take up to sixteen bytes of the buffer for its data, not including the file name. This means that when there are a lot of changes in a short period of time, the buffer can quickly become overloaded and notifications can be lost.
One way to manage the buffer size problem is to change the maximum size that is permitted. If you know that your program is likely to process large numbers of file changes in a short period, you can change the value in the InternalBufferSize property. This property allows the size of the buffer to be specified as a number of bytes. Care should be taken as the buffer is held in memory that cannot be swapped to disk so this memory becomes unavailable to other programs. The size selected should be a multiple of 4096 to give the best performance.
Each time the buffer size limit is reached, the FileSystemWatcher object raises an Error event. This event allows you to identify that there has been a change but the details of the file or folder modified will not be available. Generally you would log this or create some kind of notification so that a recovery can be attempted.
To capture the Error event in the sample program, add the following line to the end of the form's constructor:
_watcher.Error += new ErrorEventHandler(LogBufferError);
To process this event, add the following method to the form's class:
void LogBufferError(object sender, ErrorEventArgs e)
{
string log = string.Format("{0:G} | Buffer limit exceeded", DateTime.Now);
ChangeLogList.Items.Add(log);
}
You can test this event by either reducing the buffer size of the FileSystemWatcher object or by manipulating large numbers of files within the monitored folder. (Creating copies of thousands of files can be used to overload the buffer.)
Filtering Monitored Files
When monitoring a folder for changes to a specific type of file or for files with certain names, a name filter can be applied to the FileSystemWatcher object. Once applied, events are only raised when the name of the file or folder that changed matches the filter condition. This has the potential benefit of reducing the buffer utilisation.
To apply a filter, the Filter property is set. If only a single file is to be monitored, the name of the file is specified. When a limited set of files or folders is required, wildcard characters can be included in the filter string. An asterisk symbol (*) is used to represent any number of characters and a question mark (?) is used to replace any single character. Some example filter strings are shown in the table below.
| Filter | Matches |
|---|
| (Empty string) | Any file or folder. | | *.* | Any file or folder | | *.doc | All files with a .doc extension. | | monitor.* | All files with a name of "monitor" and any extension | | abc*.* | All files that begin with "abc". | | abc?.* | All files with a four-letter filename that begins with abc and any extension. |
To apply file name filtering in the demonstration application, we will link the FileFilterInput text box's contents to the Filter property of the FileSystemWatcher. This will be done by adding a line of code to the method that detects changes in the checkbox's status. Alter the method as follows:
private void MonitoringInput_CheckedChanged(object sender, EventArgs e)
{
_watcher.Path = FolderInput.Text;
_watcher.Filter = FileFilterInput.Text;
_watcher.EnableRaisingEvents = MonitoringInput.Checked;
FolderInput.Enabled = FileFilterInput.Enabled = !MonitoringInput.Checked;
}
After adding the filtering code, test the application using various filters.
Filtering Monitored Actions
A second type of filtering can be applied to limit the types of change that cause a notification event to be raised. This filtering is applied by setting the NotifyFilter property of the FileSystemWatcher. The property accepts a value based upon the NotifyFilters enumeration. This enumeration has a FlagsAttribute attribute so the various values may be combined using bitwise logical operators. This allows you to enable more than one type of notification whilst ignoring others. using this type of filtering reduces the number of notifications being sent to the internal buffer and the associated risk of the buffer being filled.
The following constant values and their linked notification types are defined in the enumeration:
| Enumeration Constant | Notification Event For... |
|---|
| Attributes | Changes to the file or folder attributes. | | CreationTime | Changes to the creation time of a file or folder. | | DirectoryName | Changes to the name of a folder. | | FileName | Changes to the name of a file. | | LastAccess | Changes to the time that a file or folder was last opened. | | LastWrite | Changes to the time that a file or folder was last written to. | | Security | Changes to the file or folder security settings. | | Size | Changes to the file or folder size. |
These flags are included in the demo application and source code that can be downloaded using the link at the top of this article.
Monitoring Sub-Folders
The final property to be considered is IncludeSubdirectories. This Boolean value is used to indicate whether subfolders of the monitored directory should be monitored. When set to true, the entire hierarchy of folders beneath the specified path is observed and any changes cause events to be raised. This option should be used with care as it can lead to unexpectedly large numbers of notifications being added to the buffer, increasing the risk that it will be overfilled.
The property is included in the demo application and source code that can be downloaded using the link at the top of this article.
|