根据子对象类型选择DataTemplate

Joe*_*ite 11 wpf datatemplate itemscontrol

我想数据绑定ItemsCollection,但我想渲染通过集合项上的属性到达的子对象,而不是渲染集合项.

更具体一点:这将是游戏的2D地图查看器(虽然在当前状态下它还不是2D).余数据绑定一个ItemsControl到一个ObservableCollection <广场>,其中方形有一个属性称为地形(类型地形的).地形是一个基类,有各种后代.

我想要的是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
}
Run Code Online (Sandbox Code Playgroud)

还有一个名为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}"/>
Run Code Online (Sandbox Code Playgroud)

鉴于此代码,如果我这样做:

mapView.DataContext = new ObservableCollection<Square> {
    new Square(new Dirt()),
    new Square(new SteelPlate())
};
Run Code Online (Sandbox Code Playgroud)

我得到的东西看起来与我的预期完全一样:StackPanel包含棕褐色盒子(用于Dirt)和银盒子(用于SteelPlate).但我得到了不必要的开销.

我特别关注的是我的DataTemplate for Square:

<DataTemplate DataType="{x:Type TerrainDataModels:Square}">
    <ContentControl Content="{Binding Path=Terrain}"/>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

我真正想说的是"不,不要费心渲染Square本身,而是渲染它的Terrain属性".这接近于此,但是这为每个Square添加了一个额外的两个控件到可视树:一个ContentControl,在上面的XAML中显式编码,以及它的ContentPresenter.我不是特别想在这里使用ContentControl; 我真的想要短路并将Terrain属性的DataTemplate直接插入控制树.

但是我如何告诉ItemsControl渲染collectionitem.Terrain(因此查找Terrain对象的上述DataTemplates之一)而不是渲染collectionitem(并为Square对象寻找DataTemplate)?

我想使用的DataTemplates的地形,但不是在所有必要的广场 - 这只是我发现,工作充分第一种方法.其实,我真正想要做的是完全不同的东西 - 我真的想设置的ItemsControl的的DisplayMemberPath为"地形".这会直接呈现正确的对象(Dirt或SteelPlate对象),而无需添加额外的ContentControl或ContentPresenter.不幸的是,DisplayMemberPath总是呈现一个字符串,忽略了Terrains的DataTemplates.所以它有正确的想法,但它对我没用.

整个事情可能是过早的优化,如果没有简单的方法来获得我想要的东西,我会生活在我所拥有的东西中.但是如果有一个"WPF方式"我还不知道绑定到一个属性而不是整个集合项,它会增加我对WPF的理解,这正是我所追求的.

ben*_*wey 10

我不完全确定你的模型是什么样的,但你可以随时使用.绑定到对象属性.例如:

<DataTemplate DataType="TerrainModels:Square">
  <StackPanel>
    <TextBlock Content="{Binding Path=Feature.Name}"/>
    <TextBlock Content="{Binding Path=Feature.Type}"/>
  </StackPanel>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

更新

虽然,如果您正在寻找一种方法来绑定集合中的两个不同对象,您可能需要查看该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;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它你会有:

App.xaml中

<Application ..>
  <Application.Resources>
    <DataTemplate x:Key="DirtTemplate">
      <!-- template here -->
    </DataTemplate>
    <DataTemplate x:Key="SteelTemplate">
      <!-- template here -->
    </DataTemplate>
  </Application.Resources>
</Application>
Run Code Online (Sandbox Code Playgroud)

Window.xaml

<Window  ..>
  <Window.Resources>
    <local:TerrainSelector x:Key="templateSelector" />
  </Window.Resources>
  <ItemsControl ItemSource="{Binding Path=Terrain}" ItemTemplateSelector="{StaticResource templateSelector}" />
</Window>
Run Code Online (Sandbox Code Playgroud)