Click here to Skip to main content
Click here to Skip to main content

FileSystemWatcher - Pure Chaos (Part 1 of 2)

By , 17 Dec 2010
 

View Part 2 of this article series.

FileWatcherDemo

Introduction

One of my first official tasks as a DotNet programmer was to write a Windows Service which monitored a folder on a server for new files, processed those files, and then transferred them to another server (via FTP). I became immediately aware of the shortcomings of the FileSystemWatcher class, and those shortcomings have always kind stuck to my brain. A day or so ago, someone posted a question regarding a similar task, and I immediately suggested the same route I had taken. It was then, that the thought occurred to me that these FileSystemWatcher problems have never really be addressed (that I could find).

The Problem

My primary issue is that the FileSystemWatcher allows you to be notified that a file in a folder has changed, but not precisely what change was made. I found this particularly irritating since they provided eight notification filters to make the FileSystemWatcher send the Changed event. You could use one or more of these filters, but upon receiving the Changed event, there is no way to see what filter actually triggered the event. The ChangeType property in the FileSystemEventArgs parameter for the event merely indicated whether the item in question was changed, deleted, created, or renamed, and there is no property indicating the applicable filter in the case of the Changed event.

My Solution

I came to the realization that you would need up to NINE FileSystemWatcher objects to pull this off - one to handle all of the other ChangeTypes, and one for each of the eight NotifyFilter enumerators. You can pretty much guess that trying to manage that many FileSystemWatcher objects in a form would be excruciatingly painful. The technique presented in this article is in the form of a wrapper class than manages these individual FileSystemWatcher objects and provides a handy interface and event mechanism that can be used to cherry-pick the filters you want to use. Gone (I hope) are the days of multiple events that are fired when programs like Notepad create a file. You should now be able to pick and choose which events to handle, and when.

Something Borrowed - The FileSystemWatcherEx Class

