Thursday, 11 April 2013

WPF - Different Data Templates for Different Child Types in a TreeView

The TreeView control in WPF allows us to display hierarchical data and allows us to customise the appearance of this by setting the ItemTemplate property using a HierarchicalDataTemplate, giving us expandable nodes within our tree. However, we can only set the ItemTemplate property once with one data template. Imagine the scenario where you want to display a directory structure. A directory can contain files or other directories which in turn can also contain files or other directories and so on. You might want to use a different data template for files than for directories but as stated earlier you can only set the ItemTemplate property once, so how can we achieve this?

The answer is to declare our data templates in the TreeView's resources, specifying the object type we want each data template to be applied to. Here's a simple example of what our main, directory and file view models might look like.

  1. class MainViewModel : ViewModelBase
  2. {
  3.     public ObservableCollection<object> Children { get; set; }
  4.  
  5.     public MainViewModel()
  6.     {
  7.         this.Children = new ObservableCollection<object>();
  8.     }
  9. }

  1. class DirectoryViewModel : ViewModelBase
  2. {
  3.     private string _name;
  4.     public ObservableCollection<object> Children { get; set; }
  5.  
  6.     public string Name
  7.     {
  8.         get { return _name; }
  9.         set { _name = value; }
  10.     }
  11.  
  12.     public DirectoryViewModel()
  13.     {
  14.         this.Children = new ObservableCollection<object>();
  15.     }
  16. }

  1. class FileViewModel : ViewModelBase
  2. {
  3.     private string _name;
  4.     public DirectoryViewModel ParentFolder { get; set; }
  5.  
  6.     public string Name
  7.     {
  8.         get { return _name; }
  9.         set { _name = value; }
  10.     }
  11. }

The main thing of note here is both our MainViewModel and DirectoryViewModel have an ObservableCollection of generic objects for all child objects i.e. directories and files. We need to do this rather than have two separate strongly typed collections of directories and files. Take a look at the XAML below and the reason for this should become apparent.

  1. <TreeView ItemsSource="{Binding Children}">
  2.     <TreeView.Resources>
  3.         <HierarchicalDataTemplate DataType="{x:Type local:FolderViewModel}" ItemsSource="{Binding Children}">
  4.             <TextBlock Text="{Binding Name}"/>
  5.         </HierarchicalDataTemplate>
  6.         <DataTemplate DataType="{x:Type local:FileViewModel}">
  7.             <TextBlock Text="{Binding Name}"/>
  8.         </DataTemplate>
  9.     </TreeView.Resources>
  10. </TreeView>

The ItemsSource property of the TreeView binds to the Children property of our MainViewModel, we can put either DirectoryViewModels or FileViewModels in here. In our TreeView's resource section we define a HierarchicalDataTemplate and a DataTemplate specifying the DataType we want each template to be applied to.

We specify our DirectoryViewModel as the DataType for the HierarchicalDataTemplate and bind the ItemsSource to the Children property of DirectoryViewModel, again, this can contain either DirectoryViewModels or FileViewModels. Then we specify our FileViewModel as the DataType for the DataTemplate, our FileViewModel has no children therefore we don't need to use a HierarchicalDataTemplate for this. WPF is then clever enough to apply the correct template to each of our view models allowing us to easily style each individually.

No comments:

Post a comment