Search code examples
xamlmvvmavaloniaui

Use ViewLocator inside TabControl.ContentTemplate


In an AvaloniaUI window, I want to have a TabControl whose tabs are added and removed from an ObservableCollection<T>. The tab's "title" (the text appearing on the tab strip) should be set inside each item of the collection, which could belong to a different type.

For that I defined a type:

public abstract class TabViewModelBase : ViewModelBase
{
    public abstract string TabHeader { get; }
}

and my collection is defined like this:

public ObservableCollection<TabViewModelBase> OpenTabs { get; } = new();

In the axaml file, this is the definition of the TabControl:

<TabControl Items="{Binding OpenTabs}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabHeader}"/>
        </DataTemplate>
    </TabControl.ItemTemplate>
</TabControl>

So far, this works like a charm.

The problem begins when I also want to set up a container for the view inside each tab, which should not be a part of the contained view itself. I've tried by editing the xaml above and setting a ContentTemplate like this:

<TabControl Items="{Binding OpenTabs}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabHeader}"/>
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate>
            <Border Child="{Binding}"/>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

However this results in the following error:

[Binding] Error in binding to 'Avalonia.Controls.Border'.'Child': 'Could not convert 'Project.ViewModels.TestingViewModel' to 'IControl'.'

This seems to be because ViewLocator, which automatically matches a view model to a view based on its name, is not being called. I assume this is because I've defined a DataTemplate inside TabControl.ContentTemplate.

Is it possible to instruct Avalonia to use ViewLocator inside TabControl.ContentTemplate, so that a view is selected based on its name?


Solution

  • <Border Child="{Binding}"/>

    Border expects an actual control as a child, not a view model. You need to use ContentControl instead. It can also have it's own data template or view locator.