While I was researching for this article, I stumbled across a CodeProject article by George Oakes, called Advanced FileSystemWatcher. In that article, George created an extension of the FileSystemWatcher class that allows it to monitor the folder being watched to make sure it's available. The reasons are laid out in his article, but I'll summarize his description by saying that if the watched directory somehow becomes inaccessible (maybe it's on a remote machine, maybe it was deleted), the FileSystemWatcher object doesn't recover - at all. The article was written in VB.Net, so I had to convert it to C#, and I also made the following modification.

From Timer To Thread

The original version used a Timer object to trigger verification of the folder's existence. I have an almost unnatural disdain for the Timer object (Windows timer events are the lowest priority event, and are NOT guaranteed to be sent on a very busy system), and prefer to use honest-to-god threads for this kind of work. So, the first thing we have to do is create the thread.

//--------------------------------------------------------------------------------
private void CreateThread()
{
    Interval = Math.Max(0, Math.Min(Interval, MaxInterval));
    if (Interval > 0)
    {
        thread              = new Thread(new ThreadStart(MonitorFolderAvailability));
        thread.Name         = Name;
        thread.IsBackground = true;
    }
}

The first line of the method normalizes the interval (how long the object waits before checking for the watched folder). The minimum acceptable value is 0 milliseconds, and the longest acceptable value is 60,000 milliseconds. A value of zero indicates that the programmer doesn't want to use this folder-checking functionality. The default value is 100 milliseconds. (This demo uses 250 milliseconds.)

Next, we need to create a thread method.

//--------------------------------------------------------------------------------
public void MonitorFolderAvailability()
{
    while (Run)
    {
        if (IsNetworkAvailable)
        {
            if (!Directory.Exists(base.Path))
            {
                IsNetworkAvailable = false;
                RaiseEventNetworkPathAvailablity();
            }
        }
        else
        {
            if (Directory.Exists(base.Path))
            {
                IsNetworkAvailable = true;
                RaiseEventNetworkPathAvailablity();
            }
        }
        Thread.Sleep(Interval);
    }
}

The method simply spins until it's told to stop, and during each cycle it checks to see if the watched folder exists. If the status changes, an event is sent that indicates the new status. The event handler code looks like this:

//////////////////////////////////////////////////////////////////////////////////////
public class FileSystemWatcherEx : FileSystemWatcher
{
    public  event  PathAvailabilityHandler EventPathAvailability = delegate{};

    //--------------------------------------------------------------------------------
    private void RaiseEventNetworkPathAvailablity()
    {
        EventPathAvailability(this, new PathAvailablitiyEventArgs(IsNetworkAvailable));
    }
}

//////////////////////////////////////////////////////////////////////////////////////
public class PathAvailablitiyEventArgs : EventArgs
{
	public bool PathIsAvailable { get; set; }
	public PathAvailablitiyEventArgs(bool available)
	{
		PathIsAvailable = available;
	}
}

//////////////////////////////////////////////////////////////////////////////////////
public delegate void PathAvailabilityHandler(object sender, PathAvailablitiyEventArgs e);

Other Minor Changes

I added a read-only variable that specifies the maximum allowed interval (in milliseconds), a way to name the file system watcher, and several constructor overloads to make the object more versatile.

//////////////////////////////////////////////////////////////////////////////////////
public class FileSystemWatcherEx : FileSystemWatcher
{
    // set a reasonable maximum interval time
    public readonly int MaxInterval = 60000;

    public  event  PathAvailabilityHandler EventPathAvailability = delegate{};
    private bool   IsNetworkAvailable = true;
    private int    Interval           = 100;
    private Thread thread             = null;
    public  string Name               = "FileSystemWatcherEx";
    private bool   Run                = false;

    #region Constructors
    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx():base()
    {
        CreateThread();
    }

    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx(string path):base(path)
    {
        CreateThread();
    }

    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx(int interval):base()
    {
        Interval = interval;
        CreateThread();
    }

    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx(string path, int interval):base(path)
    {
        Interval = interval;
        CreateThread();
    }

    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx(int interval, string name):base()
    {
        Interval = interval;
        Name     = name;
        CreateThread();
    }

    //--------------------------------------------------------------------------------
    public FileSystemWatcherEx(string path, int interval, string name):base(path)
    {
        Interval = interval;
        Name     = name;
        CreateThread();
    }
}

Like I said before, the credit for the original class extension goes to George Oakes. He rocks!

Support Classes

The classes which support the WatcherEx class are few. They're used to abstract out some of the housekeeping from the main class and keep things a bit more organized and reusable.

The WatcherInfo Class

This class is responsible for initializing the WatcherEx class.

////////////////////////////////////////////////////////////////////////////////////// 
public class WatcherInfo
{
    public string                  WatchPath         { get; set; }
    public bool                    IncludeSubFolders { get; set; }
    public bool                    WatchForError     { get; set; }
    public bool                    WatchForDisposed  { get; set; }
    public System.IO.NotifyFilters ChangesFilters    { get; set; }
    public WatcherChangeTypes      WatchesFilters    { get; set; }
    public string                  FileFilter        { get; set; }
    public uint                    BufferKBytes      { get; set; }
    // only applicable if using WatcherEx class
    public int                     MonitorPathInterval     { get; set; }

    //--------------------------------------------------------------------------------
    public WatcherInfo()
    {
        WatchPath           = "";
        IncludeSubFolders   = false;
        WatchForError       = false;
        WatchForDisposed    = false;
        ChangesFilters      = NotifyFilters.Attributes;
        WatchesFilters      = WatcherChangeTypes.All;
        FileFilter          = "";
        BufferKBytes        = 8;
        MonitorPathInterval = 0;
    }
}
  • WatchPath - This is the path that is watched by all of the internal FileSystemWatcherEx objects.
  • IncludeSubFolders - This value allows the programmer to specify whether or not to include subfolders during the watch.
  • WatchForError - If true, watches for Error events
  • WatchForDispose - If true, watches for Disposed events
  • ChangeFilters - This is a flags-base enumerator that allows the programmer to specify which NotifyFilters to monitor.
  • WatchesFilters - This is a flags-based enumerator that allows the programmer to specify which basic events to handle (Changed, Created, Deleted, Renamed, All).
  • FileFilter - This is the file mask of files to monitor. The default value is an empty string.
  • BufferKBytes - This is the desired size of the internal buffer.
  • MonitorPathInterval - This is the sleep interval between verifications that the watched folder exists. If this is set to 0, the Availability event will not be sent.

This class is generally created/modified in the subscribing object, and passed as a constructor parameter for the WatcherEx object.

The WatcherExEventArgs Class

Anytime you see a custom event in a program, chances are pretty good that they will require their own custom EventArgs-derived object. This particular example is no different. The reason you almost always want to create a custom argument object is so that you can pass specific data back to the event subscriber. The data we need to pass back follows.

  • The FileSystemWatcherEx that's sending the event - I know this seems redundant since the sender parameter is exactly the same thing but you can never send back too much info. :)
  • The original EventArgs-derived event argument - The WatcherEx class is essentially reflecting many different events, and some of them are of different types. Since we may want to be able to see the origial event arguments that were posted, we have to be able to pass them back as part of this class.
  • The EventArgs-derived argument type - This is represented as an enumerator for easier identification in the subscribing object. Instead of investigating the type, you can simply check this enumerator and cast more efficiently.
  • The NotifyFilters item that triggered the event - This would allow you to handle all Changed events in a single subscriber method, and decide how to handle the even from a switch statement.

Here's the class source:

////////////////////////////////////////////////////////////////////////////////////// 
public class WatcherExEventArgs
{
    public FileSystemWatcherEx Watcher    { get; set; }
    public object            Arguments  { get; set; }
    public ArgumentType      ArgType    { get; set; }
    public NotifyFilters     Filter     { get; set; }

    //------------------------------------------------------------------------------------
    public WatcherExEventArgs(FileSystemWatcherEx watcher, 
                              object              arguments,
                              ArgumentType        argType,
                              NotifyFilters       filter)
    {
        Watcher   = watcher;
        Arguments = arguments;
        ArgType   = argType;
        Filter    = filter;
    }

    //------------------------------------------------------------------------------------
    public WatcherExEventArgs(FileSystemWatcherEx watcher, 
                              object              arguments,
                              ArgumentType        argType)
    {
        Watcher   = watcher;
        Arguments = arguments;
        ArgType   = argType;
        Filter    = NotifyFilters.Attributes;
    }
}

The WatcherEx Class

This is the class that wraps all of the internal FileSystemWatcherEx objects. The first item of note is that the class inherits from IDisposable. The reason for this is that the FileSystemWatcher object is disposable, and I felt the need to control the disposing. (It may not even be necessary, but I'm doing it anyway.)

public class WatcherEx : IDisposable
{

There are very few member variables - just enough to keep track of our internal watchers, the initialization object, and whether or not the object has been disposed.

    private bool           disposed    = false;
    private WatcherInfo    watcherInfo = null;
    private WatchersExList watchers    = new WatchersExList();

Next, we have the event delegate definitions. You'll notice that there is one event delegate for each of the possible NotifyFilters triggers.

   public event WatcherExEventHandler EventChangedAttribute     = delegate {};
    public event WatcherExEventHandler EventChangedCreationTime  = delegate {};
    public event WatcherExEventHandler EventChangedDirectoryName = delegate {};
    public event WatcherExEventHandler EventChangedFileName      = delegate {};
    public event WatcherExEventHandler EventChangedLastAccess    = delegate {};
    public event WatcherExEventHandler EventChangedLastWrite     = delegate {};
    public event WatcherExEventHandler EventChangedSecurity      = delegate {};
    public event WatcherExEventHandler EventChangedSize          = delegate {};
    public event WatcherExEventHandler EventCreated              = delegate {};
    public event WatcherExEventHandler EventDeleted              = delegate {};
    public event WatcherExEventHandler EventRenamed              = delegate {};
    public event WatcherExEventHandler EventError                = delegate {};
    public event WatcherExEventHandler EventDisposed             = delegate {};
    public event WatcherExEventHandler EventPathAvailability     = delegate {};

Then we see some helper methods that make remove some of the chore of typing. These methods simply manipulate the two flag enumerators to find out if the specified ChangeType or NotifyFilter have been specified.

   //--------------------------------------------------------------------------------
    public bool HandleNotifyFilter(NotifyFilters filter)
    {
        return (((NotifyFilters)(watcherInfo.ChangesFilters & filter)) == filter);
    }

    //--------------------------------------------------------------------------------
    public bool HandleWatchesFilter(WatcherChangeTypes filter)
    {
        return (((WatcherChangeTypes)(watcherInfo.WatchesFilters & filter)) == filter);
    }

After the subscribing object has created a WatcherEX object, it at some point call the Initialize method. This method is responsible for creating all of the necessary internal FileSystemWatcherEx objects.

   //--------------------------------------------------------------------------------
    private void Initialize()
    {
        watcherInfo.BufferKBytes = Math.Max(4, Math.Min(watcherInfo.BufferKBytes, 64));

        CreateWatcher(false, watcherInfo.ChangesFilters);

        CreateWatcher(true, NotifyFilters.Attributes);
        CreateWatcher(true, NotifyFilters.CreationTime);
        CreateWatcher(true, NotifyFilters.DirectoryName);
        CreateWatcher(true, NotifyFilters.FileName);
        CreateWatcher(true, NotifyFilters.LastAccess);
        CreateWatcher(true, NotifyFilters.LastWrite);
        CreateWatcher(true, NotifyFilters.Security);
        CreateWatcher(true, NotifyFilters.Size);
    }

The first line in the method performs a sanity check on the buffer size. Default is 8k, minimum is 4k, and the maximum is 64k. The next line creates what I call the "main" FileSystemWatcherEx object. This watcher is responsible for handling everything except Changed events. Finally, the last eight lines create a FileSystemWatcherEx object for each of the NotifyFilters.

Here's the common CreateWatcher method called from Initialize. First, we create a watcher, and calculate the actual buffer size.

   //--------------------------------------------------------------------------------
    private void CreateWatcher(bool changedWatcher, NotifyFilters filter)
    {
        FileSystemWatcherEx watcher = null;
        int bufferSize = (int)watcherInfo.BufferKBytes * 1024;

If the watcher we're trying to create is one of the Changed events. This code only creates a watcher for the specified filter if the filter was included in the WatcherInfo initializing object. The appropriate settings are applied to the watcher, and the Changed event is registered.

        if (changedWatcher)
        {
            // if we're not handling the currently specified filter, get out
            if (HandleNotifyFilter(filter))
            {
                watcher                       = new FileSystemWatcherEx(watcherInfo.WatchPath);
                watcher.IncludeSubdirectories = watcherInfo.IncludeSubFolders;
                watcher.Filter                = watcherInfo.FileFilter;
                watcher.NotifyFilter          = filter;
                watcher.InternalBufferSize    = bufferSize;
				switch (filter)
                {
                    case NotifyFilters.Attributes    :
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedAttribute);
                        break;
                    case NotifyFilters.CreationTime  : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedCreationTime);
                        break;
                    case NotifyFilters.DirectoryName : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedDirectoryName);
                        break;
                    case NotifyFilters.FileName      : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedFileName);
                        break;
                    case NotifyFilters.LastAccess    : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedLastAccess);
                        break;
                    case NotifyFilters.LastWrite     : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedLastWrite);
                        break;
                    case NotifyFilters.Security      : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedSecurity);
                        break;
                    case NotifyFilters.Size          : 
                        watcher.Changed += new FileSystemEventHandler(watcher_ChangedSize);
                        break;
                }
            }
        }

