使用DataTemplate和ItemTemplate出现数据错误26

7
我有一个TabControl,其中只有一个具体的选项卡,并绑定到一个VM集合,使用不同的用户控件。为此,我使用CompositeCollection和在控件资源中定义的DataTemplates,根据VM类型选择正确的用户控件(作为ContentTemplate)。
我还设置了ItemTemplate以使用绑定定义选项卡项的名称,但它没有在资源中定义,因为我猜想会与“ContentTemplate”冲突。
它能正常工作,但我看到以下错误跟踪:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TabItem'
看起来ContentTemplate和ItemTemplate之间存在一些冲突,但我不知道如何修复它?
代码如下:
<TabControl HorizontalAlignment="Left" Height="300" Width="500">
    <TabControl.Resources>
        <CollectionViewSource x:Key="personCollection" Source="{Binding Persons}" />
        <DataTemplate DataType="{x:Type viewModel:Main}">
            <local:MainView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type viewModel:Person}">
            <local:PersonView />
        </DataTemplate>
    </TabControl.Resources>
    <TabControl.ItemsSource>
        <CompositeCollection>
            <TabItem Header="General" Content="{Binding }"/>
            <CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
        </CompositeCollection>
    </TabControl.ItemsSource>
    <TabControl.ItemTemplate>
        <DataTemplate DataType="viewModel:Person">
            <TextBlock Text="{Binding FirstName}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
</TabControl>
1个回答

7
你遇到的错误很明显。你将TabControl的ItemsSource定义为一个CompositeCollection,其中包含不同类型的元素:
  • 一个名为“General”的TabItem;
  • 一堆Person视图模型。
所以你在一个集合中混合了一个视图和一些视图模型,这不太好。 WPF通过错误消息告诉你这一点。引擎尝试为项目创建视图(使用DataTemplate),突然遇到了一个已经指定的视图(TabItem),它恰好是项目容器的类型(因为对于TabControl,将为每个视图模型插入一个视图到TabItem容器中)。因此,WPF只需将TabItem插入TabControl中,并通知它没有使用任何ItemTemplate或ItemTemplateSelector来创建它。
你可以简单地忽略此错误,因为最终控件应该看起来像你想要的(我想)。
另一种替代方法(可能更加整洁)是不要在一个集合中混合视图和视图模型,而是为“General”选项卡指定一个“general”视图模型:
<TabControl.ItemsSource>
    <CompositeCollection>
        <viewModel:GeneralViewModel/>
        <CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
    </CompositeCollection>
</TabControl.ItemsSource>

当然,您需要告诉WPF如何将其可视化:
<TabControl.Resources>
    <DataTemplate DataType="{x:Type viewModel:GeneralViewModel}">
        <local:GeneralView />
    </DataTemplate>
    <!-- ... -->
</TabControl.Resources>

更新

为了解决您在评论中提出的问题。

1. 我如何将GeneralViewModel绑定到存在于我的DataContext中的ViewModel?
这是可行的,但需要一些额外的工作。您需要创建一个绑定代理(可以参考这里)。
另外,您还需要一个标记扩展:

class BindingProxyValue : MarkupExtension
{
    public BindingProxy Proxy { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return Proxy.DataContext;
    }   
}

请在您的集合中与绑定代理一起使用此标记扩展:
<TabControl.Resources>
    <local:BindingProxy x:Key="Proxy" DataContext="{Binding GeneralViewModel}"/>
</TabControl.Resources>
<!--...-->
<CompositeCollection>
   <local:BindingProxyValue Proxy="{StaticResource Proxy}"/>
   <CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>

您可以根据需要扩展标记扩展,例如,以这样的方式观察对象更新并替换目标 CompositeCollection 中的项目。

2. 如何指定通用选项卡的标题名称?
您可以使用 ItemTemplate ,但它会变得有些复杂。 您必须为您的 TabControl 实现一个 DataTemplateSelector

class YourTabItemDataTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;

        if (element != null && item != null)
        {
            if (item is GeneralViewmodel)
            {
                return (DataTemplate)element.FindResource("GeneralTabItemTemplate");
            }
            else
            {
                return (DataTemplate)element.FindResource("PersonTabItemTemplate");
            }
        }

        return null;
    }
}

然后,您可以为不同的TabItem定义不同的ItemTemplate

<TabControl.Resources>
    <!-- ... -->
    <DataTemplate x:Key="GeneralTabItemTemplate">
        <TextBlock Text="General" />
    </DataTemplate>
    <DataTemplate x:Key="PersonTabItemTemplate">
        <TextBlock Text="{Binding FirstName}" />
    </DataTemplate>
</TabControl.Resources>

问题是:是否值得花费这些努力,或者你可以接受错误代码26?由你决定。

好的回答,谢谢!但是我对这个解决方案有两个问题:1. 我如何将GeneralViewModel绑定到我的DataContext中已经存在的ViewModel上(我不想创建另一个ViewModel,而且它没有默认构造函数)2. 我该如何指定通用选项卡的标题名称(因为我不能为其设置ItemTemplate)? - CharlesB
有效的解决方案,但是在尝试了这些技术并对以下两点印象不深时:1)冗长的代码和2)必须使用虚拟模型污染你的代码后端,我已经决定忽略错误26并继续进行我的工作。 - josh2112
有效的解决方案,但是尝试过这些技术后,对于1)冗长的代码和2)必须在代码后端添加虚拟视图模型的事实并不满意,所以我决定忽略错误26并继续进行我的工作。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接