Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

FileSystemWatcher - Pure Chaos (Part 1 of 2)

4.92/5 (107 votes)
18 Dec 2010CPOL11 min read 320.9K   15.1K  
How to get all the events you want - whether you need them or not.

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)