If the watcher is the "main" watcher, we setup all of the appropriate events for it.

        else
        {
            if (HandleWatchesFilter(WatcherChangeTypes.Created) ||
                                    HandleWatchesFilter(WatcherChangeTypes.Deleted) ||
                                    HandleWatchesFilter(WatcherChangeTypes.Renamed) ||
                                    watcherInfo.WatchForError ||
                                    watcherInfo.WatchForDisposed)
            {
                watcher                       = new FileSystemWatcherEx(watcherInfo.WatchPath, 
                                                                        watcherInfo.MonitorPathInterval);
                watcher.IncludeSubdirectories = watcherInfo.IncludeSubFolders;
                watcher.Filter                = watcherInfo.FileFilter;
                watcher.InternalBufferSize    = bufferSize;
            }

            if (HandleWatchesFilter(WatcherChangeTypes.Created)) 
            {
                watcher.Created += new FileSystemEventHandler(watcher_CreatedDeleted);
            }
            if (HandleWatchesFilter(WatcherChangeTypes.Deleted))
            {
                watcher.Deleted += new FileSystemEventHandler(watcher_CreatedDeleted);
            }
            if (HandleWatchesFilter(WatcherChangeTypes.Renamed))
            {
                watcher.Renamed += new RenamedEventHandler(watcher_Renamed);
            }
            if (watcherInfo.MonitorPathInterval > 0)
            {
                watcher.EventPathAvailability += new PathAvailabilityHandler(watcher_EventPathAvailability);
            }
        }

