2009-04-26 9 views
11

Ich möchte eine ItemsCollection erstellen, aber anstatt die Sammlungselemente zu rendern, möchte ich Unterobjekte rendern, die über eine Eigenschaft für das Sammlungselement erreicht werden.Auswählen von DataTemplate auf Basis des Unterobjekttyps

Um genauer zu sein: Dies wird ein 2D-Karten-Viewer für ein Spiel (obwohl in seinem aktuellen Zustand ist es noch nicht 2D). Ich databind ein ItemsControl zu einer ObservableCollection <Square>, wo Square eine Eigenschaft namens Terrain (vom Typ Terrain) hat. Terrain ist eine Basisklasse und hat verschiedene Nachkommen.

Ich möchte, dass das ItemsControl die Terrain-Eigenschaft von jedem Auflistungselement darstellt, nicht das Auflistungselement selbst.

Ich kann das schon machen, aber mit einigen unnötigen Overhead. Ich möchte wissen, ob es einen guten Weg gibt, den unnötigen Overhead zu beseitigen.

Was ich zur Zeit haben, sind die folgenden Klassen (vereinfacht):

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 
} 

Und ein Usercontrol genannt MapView, mit folgendem Inhalt:

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

diesen Code gegeben, wenn ich tun:

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

Ich bekomme etwas, das genau so aussieht, wie ich es erwarte: ein StackPanel mit einer Bräunung Box (für den Schmutz) und eine silberne Box (für die SteelPlate). Aber ich bekomme es mit unnötigen Overhead.

Mein besonderes Anliegen ist mit meinem Datatemplate für Square:

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

Was ich eigentlich sagen will ist „nein, nicht die Mühe der Platz selbst zu machen, dessen Terrain Eigenschaft machen statt“. Dies nähert sich dem an, aber dies fügt dem visuellen Baum für jedes Quadrat zwei zusätzliche Steuerelemente hinzu: ein ContentControl, wie es im obigen XAML explizit codiert ist, und seinen ContentPresenter. Ich möchte hier nicht unbedingt ein ContentControl; Ich möchte die DataTemplate der Terrain-Eigenschaft kurzschließen und direkt in den Steuerungsbaum einfügen.

Aber wie sage ich dem ItemsControl, um collectionitem.Terrain zu rendern (also eines der obigen DataTemplates für das Terrain-Objekt nachzuschlagen), anstatt collectionitem zu rendern (und nach einem DataTemplate für das Square-Objekt zu suchen)?

Ich möchte DataTemplates für die Terrains verwenden, aber nicht unbedingt für den Square - das war nur der erste Ansatz, den ich fand, der angemessen funktionierte. Eigentlich möchte ich etwas komplett anderes machen - ich möchte den DisplayMemberPath von ItemsControl wirklich auf "Terrain" setzen. Dadurch wird das richtige Objekt (das Dirt- oder SteelPlate-Objekt) direkt gerendert, ohne dass ein zusätzliches ContentControl oder ContentPresenter hinzugefügt wird. Leider rendert DisplayMemberPath immer einen String und ignoriert die DataTemplates für die Terrains. Es hat also die richtige Idee, aber es ist nutzlos für mich.

Diese ganze Sache kann vorzeitige Optimierung sein, und wenn es keine einfache Weise gibt, was ich will, werde ich mit dem leben, was ich habe. Aber wenn es einen "WPF-Weg" gibt, den ich noch nicht kenne, um mich an eine Eigenschaft zu binden, statt an den gesamten Sammlungsgegenstand, wird es zu meinem Verständnis von WPF beitragen, was wirklich mein Ziel ist.

+0

I hinzugefügt eine zweite Antwort. Werfen Sie einen Blick und lassen Sie mich wissen, wenn das hilft. – bendewey

Antwort

10

Ich bin nicht genau sicher, wie Ihr Modell aussieht, aber Sie können immer ein verwenden. an eine Objekteigenschaft binden.Zum Beispiel:

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

aktualisieren

Obwohl, wenn Sie nach einem Weg suchen zwei verschiedene Objekte in einer Sammlung binden Sie vielleicht einen Blick auf die ItemTemplateSelector Eigenschaft nehmen wollen.

In Ihrem Szenario wäre es so etwas wie dieses (nicht getestet):

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

Dann, es zu benutzen Sie haben würde:

App.xaml

<Application ..> 
    <Application.Resources> 
    <DataTemplate x:Key="DirtTemplate"> 
     <!-- template here --> 
    </DataTemplate> 
    <DataTemplate x:Key="SteelTemplate"> 
     <!-- template here --> 
    </DataTemplate> 
    </Application.Resources> 
</Application> 

Window.xaml

+0

Ich habe ein Update hinzugefügt, ich habe deine Frage vielleicht beim ersten Mal nicht verstanden. – bendewey

+0

Das sieht nach etwas aus, das funktionieren sollte. Removed meine Antwort, da es nur um Silverlights Mangel an WPF-Mechanik vor allem gearbeitet hat. –

+0

aktualisiert wieder auf Dreck/Stahl, ich bin immer noch ein wenig verschwommen auf Ihrem Modell. – bendewey

2

Ich glaube, das Beste, was Sie tun können, visuelle Struktur Overhead (und Redundanz) zu beseitigen, ist dies:

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

Ich hätte schwören können, könnten Sie durch die direkte Zuordnung einen Schritt weiter, um die Content Eigentum die ContentPresenter für jedes Element in den ItemsControl generiert:

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

Allerdings scheint die ContentPresenter die Eltern DataContext als DataContext zu haben, als die Square. Das ergibt für mich keinen Sinn. Es funktioniert gut mit einer ListBox oder jeder anderen ItemsControl Unterklasse. Vielleicht ist das ein WPF-Bug - nicht sicher. Ich muss weiter nachsehen.

+0

Wie unterscheidet sich das von dem Beispiel, das das OP verwendet hat? – bendewey

+0

Vereinbarungsgemäß wird das DataTemplate nur inline verschoben, während für jedes Element in der Sammlung noch ein zusätzliches ContentControl erstellt wird. Ich möchte die zusätzlichen Kontrollen im Kontrollbaum vermeiden, wenn ich kann. –

+0

@Joe: Wenn Sie das ContentControl nicht in der visuellen Struktur verwenden möchten, verwenden Sie stattdessen einen ContentPresenter. Aktualisieren meiner Antwort, um dies zu widerspiegeln ... –

2

Ich füge eine andere Antwort hinzu, weil dies eine andere Art von Problem ist als meine andere Antwort.

Wenn Sie versuchen, den Hintergrund der Leinwand zu ändern, dann können Sie einen Datatrigger wie folgt verwenden:

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

Sie würden auch brauchen diese Konverter zu verwenden:

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

Tatsächlich ist der Code, den ich gepostet habe, vereinfacht; In meinem realen Projekt enthalten diese DataTemplates UserControls, keine Volltonfarben. Ich wollte den Code einfach nicht komplizierter machen, als er es schon war. Gute Gedanken. –

+0

Es ist immer noch möglich, mit dieser Technik Benutzerkontrollen zu tauschen, aber es wird immer komplizierter als das, was Sie bereits tun. Ich denke, was du hast, wird gut funktionieren. – bendewey

Verwandte Themen