Search code examples
c#wpfbindingtreeviewself-reference

WPF - How to display TreeView with a mix of self-referencing and external entities


I am trying to render a TreeView with a mix of self-referencing and external entities but cannot find any solution that works..

I have the following models:

# Folder
-> string Name
-> Folder ParentFolder
-> Collection<Folder> Children
-> Collection<Document> Documents

# Document
-> string Name
-> Folder Folder

I'm trying to display it as a file system hierarchy like this:

+ Folder 1
  + Folder 1.1
    - Doc 1
    - Doc 2
+ Folder 2
  + Folder 2.1
  + Folder 2.2
    - Doc 3
  - Doc 4

The ViewModel bound to the view contains a Collection<Folder> Folders.

I've tried a lot of different combination in the XAML with simple binding and MultiBinding but without success..
Here is my current XAML (which doesn't work because not displaying the hierarchy):

<TreeView ItemsSource="{Binding Folders}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type model:Folder}">
            <HierarchicalDataTemplate.ItemsSource>
                <MultiBinding>
                    <Binding Path="Children" />
                    <Binding Path="Documents" />
                </MultiBinding>
            </HierarchicalDataTemplate.ItemsSource>

            <TextBlock Text="{Binding Name}" Background="Blue"/>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type model:Document}">
            <TextBlock Text="{Binding Name}" Background="Red"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

I've also tried the following that displays the folders hierarchy but not the documents (which is I think due to not specifying the Documents property anywhere in the XAML but not sure how..):

<TreeView ItemsSource="{Binding Folders}">
    <TreeView.Resources>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type model:Folder}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type model:Document}">
            <TextBlock Text="{Binding Name}" Background="Red"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

Thank you for your help!


Solution

  • Your current XAML should work if you just define and use a converter that creates a composite collection of folders and documents:

    public class Converter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            IEnumerable<object> folders = values[0] as IEnumerable<object>;
            IEnumerable<object> docs = values[1] as IEnumerable<object>;
    
            if (folders != null && docs != null)
                return Enumerable.Concat(folders, docs);
            else if (folders != null)
                return folders;
            else if (docs != null)
                return docs;
    
            return null;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
            throw new NotSupportedException();
    }
    

    XAML:

    <TreeView ItemsSource="{Binding Folders}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type model:Folder}">
                <HierarchicalDataTemplate.ItemsSource>
                    <MultiBinding>
                        <MultiBinding.Converter>
                            <model:Converter />
                        </MultiBinding.Converter>
                        <Binding Path="Children" />
                        <Binding Path="Documents" />
                    </MultiBinding>
                </HierarchicalDataTemplate.ItemsSource>
                <TextBlock Text="{Binding Name}" Background="Blue"/>
            </HierarchicalDataTemplate>
    
            <DataTemplate DataType="{x:Type model:Document}">
                <TextBlock Text="{Binding Name}" Background="Red"/>
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>