Finally, we register the Error and Disposed events if necessary, and add the watcher to our list. Notice that these handlers are registered for EVERY watcher we create if the programmer specified them in the WatcherInfo object. If you want more "atomic" application of these events, I leave it to you to implement.

   if (watcher != null)
        {
            if (watcherInfo.WatchForError)
            {
	            watcher.Error += new ErrorEventHandler(watcher_Error);
            }
            if (watcherInfo.WatchForDisposed)
            {
	            watcher.Disposed += new EventHandler(watcher_Disposed);
            }
            watchers.Add(watcher);
        }
    }

The last thing of any interest in the class are the Start/Stop methods. They simply set all of the watchers' EnableRaisingEvent property to the value appropriate for the method.

   //--------------------------------------------------------------------------------
    public void Start()
    {
        watchers[0].StartFolderMonitor();
        for (int i = 0; i < watchers.Count; i++)
        {
            watchers[i].EnableRaisingEvents = true;
        }
    }

    //--------------------------------------------------------------------------------
    public void Stop()
    {
        watchers[0].StopFolderMonitor();
        for (int i = 0; i < watchers.Count; i++)
        {
            watchers[i].EnableRaisingEvents = false;
        }
    }

The remaining methods in the object are the watcher event handlers, and while not very interesting, I'll show you one of them in the interest of completeness.

   //--------------------------------------------------------------------------------
    private void watcher_ChangedAttribute(object sender, FileSystemEventArgs e)
    {
        EventChangedAttribute(this, new WatcherExEventArgs(sender as FileSystemWatcherEx, 
                                                           e, 
                                                           ArgumentType.FileSystem, 
                                                           NotifyFilters.Attributes));
    }

Usage Example - The Form In The Demo Application

The form itself is a simple affair, providing the following controls:

  • A TextBox/Browse button combo that allows you to specify a folder to monitor
  • A checkbox to indicate that you want to include sub-directories while monitoring
  • A listView to display events as the form receives them
  • A Clear button to clear the list contents (mostly because I wanted to keep the ListView fairly free of clutter for Part 2 of this article.
  • A Start/Stop button to control the watcher

We start off with some helper methods. The first is one registers/unregisters WatcherEx events. Notice that I handle all of the Changed events in one method. This certainly isn't a requirement, and you should feel free to do it differently if you so choose. In the interest of brevity, the snippet below just shows the registering code (the unregistering code is in the actual demo).

//--------------------------------------------------------------------------------
private void ManageEventHandlers(bool add)
{
    if (fileWatcher != null)
    {
        if (add)
        {
            fileWatcher.EventChangedAttribute     += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedCreationTime  += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedDirectoryName += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedFileName      += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedLastAccess    += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedLastWrite     += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedSecurity      += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventChangedSize          += new WatcherExEventHandler(fileWatcher_EventChanged);
            fileWatcher.EventCreated              += new WatcherExEventHandler(fileWatcher_EventCreated);
            fileWatcher.EventDeleted              += new WatcherExEventHandler(fileWatcher_EventDeleted);
            fileWatcher.EventDisposed             += new WatcherExEventHandler(fileWatcher_EventDisposed);
            fileWatcher.EventError                += new WatcherExEventHandler(fileWatcher_EventError);
            fileWatcher.EventRenamed              += new WatcherExEventHandler(fileWatcher_EventRenamed);
            fileWatcher.EventPathAvailability     += new WatcherExEventHandler(fileWatcher_EventPathAvailability);
        }
        else
        {
            // .....
        }
    }
}

Next, we have the InitWatcher() method. It's purpose in life is to create and initialize the WatcherEx object. For the purpose of this demo, I'm monitoring all of the NotifyFilter events, but in actuality, you probably will never want to do that.

//--------------------------------------------------------------------------------
private bool InitWatcher()
{
    bool result = false;
    if (Directory.Exists(this.textBoxFolderToWatch.Text) || 
        File.Exists(this.textBoxFolderToWatch.Text))
    {
        WatcherInfo info = new WatcherInfo();
        info.ChangesFilters = NotifyFilters.Attributes    | 
                              NotifyFilters.CreationTime  | 
                              NotifyFilters.DirectoryName | 
                              NotifyFilters.FileName      | 
                              NotifyFilters.LastAccess    | 
                              NotifyFilters.LastWrite     | 
                              NotifyFilters.Security      | 
                              NotifyFilters.Size;

        info.IncludeSubFolders   = this.checkBoxIncludeSubfolders.Checked;
        info.WatchesFilters      = WatcherChangeTypes.All;
        info.WatchForDisposed    = true;
        info.WatchForError       = false;
        info.WatchPath           = this.textBoxFolderToWatch.Text;
        info.BufferKBytes        = 8;
        info.MonitorPathInterval = 250;
        fileWatcher              = new WatcherEx(info);
        ManageEventHandlers(true);
        result                   = true;
    }
    else
    {
        MessageBox.Show("The folder (or file) specified does not exist...");
    }
    return result;
}

Because the ListView could potentially be updated from another thread (remember, the FileSystemWatcherEx object runs a thread that monitors the existance of the folder being watched), we need to be able to access it through a delegate.

   private delegate void DelegateCreateListViewItem(string eventName, 
                                                         string filterName, 
                                                         string fileName);

And our delegate method looks like this.

    //--------------------------------------------------------------------------------
    private void InvokedCreateListViewItem(string eventName, string filterName, string fileName)
    {
        ListViewItem lvi = new ListViewItem();
        lvi.Text = DateTime.Now.ToString("HH:mm");
        lvi.SubItems.Add(new ListViewItem.ListViewSubItem(lvi, eventName)); 
        lvi.SubItems.Add(new ListViewItem.ListViewSubItem(lvi, filterName)); 
        lvi.SubItems.Add(new ListViewItem.ListViewSubItem(lvi, fileName));
        this.listView1.Items.Add(lvi);
    }

All of the event handlers call this method in order to update the ListView.

   //--------------------------------------------------------------------------------
    void CreateListViewItem(string eventName, string filterName, string fileName)
    {
        if (this.listView1.InvokeRequired)
        {
            DelegateCreateListViewItem method = new DelegateCreateListViewItem(InvokedCreateListViewItem);
            Invoke(method, new object[3]{eventName, filterName, fileName} );
        }
        else
        {
            InvokedCreateListViewItem(eventName, filterName, fileName);
        }
    }

When you're done with the WatcherEx object, you should call Dispose on it.

    //--------------------------------------------------------------------------------
    private void Form1Ex_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (fileWatcher != null)
        {
            ManageEventHandlers(false);
            fileWatcher.Dispose();
            fileWatcher = null;
        }
    }

