我想数据绑定ItemsCollection,但是我想渲染通过集合项上的属性到达的子对象,而不是呈现集合项。
更具体地说:这将是游戏的2D地图查看器(尽管在当前状态下它还不是2D)。我将ItemsControl数据绑定到ObservableCollection< Square>,其中Square有一个名为Terrain的属性(Terrain类型)。地形是一个基类,有各种后代。
我想要的是ItemsControl从每个集合元素渲染Terrain属性,而不是集合元素本身。
我已经可以完成这项工作,但有一些不必要的开销。我想知道是否有一种很好的方法可以消除不必要的开销。
我目前拥有以下课程(简化):
public class Terrain {}
public class Dirt : Terrain {}
public class SteelPlate : Terrain {}
public class Square
{
public Square(Terrain terrain)
{
Terrain = terrain;
}
public Terrain Terrain { get; private set; }
// additional properties not relevant here
}
一个名为MapView的UserControl,包含以下内容:
<UserControl.Resources>
<DataTemplate DataType="{x:Type TerrainDataModels:Square}">
<ContentControl Content="{Binding Path=Terrain}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type TerrainDataModels:Dirt}">
<Canvas Width="40" Height="40" Background="Tan"/>
</DataTemplate>
<DataTemplate DataType="{x:Type TerrainDataModels:SteelPlate}">
<Canvas Width="40" Height="40" Background="Silver"/>
</DataTemplate>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding}"/>
鉴于此代码,如果我这样做:
mapView.DataContext = new ObservableCollection<Square> {
new Square(new Dirt()),
new Square(new SteelPlate())
};
我得到的东西看起来与我的预期完全一样:StackPanel包含棕褐色盒子(用于Dirt)和银盒子(用于SteelPlate)。但是我得到了不必要的开销。
我特别关注的是我的DataTemplate for Square:
<DataTemplate DataType="{x:Type TerrainDataModels:Square}">
<ContentControl Content="{Binding Path=Terrain}"/>
</DataTemplate>
我真正想说的是“不,不要费心渲染Square本身,而是渲染它的Terrain属性”。这接近于此,但是这为每个Square增加了两个可视树的控件:一个ContentControl,在上面的XAML中明确编码,以及它的ContentPresenter。我不是特别想在这里使用ContentControl;我真的想要短路并将Terrain属性的DataTemplate直接插入到控制树中。
但是如何告诉ItemsControl呈现collectionitem.Terrain(因此查找上面的Terrain对象的DataTemplates之一)而不是渲染collectionitem(并为Square对象寻找DataTemplate)?
我想在地形上使用DataTemplates,但对于Square来说根本不一定 - 这只是我发现的第一种方法,它可以充分发挥作用。事实上,我真正想要的是完全不同的东西 - 我真的想将ItemsControl的DisplayMemberPath设置为“Terrain”。这会直接呈现正确的对象(Dirt或SteelPlate对象),而无需添加额外的ContentControl或ContentPresenter。不幸的是,DisplayMemberPath总是呈现一个字符串,忽略了Terrains的DataTemplates。所以它有正确的想法,但对我来说没用。
这整件事可能是过早的优化,如果没有 easy 的方式来获得我想要的东西,我会接受我所拥有的东西。但是如果有一个“WPF方式”我还不知道绑定到一个属性而不是整个集合项,它会增加我对WPF的理解,这正是我所追求的。
答案 0 :(得分:10)
我不确定你的模型是什么样的,但你可以随时使用。绑定到对象属性。例如:
<DataTemplate DataType="TerrainModels:Square">
<StackPanel>
<TextBlock Content="{Binding Path=Feature.Name}"/>
<TextBlock Content="{Binding Path=Feature.Type}"/>
</StackPanel>
</DataTemplate>
<强>更新强>
虽然,如果您正在寻找一种方法来绑定集合中的两个不同对象,您可能需要查看ItemTemplateSelector
属性。
在你的场景中,它会是这样的(未经测试):
public class TerrainSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var square = item as Square;
if (square == null)
return null;
if (square.Terrain is Dirt)
{
return Application.Resources["DirtTemplate"] as DataTemplate;
}
if (square.Terrain is Steel)
{
return Application.Resources["SteelTemplate"] as DataTemplate;
}
return null;
}
}
然后使用它你会得到:
<强>的App.xaml 强>
<Application ..>
<Application.Resources>
<DataTemplate x:Key="DirtTemplate">
<!-- template here -->
</DataTemplate>
<DataTemplate x:Key="SteelTemplate">
<!-- template here -->
</DataTemplate>
</Application.Resources>
</Application>
<强> Window.xaml 强>
<Window ..>
<Window.Resources>
<local:TerrainSelector x:Key="templateSelector" />
</Window.Resources>
<ItemsControl ItemSource="{Binding Path=Terrain}" ItemTemplateSelector="{StaticResource templateSelector}" />
</Window>
答案 1 :(得分:2)
我相信你可以做的最好的事情就是消除视觉树开销(和冗余):
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Terrain}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
我本可以发誓,您可以通过直接分配Content
中为每个项目生成的ContentPresenter
ItemsControl
属性来更进一步:
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="ContentPresenter.Content" Content="{Binding Terrain}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
但是,ContentPresenter
似乎将父DataContext
作为其DataContext
而不是Square
。这对我来说毫无意义。它适用于ListBox
或任何其他ItemsControl
子类。也许这是一个WPF错误 - 不确定。我将不得不进一步研究它。
答案 2 :(得分:2)
我正在添加另一个答案,因为这是对问题的另一种看法,然后是我的另一个答案。
如果您尝试更改Canvas的背景,那么您可以像这样使用DataTrigger:
<DataTemplate DataType="{x:Type WpfApplication1:Square}">
<DataTemplate.Resources>
<WpfApplication1:TypeOfConverter x:Key="typeOfConverter" />
</DataTemplate.Resources>
<Canvas Name="background" Fill="Green" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path="Terrain" Converter={StaticResource typeOfConverter}}" Value="{x:Type WpfApplication1:Dirt}">
<Setter TargetName="background"Property="Fill" Value="Tan" />
</DataTrigger>
<DataTrigger Binding="{Binding Path="Terrain" Converter={StaticResource typeOfConverter}}" Value="{x:Type WpfApplication1:SteelPlate}">
<Setter TargetName="background" Property="Fill" Value="Silver" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
您还需要使用此转换器:
public class TypeOfConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}