Avalonia TreeView Template Selector

835 views Asked by At

I've had a go at using a template selector with a TreeView (see this project: https://github.com/imekon/AvaloniaTreeViewTemplate)

It creates a tree but the node no longer has an arrow to allow me to see the child nodes.

This is the XAML I used:

<TreeView Items="{Binding Things}">
    <TreeView.Items>
        <scg:List x:TypeArguments="vm:ThingViewModel">
            <vm:ThingViewModel Template="Folder"/>
            <vm:ThingViewModel Template="Thing"/>
        </scg:List>
    </TreeView.Items>
    <TreeView.DataTemplates>
        <helpers:ThingTemplateSelector>
            <TreeDataTemplate x:Key="Folder" ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Name}"/>
            </TreeDataTemplate>
            <TreeDataTemplate x:Key="Thing">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Name}"/>
                    <TextBlock Text="{Binding Address}"/>
                </StackPanel>
            </TreeDataTemplate>
        </helpers:ThingTemplateSelector>
    </TreeView.DataTemplates>
</TreeView>

and this is the selector:

public class ThingTemplateSelector : IDataTemplate
{
    public bool SupportsRecycling => false;

    [Content]
    public Dictionary<string, IDataTemplate> Templates { get; } = new Dictionary<string, IDataTemplate>();

    public IControl Build(object data)
    {
        return Templates[((ThingViewModel)data).Template].Build(data);
    }

    public bool Match(object data)
    {
        return data is ThingViewModel;
    }
}

I can get a tree view working if I use a simple TreeDataTemplate but then I can't get it to select the correct template for the type of entry (a 'thing' or a 'folder'), i.e. all the nodes are one type.

Is this possible with Avalonia right now?

Image of app main window

The picture above shows the 'Thing' node but no arrow next to it. Click on it simply selects the item but you can't expand it to see the child node.

1

There are 1 answers

0
Lynn Crumbling On

I finally got this to work by having the selector implement ITreeDataTemplate instead of IDataTemplate. This gave me the opportunity to dynamically call into the correct ItemsSelector:

public class AddressTreeTemplateSelector : ITreeDataTemplate
{
    [Content]
    public Dictionary<string, IDataTemplate> AvailableTemplates { get; } = new Dictionary<string, IDataTemplate>();

    public InstancedBinding ItemsSelector(object item)
    {
        if (item is Address addr)
        {
            // decide which template's ItemSelector to call 
            string key = addr.IsSingleUnit ? "AddressSingle" : "AddressMulti";
            return ((TreeDataTemplate)AvailableTemplates[key]).ItemsSelector(item); 
        }
        else
            return null;
    }

    // Check if we can accept the provided data
    public bool Match(object data)
    {
        return (data is Address);
    }

    // Build the DataTemplate here
    Control? ITemplate<object?, Control?>.Build(object? param)
    {
        string key = param.GetType().Name;
        // decide which template to build and return
        if (param is Address addr)
            key = addr.IsSingleUnit ? "AddressSingle" : "AddressMulti";
        else
            throw new KeyNotFoundException(nameof(AddressTreeTemplateSelector) + ": Invalid key: " + key);

        if (key is null) // If the key is null, we throw an ArgumentNullException
        {
            throw new ArgumentNullException(nameof(param));
        }
        return AvailableTemplates[key].Build(param); // finally we look up the provided key and let the System build the DataTemplate for us
    }
}

Using the following Xaml:

<local:AddressTreeTemplateSelector>
    <TreeDataTemplate x:Key="AddressMulti" DataType="obj:Address" ItemsSource="{Binding Units}">
        <StackPanel Orientation="Horizontal">
            <Image Source="/Assets/tree_Address.png" />
            <TextBlock Margin="5,0,0,0"  Text="{Binding FullAddress}" />
        </StackPanel>
    </TreeDataTemplate>
                            
    <TreeDataTemplate x:Key="AddressSingle" DataType="obj:Address" ItemsSource="{Binding FirstUnitLocations}">
        <StackPanel Orientation="Horizontal">
            <Image Source="/Assets/tree_MiniAddress.png" />
            <TextBlock Margin="5,0,0,0" Text="{Binding FullAddress}"/>
        </StackPanel>
    </TreeDataTemplate>
</local:AddressTreeTemplateSelector>