The ListView Control

While playing with the demo app, I noticed that it flickered quite a bit, and searched around until I found a quick-and-easy fix. I ended up with a solution from a user named stromenet on Stackoverflow. This solution involves writing a new class that inherits from the original ListView class, sets DoubleBuffering and intercepts/eats WM_BACKGROUND messages. Here's the code (and many thanks once more to the StackOverflow user stormenet.

//////////////////////////////////////////////////////////////////////////////////////
public class ListViewNoFlicker : System.Windows.Forms.ListView
{
    //--------------------------------------------------------------------------------
    public ListViewNoFlicker()
    {
        // Activate double buffering
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
        // Enable the OnNotifyMessage event so we get a chance to filter out 
        // Windows messages before they get to the form's WndProc
        this.SetStyle(ControlStyles.EnableNotifyMessage, true);
    }

    //--------------------------------------------------------------------------------
    protected override void OnNotifyMessage(Message m)
    {
        // Filter out the WM_ERASEBKGND message
        if (m.Msg != 0x14)
        {
            base.OnNotifyMessage(m);
        }
    }

}

I don't know what side-effects this will have regarding the background image in cells, but since we're not directly concerned with those issues in this demo application, I'll leave it as an exercise for the reader to figure out. I also did NOT use this inherited ListView in the regular form (that inherits from the original DotNet version of the FileSystemWatcher object).

Other Comments

When I started the demo app, I had originally created the Watcher class that used the original DotNet FileSystemWatcher object. During my research into this object, I discovered George Oakes article (referenced and linked to near the top of this article), and thought it might make for a more complete and appropriate implementation. For this reason, I essentially duplicated the original Watcher object in the newer WatcherEx class. I was originally going to remove the Watcher object from the code, but I figured that there might be a number of you that don't want/need to use the WatcherEx object, so I left BOTH versions in the demo. Each version of the class has its own form in the demo. The demo currently only uses the WatcherEx version of the form, but it's a simple matter to change Program.cs to use the form you want to use. Be aware that the non-Ex version of the form may not be as up-to-date, and therefore might required some tweaking.

Part 2 In The Series

Because this article ended up being fairly lengthy, I decided to create a multi-part article series, with Part 2 centering on observations made while using demo application in preparation for writing this article.

View Part 2 of this article series.

History

12/18/2010 - Changed some text to make it more readable, and fixed a few misspellings.

09/30/2010 - Fixed a link to another CodeProject artical, and some errant <code> tags.

02/14/2010 - Original version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

John Simmons / outlaw programmer
Software Developer (Senior)
United States United States
Member
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.
 
My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHang on disposememberTodd Cherry7 Mar '13 - 11:13 
John,
 
Great work on this, and i have a question.
 
I'm using your code to monitor a folder across a network. If i unplug the physical Ethernet cable, I get the correct event raised (Watcher Error) for each watcher thread. However, i'm unsure how to go about recovering when the cable is plugged back in.
 
I've noticed that when trying to call _fileWatcher.Dispose() some events return and some are dead (never return).
 
Any ideas on how to implement this correctly?
Thanks,
Todd
AnswerRe: Hang on disposememberJohn Simmons / outlaw programmer7 Mar '13 - 14:45 
I would probably try firing up a thread that periodically checked when the network was available again. You could check by using Directory.Exists on the UNC path, and just eat the exception (if one is thrown).
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

GeneralMy vote of 5memberPatrick Harris20 Jan '13 - 9:15 
Just what the doctor ordered! Thank you...
QuestionExcellent job!memberJohn M. Baughman5 Dec '12 - 9:13 
This is exactly what I was looking for to simplify creating file watchers. I added in a few other minor things such as a configuration based definition and a name to the watcher info, but overall nothing else had to change!
 
I am going to look into creating a FileInfo object that can be passed on the event to be able to have all that info readily available, but that is just extension stuff...
 
Thank you for your work on this!!
 
-John
QuestionMy vote 5. And a Question: App/exe modifying the filememberMd Saleem Navalur28 Nov '12 - 2:16 
Thanks for the wonderfull article.
 
A question though, is it possible to find out which exe or application has modified the file?
 
Thanks,
Saleem
AnswerRe: My vote 5. And a Question: App/exe modifying the filemvpJohn Simmons / outlaw programmer28 Nov '12 - 5:07 
No, because none of the event objects contain info about the app making the modification. You'd probably have to go with some interop code to get that info, or maybe write a native DLL that can do it. Generally speaking, Windows does not track that kind of thing, and does therefore not store that kind of info without you writing or obtaining some code to do it. Here's a comment from someone on StackOverflow:
 
