This post demonstrates how to bind an ItemsControl to the files in a directory. We will see how to use the FileSystemWatcher within a custom DataSourceProvider to update a collection of FileInfo wrappers, and put the INotifyPropertyChanged interface to use for providing visual alerts of when a file changes.
This weekend I built a little app which monitors the files in a directory. It displays information about each file, and updates the UI when a file is created, changed, renamed, or deleted. Since the whole app depends on the flaky FileSystemWatcher component it is not the most reliable piece of software in the world, but this is just a learning experience; not production code. In this post I will review some of the more interesting aspects of how the app works, but feel free to download the source code from the bottom of this page to see the whole thing.
Here is what the app looks like when running it and one file information area is expanded:
When the ‘Secrets.txt’ file is edited and saved, the UI notifies the user by flashing that file’s area, as seen below:
So how does this work? We can break it down into five parts, and briefly examine each of them.
You can bind to a System.IO.FileInfo object and display its data in the UI, but a FileInfo is just a snapshot. If the file on disk is modified after you create a FileInfo snapshot of it, that snapshot is not updated. Since the purpose of this application is to monitor changes to files in a directory, we need to take another approach.
To work around the static nature of FileInfo, I created a class called ObservableFileInfo. That class is a FileInfo wrapper which descends from my BindableObject base class. It exposes a FileInfo property so that the UI can bind to the data about the file. When the file is changed we assign the FileInfo property a new snapshot of the file, and raise the PropertyChanged event for the FileInfo property. This informs the binding system that it should reference the new value of the property, with the new snapshot in it.
Some class needs to be responsible for generating a collection of ObservableFileInfo objects and updating them when the corresponding files on disk are modified. That work is handled by the FileSystemDataProvider class. Internally it uses the FileSystemWatcher component to monitor changes to a directory. Unfortunately FileSystemWatcher is notoriously unreliable, so expect to see some strange inconsistencies (such as, opening a file sometimes affects its LastAccessedTime and sometimes does not…).
Here is the constructor for that class:
When it is time to retrieve the initial list of files in the target directory, this overridden method is invoked:
When the FileSystemWatcher’s events are raised, the event handlers modify the appropriate ObservableFileInfo object in the _files collection. For example:
When a file is created or deleted from the monitored directory, the FileSystemDataProvider will add or remove an ObservableFileInfo from the list of files. But how does the UI know to add or remove the corresponding visual elements? The answer lies in the fact that we store the ObservableFileInfo objects in an ObservableCollection<T> subclass called ObservableFileInfoCollection. That class leverages the collection changed notifications built into ObservableCollection<T> to let the binding system know when the list has changed. Here is that class:
FileSystemDataProvider exposes a list of ObservableFileInfo objects to the UI. Since WPF has no idea how to display those objects, we tell it how to do so by providing it a DataTemplate. That DataTemplate renders an ObservableFileInfo object in an Expander control, like this:
Lastly we have the Window which contains an ItemsControl which is bound to the list of files. Most of that Window’s XAML is seen below:
Download the demo project here: FileSysViewer (demo project) Be sure to change the file extension from .DOC to .ZIP and then decompress the file.