Dynamically Changing TreeView Contents
June 25, 2006
Roscoe, NY
I received an email from someone who had seen an earlier blog posting on using HierarchicalDataTemplate in connection with a TreeView control to display the directory structure of a disk drive. The question is: How do you get this code to respond dynamically to changes in the directories, such as new directories, deleted directories, etc?
A brief overview on the TreeView control and templates: When you create a WPF TreeView control, normally you fill it with objects of type TreeViewItem. You set the Header property of each TreeViewItem to what you want displayed (customarily this is a text string but it can be anything that derives from UIElement for much fancier effects) and you set the Items property of each TreeViewItem to a collection of sub-items of that item.
However, you can alternatively put an object of any type into the TreeView, perhaps an object of type Whatsit. The TreeView will then search through the program's resources for a HierarchicalDataTemplate that has a DataType property indicating the Whatsit class. (Or, the HierarchicalDataTemplate object might be set directly to the ItemTemplate property of the TreeViewItem.) The TreeView then uses the VisualTree property of that template to display the item based on properties of Whatsit, and it uses the ItemsSource property of the template to obtain sub-items from Whatsit. These sub-items don't necessarily have to be objects of type Whatsit; if they are objects of other types, those other types would probably also be associated with HierarchicalDataTemplate objects defined as resources.
The earlier blog entry cited above (which used a program from Chapter 16 of my book) contains a small class named DiskDirectory that is basically a wrapper around a .NET DirectoryInfo object. DiskDirectory exposes the Name property of DirectoryInfo as a property also named Name. DiskDirectory also includes a read-only property named Subdirectories that returns a List<DiskDirectory> collection of subdirectories of that directory. If an object of type DiskDirectory is put into the root of a TreeView, the HierarchicalDataTemplate for DiskDirectory does the rest.
If you want a TreeView to respond to changes in the underlying data, you must provide a mechanism for the underlying data to notify the TreeView when the data has changed. For updating disk directories, the DiskDirectory object must certainly create an object of type FileSystemWatcher so it can be notified of changes in the directory, but how it conveys these changes to the outside world can be done in a couple different ways.
The revised DiskDirectory.cs file shows one approach. The class's constructor now installs a FileSystemWatcher and processes three events in a single handler. (To be sure, this is an extremely simple-minded approach to FileSystemWatcher, but I wanted to focus more on mechanisms here.) The class also implements the INotifyPropertyChanged interface, which means it defines an event named PropertyChanged that it fires to indicate that a property in the class has changed. The PropertyChanged event is accompanied by a PropertyChangedEventArgs containing the text name of the event. So, when one of the FileSystemWatcher events comes through, DiskDirectory fires a PropertyChanged event indicating that the Subdirectories property has changed. Anybody making use of DiskDirectory objects can then access the Subdirectories property again to obtain the updated subdirectory list.
A HierarchicalDataTemplate for objects of type DiskDirectory is defined in this DynamicDirectoryTreeViewWindow.xaml file. Notice that the DataType of the template is set to the DiskDirectory class. The template displays the Name property of the DiskDirectory object in a TextBlock, and it obtains sub-items from the Subdirectories property. The TreeView itself is defined down at the bottom of the file.
The DynamicDirectoryTreeViewWindow.cs code-behind file simply sets a single DiskDirectory item in the TreeView corresponding to the root directory of the system drive. Everything else is handled through the template in conjunction with the DiskDirectory class. (The DynamicDirectoryTreeView.csproj project file completes the project.)
The INotifyPropertyChanged interface is one way a class such as DiskDirectory can provide a notification of changes in properties. In this example, a simpler approach would have been for DiskDirectory to define an event named SubdirectoriesChanged that it fired in response to changes in the monitored directory. A somewhat more difficult approach is backing Subdirectories with a DependencyProperty object named SubdirectoriesProperty.
Another approach would be for DiskDirectory to implement its Subdirectories property not as an object of type List<DiskDirectory> but of type ObservableCollection<DiskDirectory>. This collection class has its own built-in mechanism for change notification. The DiskDirectory class would create an object of type ObservableCollection<DiskDirectory> once (rather than each time the Subdirectories property is called), and then directly modify the members of this collection based on the FileSystemWatcher events. This is probably a much more difficult approach, and I'm not sure it can even be implemented successfully in this particular case.