在 TabControl.ContentTemplate 内使用 ViewLocator

Use ViewLocator inside TabControl.ContentTemplate

在 AvaloniaUI window 中,我想要一个 TabControl,其选项卡在 ObservableCollection<T> 中添加和删除。选项卡的“标题”(出现在选项卡条上的文本)应设置在 collection 的每个项目内,它可以属于不同的类型。

为此我定义了一个类型:

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

而我的 collection 是这样定义的:

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

在xaml文件中,这是TabControl的定义:

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

到目前为止,这很有效。

当我还想为每个选项卡内的视图设置一个容器时,问题就开始了,它不应该是包含的视图本身的一部分。我试过编辑上面的 xaml 并像这样设置 ContentTemplate

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

但是这会导致以下错误:

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

这似乎是因为未调用 ViewLocator,它根据名称自动将视图模型匹配到视图。我认为这是因为我在 TabControl.ContentTemplate.

中定义了一个 DataTemplate

是否可以指示 Avalonia 在 TabControl.ContentTemplate 中使用 ViewLocator,以便根据名称选择视图?

<Border Child="{Binding}"/>

Border 需要一个实际的控件作为子控件,而不是视图模型。您需要改用 ContentControl。它还可以拥有自己的数据模板或视图定位器。

我找到了解决这个问题的方法,方法是定义一个在内部使用 ViewLocatorIValueConverter

public class ViewModelValueConverter : IValueConverter
{
    public object? Convert(
        object value, Type targetType, object parameter, CultureInfo culture
    )
    {
        if (value == null)
            return null;

        if (
            value is ViewModelBase viewModel
            && targetType.IsAssignableFrom(typeof(IControl))
        )
        {
            ViewLocator viewLocator = new();
            return viewLocator.Build(value);
        }

        throw new NotSupportedException();
    }

    public object ConvertBack(
        object value, Type targetType, object parameter, CultureInfo culture
    )
    {
        throw new NotSupportedException();
    }
}

并在 XAML 中使用它:

<Window.Resources>
    <local:ViewModelValueConverter x:Key="variableView"/>
</Window.Resources>
<TabControl Items="{Binding OpenTabs}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabHeader}"/>
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate>
            <Border Child="{Binding, Converter={StaticResource variableView}}"/>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

但感觉可能有更简单的解决方案。