If you want to determine the process that is modifying the file during the runtime of your program, you could use the Windows API or a mini-filter driver to track what every process on the system is doing (similar to using SysInternals FileMon with custom filters), but that will apply only during the runtime of your "capture" program. Once the file is modified, however, all the traces are gone.
 
I believe the source code for SysInternals is available, but a) it's native C++, and b) you'll pretty much be on your own trying to figure out what they're doing.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

GeneralRe: My vote 5. And a Question: App/exe modifying the filememberbleekay28 Mar '13 - 11:10 
Hello, John.
 
I have been trying to do some research on being able to tie in a user profile (e.g. SID, local username, or whatever) with the events captured by a FileSystemWatcher and have always been left dazed and confused. WTF | :WTF: I have looked into using the SysInternals FileMon, however, that utility has been depreciated for the SysInternals ProcessMon. The ProcessMon comes with its own custom filters, too, but is very much like trying to figure out fly a Boeing 787 when all you really need is a prop plane. OMG | :OMG: There are way too many filters and options that need to be configured before you avoid being overwhelmed with information.
 
Are you able to shed some light direct me to a site that would have the kind of low-level API calls, interop code, etc. I would need to make for a custom DLL like that?
 
Any feedback would be apreciated, TIA.
 
Bleekay.
BugThe order of events is not accurate - race conditionmemberPaaya20 Sep '12 - 20:40 
FileSystemWatcher uses ThreadPool to raise it's events. If you use multiple FileSystemWatchers to monitor different events/attributes/whatever in the same folder, then you might have a race condition. I've looked at how the FileSystemWatcher is implemented and you can't change it to use something different than the default .NET ThreadPool. The only solution is to completely reimplement FileSystemWatcher so that your multiple watchers all use the same single thread (if that's even possible). Or use some cool synchronizing magic, but that would still require reimplementing the FSW. That being said, the order in which events pop into your listview might not reflect the reality. Especially when there are 10 events for a single high-level operation, they are probably handled by 2 or more different threads. You can easily confirm this for yourself by reading
System.Threading.Thread.CurrentThread.ManagedThreadId
in your event handlers.
GeneralRe: The order of events is not accurate - race conditionmvpJohn Simmons / outlaw programmer21 Sep '12 - 10:48 
Have you actually been able to enter a race condition with this code, or are you just theorizing?
 
The order of events in the list are the order in which they are received. Since FileSystemWatcher doesn't provide the necessary info, it's impossible to tell when/if it delivers events out of order.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

AnswerRe: The order of events is not accurate - race conditionmemberPaaya21 Sep '12 - 11:30 
I haven't tried to enter a race condition with your particular code, but I studied how your class works and made my own tests to confirm that indeed, when using multiple watchers, you can enter a race condition. But when using a single watcher you won't have that problem, even though it's still using ThreadPool - the queued events wait for your event handler to finish before raising new event (which might still be on a different thread than the last time). Also, a "just theoretical" race condition is a problem waiting to happen, non reproducible when your app gets released to your customers and possibly crashing the app randomly (not this one though).
GeneralRe: The order of events is not accurate - race conditionmvpJohn Simmons / outlaw programmer22 Sep '12 - 3:02 
Well, consider that this article merely illustrates the chaos associated with using FileSystemWatcher, and as with any code one downloads from the internet, it comes with the caveat that you should always thoroughly vet the source (code) before including it in your own production code.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

GeneralMy vote of 4memberSperneder Patrick4 Jun '12 - 7:31 
while(true)??? cummon.. bad taste.
GeneralRe: My vote of 4mvpJohn Simmons / outlaw programmer5 Jun '12 - 3:07 
0) You're gonna have to give me a bit more context.
 
1) Using while (true) is a perfectly valid and acceptable practice.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

QuestionException when the directory does't exist during constructionmemberMuir16 Jan '12 - 18:51 
If the directory being monitored does not exist when FileSystemWatcherEx is constructed it generates an exception. This is different to the path disappearing and reappearing which it is designed to recover.
 
I'm trying to use it to monitor a directory/file that I know will exist in the future but doesn't yet.
 
Unfortunately I don't have time to fix this for a while, but I wanted to let people know.
 
I suspect the directory monitoring thread needs to be moved from FileSystemWatcherEx to WatcherEx. I'm also concerned that if the directory disappears and reappears while the thread is sleeping that the watcher will stop working, and the monitor will not notice.
QuestionRewrite as Windows Service?memberbleekay6 Jan '12 - 9:28 
Nice article!
 
Any advice on rewriting this into a Windows Service?
 
TIA...
AnswerRe: Rewrite as Windows Service?mvpJohn Simmons / outlaw programmer7 Jan '12 - 1:47 
It's essentially the same thing only, without the UI.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

GeneralRe: Rewrite as Windows Service?memberbleekay12 Jan '12 - 12:03 
WTF | :WTF: Fair enough!
 
Here's the $64,000 question: Is there any to tie in user profile info in with these events?
 
The only thing I've been able to figure out is to setup some SACL's on the folder and then check the Security Event Log and somehow tie them in with the FileSystemWatcher events.
 
TIA...
QuestionThankmemberjanfen25 Jul '11 - 18:05 
Thank you so much, your project is very good
I love my wife very much

QuestionMy vote of 5memberphil.o28 Jun '11 - 23:21 
Very useful code, and well explained.
Have my 5 Wink | ;)
GeneralEventPathAvailability handler needs Start()membertomsame6 Jun '11 - 4:41 
Thanks for the great article!
 
