2009-04-26 5 views
11

Je souhaite répertorier une ItemsCollection, mais au lieu de rendre les éléments de la collection, je souhaite rendre les sous-objets atteints via une propriété de l'élément de collection. Pour être plus précis: ce sera une visionneuse de carte 2D pour un jeu (bien que dans son état actuel ce n'est pas encore 2D). Je catalogue une ItemsControl à une ObservableCollection <Square>, où Square a une propriété appelée Terrain (de type Terrain). Le terrain est une classe de base et a plusieurs descendants.Sélection de DataTemplate en fonction du type de sous-objet

Ce que je veux, c'est que ItemsControl rende la propriété Terrain de chaque élément de collection, pas l'élément de collection lui-même.

Je peux déjà faire ce travail, mais avec un surcoût inutile. Je veux savoir s'il y a un bon moyen de supprimer les frais inutiles.

Ce que je actuellement sont les classes suivantes (simplifié):

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 
} 

Et un UserControl appelé MapView, contenant les éléments suivants:

<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}"/> 

Donné ce code, si je fais:

mapView.DataContext = new ObservableCollection<Square> { 
    new Square(new Dirt()), 
    new Square(new SteelPlate()) 
}; 

Je reçois quelque chose qui ressemble exactement à ce que j'attends: un StackPanel contenant un bronzage boîte (pour la saleté) et une boîte en argent (pour le SteelPlate). Mais je l'obtiens avec des frais généraux inutiles.

Ma préoccupation particulière est mon DataTemplate pour la place:

<DataTemplate DataType="{x:Type TerrainDataModels:Square}"> 
    <ContentControl Content="{Binding Path=Terrain}"/> 
</DataTemplate> 

Ce que je veux vraiment dire est « non, ne prend pas la peine de rendre la place elle-même, rendre sa propriété Terrain à la place ». Cela se rapproche de cela, mais cela ajoute deux contrôles supplémentaires à l'arborescence visuelle pour chaque Square: un ContentControl, tel que codé explicitement dans le XAML ci-dessus, et son ContentPresenter. Je ne veux pas particulièrement un ContentControl ici; Je veux vraiment court-circuiter et insérer le DataTemplate de la propriété Terrain directement dans l'arbre de contrôle. Mais comment puis-je dire à ItemsControl de rendre collectionitem.Terrain (en recherchant ainsi l'un des DataTemplates ci-dessus pour l'objet Terrain) plutôt que de rendre collectionitem (et de chercher un DataTemplate pour l'objet Square)? Je veux utiliser des DataTemplates pour les terrains, mais pas du tout pour le Square - c'était juste la première approche que j'ai trouvée qui fonctionnait correctement. En fait, ce que je veux vraiment faire est quelque chose de complètement différent - je veux vraiment définir DisplayMemberPath de ItemsControl sur "Terrain". Cela rend le bon objet (l'objet Dirt ou SteelPlate) directement, sans ajouter ContentControl ou ContentPresenter supplémentaire. Malheureusement, DisplayMemberPath restitue toujours une chaîne, en ignorant les DataTemplates pour les terrains. Donc ça a la bonne idée, mais c'est inutile pour moi.

Tout cela peut être une optimisation prématurée, et s'il n'y a pas facile façon d'obtenir ce que je veux, je vais vivre avec ce que j'ai. Mais s'il y a une "méthode WPF" que je ne sais pas encore sur le point de lier à une propriété au lieu de l'ensemble de la collection, cela va ajouter à ma compréhension de WPF, qui est vraiment ce que je cherche.

+0

J'ai ajouté une deuxième réponse. Jetez un coup d'oeil et laissez-moi savoir si cela aide. – bendewey

Répondre

10

Je ne sais pas exactement à quoi ressemble votre modèle, mais vous pouvez toujours utiliser un. pour lier à une propriété d'objets.Par exemple:

<DataTemplate DataType="TerrainModels:Square"> 
    <StackPanel> 
    <TextBlock Content="{Binding Path=Feature.Name}"/> 
    <TextBlock Content="{Binding Path=Feature.Type}"/> 
    </StackPanel> 
</DataTemplate> 

Mise à jour

Bien que, si vous êtes à la recherche d'un moyen de lier deux objets différents dans une collection, vous pouvez jeter un oeil à la propriété ItemTemplateSelector.

Dans votre scénario, il serait quelque chose comme ça (non testé):

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; 
    } 
} 

ensuite l'utiliser, vous auriez:

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> 
+0

J'ai ajouté une mise à jour, je n'ai peut-être pas bien compris votre question la première fois. – bendewey

+0

Cela ressemble à quelque chose qui devrait fonctionner. Retiré ma réponse car il travaillait juste autour de Silverlights manque de mécanique WPF principalement. –

+0

mis à jour à nouveau à la saleté/acier, je suis encore un peu brumeux sur votre modèle. – bendewey

2

je crois que le mieux que vous pouvez faire pour éliminer les frais généraux d'arbre visuel (et la redondance) est la suivante:

<ItemsControl ItemsSource="{Binding Squares}"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
      <ContentPresenter Content="{Binding Terrain}"/> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
</ItemsControl> 

Je vous aurais juré pourrait prendre un peu plus loin en attribuant directement à la propriété Content de le ContentPresenter généré pour chaque élément de la ItemsControl:

<ItemsControl ItemsSource="{Binding Squares}"> 
    <ItemsControl.ItemContainerStyle> 
     <Style> 
      <Setter Property="ContentPresenter.Content" Content="{Binding Terrain}"/> 
     </Style> 
    </ItemsControl.ItemContainerStyle> 
</ItemsControl> 

Cependant, le ContentPresenter semble avoir le parent DataContext comme DataContext plutôt que le Square. Cela n'a aucun sens pour moi. Il fonctionne très bien avec un ListBox ou toute autre sous-classe ItemsControl. Peut-être que c'est un bug WPF - pas sûr. Je vais devoir regarder plus loin.

+0

En quoi cela diffère-t-il de l'exemple utilisé par l'OP? – bendewey

+0

D'accord, cela déplace simplement le DataTemplate en ligne, tout en créant un ContentControl supplémentaire pour chaque élément de la collection. Je veux éviter les contrôles supplémentaires dans l'arbre de contrôle si je peux. –

+0

@Joe: Si vous ne voulez pas ContentControl dans l'arborescence visuelle, utilisez plutôt ContentPresenter. Mise à jour de ma réponse pour refléter cela ... –

2

J'ajoute une autre réponse, parce que c'est un peu différent du problème, alors mon autre réponse.

Si vous essayez de changer l'arrière-plan de la toile, vous pouvez utiliser un DataTrigger comme ceci:

<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> 

Vous auriez également besoin d'utiliser ce convertisseur:

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(); 
    } 

} 
+0

En fait, le code que j'ai posté est simplifié; Dans mon projet réel, ces DataTemplates contiennent des UserControls, pas des couleurs unies. Je ne voulais simplement pas rendre le code plus compliqué qu'il ne l'était déjà. Bonne pensée, cependant. –

+0

Il est peut-être encore possible avec cette technique d'échanger des commandes, mais cela commence à devenir plus compliqué que ce que vous faites déjà. Je pense que ce que vous avez fonctionnera bien. – bendewey

Questions connexes