How to mix databound and static levels in a TreeView? How to mix databound and static levels in a TreeView? wpf wpf

How to mix databound and static levels in a TreeView?


Oh man this is an incredibly frustrating task. I've tried doing it myself many times. I had a very similar requirement where I've got something like a Customer class that has both a Locations collection and a Orders collection. I wanted Locations and Orders to be "folders" in the tree view. As you've discovered, all the TreeView examples that show you how to bind to self-referencing types are pretty much useless.

First I resorted to manually building a tree of FolderItemNode and ItemNode objects that I would generate in the ViewModel but this defeated the purpose of binding because it would not respond to underlying collection changes.

Then I came up with an approach which seems to work pretty well.

  • In the above described object model, I created classes LocationCollection and OrderCollection. They both inherit from ObservableCollection and override ToString() to return "Locations" and "Orders" respectively.
  • I create a MultiCollectionConverter class that implements IMultiValueConverter
  • I created a FolderNode class that has a Name and Items property. This is the placeholder object that will represent your "folders" in the tree view.
  • Define hierarchicaldatatemplate's that use MultiBinding anywhere that you want to group multiple child collections into folders.

The resulting XAML looks similar to the code below and you can grab a zip file which has all the classes and XAML in a working example.

<Window x:Class="WpfApplication2.MainWindow"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:Local="clr-namespace:WpfApplication2"        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">    <Window.Resources>        <!-- THIS IS YOUR FOLDER NODE -->        <HierarchicalDataTemplate DataType="{x:Type Local:FolderNode}" ItemsSource="{Binding Items}">            <Label FontWeight="Bold" Content="{Binding Name}" />        </HierarchicalDataTemplate>        <!-- THIS CUSTOMER HAS TWO FOLDERS, LOCATIONS AND ORDERS -->        <HierarchicalDataTemplate DataType="{x:Type Local:Customer}">            <HierarchicalDataTemplate.ItemsSource>                <MultiBinding>                    <MultiBinding.Converter>                        <Local:MultiCollectionConverter />                    </MultiBinding.Converter>                    <Binding Path="Locations" />                    <Binding Path="Orders" />                </MultiBinding>            </HierarchicalDataTemplate.ItemsSource>            <Label Content="{Binding Name}" />        </HierarchicalDataTemplate>        <!-- OPTIONAL, YOU DON'T NEED SPECIFIC DATA TEMPLATES FOR THESE CLASSES -->        <DataTemplate DataType="{x:Type Local:Location}">            <Label Content="{Binding Title}" />        </DataTemplate>        <DataTemplate DataType="{x:Type Local:Order}">            <Label Content="{Binding Title}" />        </DataTemplate>    </Window.Resources>    <DockPanel>        <TreeView Name="tree" Width="200" DockPanel.Dock="Left" />        <Grid />    </DockPanel></Window>

Folders in TreeView


The problem is that a TreeView is not very well suited to what you want to acomplish: It expects all the subnodes to be of the same type. As your database node has a node of type Collection<Schemas> and of type Collection<Users> you cannot use a HierarchicalDataTemplate. A Better approach is to use nested expanders that contain ListBoxes.

The code below does what you want I think,while being as close as possible to your original intent:

<Window x:Class="TreeViewSelection.Window1"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    xmlns:smo="clr-namespace:TreeViewSelection"    Title="Window1" Height="300" Width="300">    <Window.Resources>        <Style TargetType="ListBox">            <Setter Property="BorderThickness" Value="0"/>        </Style>        <DataTemplate DataType="{x:Type smo:Database}">                <TreeViewItem Header="{Binding Name}">                    <TreeViewItem Header="Schemas">                        <ListBox ItemsSource="{Binding Schemas}"/>                    </TreeViewItem>                    <TreeViewItem Header="Users">                    <ListBox ItemsSource="{Binding Users}"/>                </TreeViewItem>                </TreeViewItem>         </DataTemplate>        <DataTemplate DataType="{x:Type smo:User}" >            <TextBlock Text="{Binding Name}"/>        </DataTemplate>        <DataTemplate DataType="{x:Type smo:Schema}">            <TextBlock Text="{Binding Name}"/>        </DataTemplate>    </Window.Resources>    <StackPanel>        <TreeViewItem ItemsSource="{Binding DataBases}" Header="All DataBases">        </TreeViewItem>    </StackPanel></Window>using System.Collections.ObjectModel;using System.Windows;namespace TreeViewSelection{    public partial class Window1 : Window    {        public ObservableCollection<Database> DataBases { get; set; }        public Window1()        {            InitializeComponent();            DataBases = new ObservableCollection<Database>                            {                                new Database("Db1"),                                new Database("Db2")                            };            DataContext = this;        }    }    public class Database:DependencyObject    {        public string Name { get; set; }        public ObservableCollection<Schema> Schemas { get; set; }        public ObservableCollection<User> Users { get; set; }        public Database(string name)        {            Name = name;            Schemas=new ObservableCollection<Schema>                        {                            new Schema("Schema1"),                            new Schema("Schema2")                        };            Users=new ObservableCollection<User>                      {                          new User("User1"),                          new User("User2")                      };        }    }    public class Schema:DependencyObject    {        public string Name { get; set; }        public Schema(string name)        {            Name = name;           }    }    public class User:DependencyObject    {        public string Name { get; set; }        public User(string name)        {            Name = name;        }    }}


You need to fill the properties you're using in your binding with data from your database. Currently you're using a new TreeViewItem, and using it as a datasource, so what you're saying about it seeing everything as a single node makes sense, as you've placed it in a single node.

You need to load your database data and attach it to the properties you've used in your WPF template as binding items.