I've found one issue though. In order to recover from a network outage, watcher_EventPathAvailability needs call to Start() after Initialize().
GeneralWindows 7 Resource Monitor Disk IOmemberTurulo27 Mar '11 - 8:39 
I'm trying to figure out how to build something like the windows 7 resource monitor but as a gadget that it shows which files are being written or read. Is the API in this example the only way to get that information?
 
Thank you.
GeneralRe: Windows 7 Resource Monitor Disk IOmvpJohn Simmons / outlaw programmer27 Mar '11 - 11:59 
You could always use the native FileSystemWatcher implementation, but I think this way is much better.
".45 ACP - because shooting twice is just silly" - JSOP, 2010
-----
You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass." - Dale Earnhardt, 1997

GeneralRe: Windows 7 Resource Monitor Disk IOmemberTurulo28 Mar '11 - 3:49 
Thank you, I will try and see where I get with this.
GeneralMy vote of 5memberMarcelo Ricardo de Oliveira3 Jan '11 - 4:01 
Awesome article as always, John. Great job with threading Thumbs Up | :thumbsup:
cheers,
marcelo
Take a look at WPF Midi Band here in The Code Project.

GeneralMy vote of 5memberMember 432084424 Dec '10 - 13:42 
I like it
GeneralMy vote of 5memberprasad0222 Dec '10 - 3:51 
Great article 5/5
GeneralNice articlememberBenny S. Tordrup20 Dec '10 - 3:25 
One comment though.
 
I noticed that you in the Watcher constructor throw a System.Exception if the WatcherInfo parameter is null. IMO, this is not a good practice - instead throw an ArgumentNullException which is created exactly for this purpose.
GeneralMy vote of 5memberjakie18 Dec '10 - 13:09 
been looking for the similar program, and this has saved me thousand of minutes to figure on folder monitoring then react,,,,Smile | :)
GeneralMy vote of 4memberMike Chibaka25 Nov '10 - 18:51 
Sample app is clear.
GeneralRe: My vote of 4memberJohn Simmons / outlaw programmer26 Nov '10 - 5:15 
I don't understand why you voted this a 4, especially given your comment. Did you mean to say that it's not clear?
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

GeneralMy vote of 5memberWühlmaus1 Oct '10 - 2:05 
very useful. Had the same problem.
GeneralMy vote of 5memberbabalao30 Sep '10 - 7:08 
nice
QuestionMoving filesmemberAndromeda Shun29 Sep '10 - 4:04 
Hi!
You wrote that the purpose of your first application was to monitor a folder (which can be done with your watcher class) and move files. Can you tell us something about How you accomplish the moving?
 
As you wrote in part 2 the events fired when files are created or copied to the monitored folder are quite chaotic. So when there is an event from a newly created file you cannot simply copy or even move it somewhere else - the file may still be accessed.
 
One approach I found (and the first thing that came to my mind, too) is using a loop trying to access the file exclusively when I detected its appearing. But somehow I have a bad feeling about this... Suspicious | :suss:
 
Greetings,
shun
Dead | X|
AnswerRe: Moving filesmemberJohn Simmons / outlaw programmer29 Sep '10 - 23:36 
Andromeda Shun wrote:
Can you tell us something about How you accomplish the moving?

 
I used the built-in FTP stuff in .Net.
 
Andromeda Shun wrote:
One approach I found (and the first thing that came to my mind, too) is using a loop trying to access the file exclusively when I detected its appearing. But somehow I have a bad feeling about this...

 
That's pretty much the only good option I came up with as well. I fire off a monitoring thread that tests for exclusive access, and the thread posts an event when the file can be used.
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

GeneralRe: Moving filesmemberAndromeda Shun30 Sep '10 - 0:03 
Don't you get lot's of FileSystemWatcher events for the file after you have received the first event?
 
So you have to remember which files you already detected and you are waiting to be copied.
 
Do you have a strategy for cleaning up zombie monitoring threads?
 
Regards,
Andromeda Shun
GeneralRe: Moving filesmemberJohn Simmons / outlaw programmer30 Sep '10 - 0:20 
Andromeda Shun wrote:
Don't you get lot's of FileSystemWatcher events for the file after you have received the first event?

 
How many events depends on how the file is being manipulated by the application that has it open. That's clearly illustrated in Part 2 of this article series. In my case, the file was being created by a program someone else in our company wrote, so the number of events was fairly minimal. When I actually needed the code described in this article (four years ago), I had not yet written it.
 

Andromeda Shun wrote:
So you have to remember which files you already detected and you are waiting to be copied.

 
I set up a list of file-monitoring thread objects. when I got a file event, I would check this list to see if there was already a thread running for the filename.
 
Andromeda Shun wrote:
Do you have a strategy for cleaning up zombie monitoring threads?

 
Well, if the file still wasn't accessible after 30 minutes, the action taken was dependent on the application's configuration (either kill the monitor after x minutes (1-120), or just let it run (automatically killed after 24 hours). The files were fairly large so I had to give them some time to run. The whole thing ran in a Windows service, so there was no user interaction.
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

Generalfyi: Link has changedmemberAndromeda Shun29 Sep '10 - 3:11 
Hi!
The link to the article you used (Advanced FileSystemWatcher) has changed. The new link is this[^].
Greetings,
shun
GeneralRe: fyi: Link has changedmemberJohn Simmons / outlaw programmer29 Sep '10 - 23:44 
Fixed. Thanks.
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

GeneralMy vote of 5memberCIDev14 Sep '10 - 14:32 
Useful and well written.
Generalnice!membermk.developer21 Mar '10 - 3:05 
very delicios idea. thanks..

GeneralNicememberMike Marynowski22 Feb '10 - 20:01 
Nice article John, got my 5.
 
My only issue with your code is the redundancy of subscribing to the events you want plus passing in flags for those events. I'm writng my own version inspired by this, but the events are exposed with add and remove accessors. Inside the add accessor it makes sure the underlying FileSystemWatcher object responsible for that event has been created, and if not, creates it. Inside the remove accessor, it does a count on how many delegates are registered for the event, and if it reaches zero, it disposes of the related FileSystemWatcher.
 
It is a bit trickier for "EventChanged" since there are multiple events that can fire for a single FileSystemWatcher, but nothing crazy.
 
So all you have to worry about for using the object is subscribing to the events you want. Just makes it easier to avoid problems like events not firing for some reason, or accidentally setting more watchers than you actualy make use of.
GeneralBuffer sizememberdchrno22 Feb '10 - 11:04 
Nice article, definitely worth a 5 Smile | :)
 
