在 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
。它还可以拥有自己的数据模板或视图定位器。
我找到了解决这个问题的方法,方法是定义一个在内部使用 ViewLocator
的 IValueConverter
:
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>
但感觉可能有更简单的解决方案。
在 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
。它还可以拥有自己的数据模板或视图定位器。
我找到了解决这个问题的方法,方法是定义一个在内部使用 ViewLocator
的 IValueConverter
:
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>
但感觉可能有更简单的解决方案。