DataTemplate 和 ItemTemplate 的数据错误 26
Data Error 26 with DataTemplate and ItemTemplate
我有一个 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>
您观察到的错误非常明显。
您将 TabControl
的 ItemsSource
定义为包含不同类型元素的 CompositeCollection
:
- 一个
TabItem
"General";
- 一堆
Person
视图模型。
所以您只是将一个 collection 视图和一些视图模型混合在一起 - 这并不整洁。 WPF 会通过错误消息通知您。引擎尝试为项目创建视图(使用 DataTemplate
s)并突然遇到一个已经指定的视图(TabItem
),它正是项目容器的类型(因为对于 TabControl
,每个视图模型的视图将插入到 TabItem
容器中)。所以 WPF 只是将 TabItem
插入 TabControl
并通知它没有使用任何 ItemTemplate
或 ItemTemplateSelector
来创建它。
您可以简单地忽略此错误,因为最终控件应该看起来像您想要的那样(我想)。
另一种(可能更简洁)的方法是不要将视图和视图模型混合在一个 collection 中,而是为 "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 中存在的模型?
这是可能的,但有一些开销。您必须为此创建一个绑定代理。 (看看here。)
您需要的第二件事是标记扩展:
class BindingProxyValue : MarkupExtension
{
public BindingProxy Proxy { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Proxy.DataContext;
}
}
在您的 collection:
中将此标记扩展与绑定代理一起使用
<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>
您可以根据需要扩展标记扩展,例如以这样一种方式,它可以观察 object 更新并替换目标 CompositeCollection
.
中的项目
2。
如何指定常规选项卡的 header 名称?
您可以使用 ItemTemplate
s,但它变得有点复杂。您必须为 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 满意吗?你决定。
我有一个 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>
您观察到的错误非常明显。
您将 TabControl
的 ItemsSource
定义为包含不同类型元素的 CompositeCollection
:
- 一个
TabItem
"General"; - 一堆
Person
视图模型。
所以您只是将一个 collection 视图和一些视图模型混合在一起 - 这并不整洁。 WPF 会通过错误消息通知您。引擎尝试为项目创建视图(使用 DataTemplate
s)并突然遇到一个已经指定的视图(TabItem
),它正是项目容器的类型(因为对于 TabControl
,每个视图模型的视图将插入到 TabItem
容器中)。所以 WPF 只是将 TabItem
插入 TabControl
并通知它没有使用任何 ItemTemplate
或 ItemTemplateSelector
来创建它。
您可以简单地忽略此错误,因为最终控件应该看起来像您想要的那样(我想)。
另一种(可能更简洁)的方法是不要将视图和视图模型混合在一个 collection 中,而是为 "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 中存在的模型?
这是可能的,但有一些开销。您必须为此创建一个绑定代理。 (看看here。)
您需要的第二件事是标记扩展:
class BindingProxyValue : MarkupExtension
{
public BindingProxy Proxy { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Proxy.DataContext;
}
}
在您的 collection:
中将此标记扩展与绑定代理一起使用<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>
您可以根据需要扩展标记扩展,例如以这样一种方式,它可以观察 object 更新并替换目标 CompositeCollection
.
2。
如何指定常规选项卡的 header 名称?
您可以使用 ItemTemplate
s,但它变得有点复杂。您必须为 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 满意吗?你决定。