Don't underestimate the buffer size! If you decompress a large zip file to a directory with a long name, the default buffer size of 2k is quickly filled up. I'm not sure how it is implemented post .NET 2.0, but in 1.1 the FileSystemWatcher would silently drop events that would overflow the buffer.
 
A good practice if you plan to use this in production with an expected high volume of files/any solution where you can expect the rate of new events being faster than your event handler, is to push the events to a queue and return from the event handler immediately. One or more worker threads can then pull events off the queue at their own laze.
 
If you implement this worker thread, a nice bonus is the ability to check if a file is closed before processing the event. In my library I can either push open files back to the end of the queue, or if sequential file handling is requested, simply wait a set number of milliseconds before checking the file at the head of the queue again.
GeneralRe: Buffer sizememberJohn Simmons / outlaw programmer22 Feb '10 - 11:12 
dchrno wrote:
Don't underestimate the buffer size!

 
Yeah, I hear that. Smile | :)
 
I allowed for the programmer to set it (I think it only sets the main FSW, but that could easily be changed. Adding an event queue is a good idea too.
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

Generala few suggestionsmemberMr.PoorEnglish17 Feb '10 - 11:30 
hello!
 
thanks for pointing out that strange FSW-behavior, and publish a way, how to get FileSystem-Infos more sophisticated.
 

I have some few proposes for your code:
 
implementing events
microsoft recommends not to define specials delegates, but to use the generic EventHandler<T> where T:Eventargs
 

This would look like:
public event EventHandler<WatcherEventArgs> EventChangedAttribute = delegate { };
(but your EventArgs should inherit from EventArgs)
maybe you like to look at C# Event Implementation[ . ]
 
Timer
When you don't like the winform-timer - what about System.Timers.Timer, or System.Threading.Timer?
Threading.Timer takes a Thread from the ThreadPool, which is more kind to the ressources, than to allocate a single thread which sleeps most of the time.
 
InvokeRequired
You don't need to check against .InvokeRequired, because its shure, that it is required.
GeneralRe: a few suggestionsmemberJohn Simmons / outlaw programmer17 Feb '10 - 11:51 
Mr.PoorEnglish wrote:
(but your EventArgs should inherit from EventArgs)

 
oops. I knew that, but I forgot to do it...
 

Mr.PoorEnglish wrote:
implementing events
microsoft recommends not to define specials delegates, but to use the generic EventHandler where T:Eventargs

 
I know that too, but don't forget, this is just a demo app. I'm not really concerned with optimization as much as I am making sure tings work.
 
I'll make you a deal though - since the code is still pretty fresh in my head (and I'm not currently working for a living), I'll look at making some changes.
 

Mr.PoorEnglish wrote:
When you don't like the winform-timer - what about System.Timers.Timer, or System.Threading.Timer?
Threading.Timer takes a Thread from the ThreadPool, which is more kind to the resources, than to allocate a single thread which sleeps most of the time.

 
It's not the Timer *object* that I have a problem with - it's the resulting timer *message*. I simply have no use for it.
 

Mr.PoorEnglish wrote:
InvokeRequired
You don't need to check against .InvokeRequired, because its shure, that it is required.

 
I know I don't *need* to do it. It's just accepted practice (and Invoke isn't always necessarily required.).
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

GeneralRe: a few suggestionsmemberMr.PoorEnglish17 Feb '10 - 13:21 
John Simmons / outlaw programmer wrote:
It's not the Timer *object* that I have a problem with - it's the resulting timer *message*. I simply have no use for it.

 
Do i understand it right: Although a System.Threading.Timer has no handle (or has it?) there will be raised a timer-message, when it ticks?
GeneralRe: a few suggestionsmemberJohn Simmons / outlaw programmer17 Feb '10 - 23:58 
It sends the WM_TIMER message. That's all it needs to do for me to not use it.
.45 ACP - because shooting twice is just silly
-----
"Why don't you tie a kerosene-soaked rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt, 1997
-----
"The staggering layers of obscenity in your statement make it a work of art on so many levels." - J. Jystad, 2001

GeneralRe: a few suggestionsmvpPIEBALDconsult2 Mar '10 - 12:58 
I'm still not convinced that that is true of System.Timers.Timers; from what I can tell, they work when no message pump is available -- even in console apps (I just tried it).
GeneralGood articlememberMatt McKinney15 Feb '10 - 5:11 
5 from me!
Anyone that uses filesystemwatcher for remote folders absolutely must be aware that there is NO notice that the remote system (or folder) has become unavailable... the events just stop. I admit my own solution did use a timer to trigger periodic scans as a failover method in case FSW became disconnected. There is an error event that FSW can fire, but have yet to ever see it work (is it a dummy?) Confused | :confused:
Matt McKinney

GeneralVery good and useful JohnmvpSacha Barber15 Feb '10 - 4:04 
5 stars
Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 18 Dec 2010
Article Copyright 2010 by John Simmons / outlaw programmer
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid