Dynamische Auswahl eines UserControls in einem DataTemplate

  • WPF
  • .NET (FX) 4.5–4.8

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von Schmittmuthelm.

    Dynamische Auswahl eines UserControls in einem DataTemplate

    Ich habe eine Auflistung einer Basisklasse public ObservableCollection<Basis> MyList { get; set; } = new ObservableCollection<Basis>(); und möchte diese Auflistung als ItemsSource für beispielsweise eine ListView nehmen.

    Die Klasse Basis ist abstrakt.

    C#-Quellcode

    1. public abstract class Basis
    2. {
    3. public string Text { get; set; }
    4. }


    Basis wird von Typ1 und Typ2 beerbt.

    C#-Quellcode

    1. public class Typ1 : Basis
    2. {
    3. public int Count { get; set; }
    4. public ObservableCollection<int> Items { get; set; } = new ObservableCollection<int> { 1, 2, 3 };
    5. }

    C#-Quellcode

    1. public class Typ2 : Basis
    2. {
    3. public bool IsEnabled { get; set; }
    4. }

    Die Auflistung MyList initialisiere ich wie folgt in der MainWindow.xaml.cs:

    C#-Quellcode

    1. public partial class MainWindow : Window
    2. {
    3. public MainWindow()
    4. {
    5. InitializeComponent();
    6. DataContext = this;
    7. }
    8. public ObservableCollection<Basis> MyList { get; set; } = new ObservableCollection<Basis>
    9. {
    10. new Typ1 { Text = "Ich bin Typ 1", Count=10},
    11. new Typ2 { Text = "Ich bin Typ 2", IsEnabled = true}
    12. };
    13. }


    Die MainWindow.xaml sieht so aus:

    XML-Quellcode

    1. <Window x:Class="DynamischeUserControls.MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:DynamischeUserControls"
    7. mc:Ignorable="d"
    8. d:DataContext="{d:DesignInstance Type={x:Type local:MainWindow}, IsDesignTimeCreatable=True}"
    9. Title="MainWindow" Height="450" Width="800">
    10. <ListView ItemsSource="{ Binding Path=MyList}">
    11. <ListView.ItemTemplate>
    12. <DataTemplate>
    13. <!--Entscheidung treffen, welches UserControl zu verwenden ist.
    14. UcTyp1 für Item == Typ1, bzw. UcTyp2 für Item == Typ2-->
    15. </DataTemplate>
    16. </ListView.ItemTemplate>
    17. </ListView>
    18. </Window>


    Das UserControl UcTyp1 habe ich so aufgebaut:
    Spoiler anzeigen

    C#-Quellcode

    1. public partial class UcTyp1 : UserControl
    2. {
    3. public UcTyp1()
    4. {
    5. InitializeComponent();
    6. DataContext = this;
    7. }
    8. public Typ1 MyType { get => (Typ1)GetValue(MyTypeProperty); set => SetValue(MyTypeProperty, value); }
    9. public static readonly DependencyProperty MyTypeProperty = DependencyProperty.Register(nameof(MyType), typeof(Typ1), typeof(UcTyp1));
    10. }

    XML-Quellcode

    1. <UserControl x:Class="DynamischeUserControls.UcTyp1"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6. xmlns:local="clr-namespace:DynamischeUserControls"
    7. mc:Ignorable="d"
    8. d:DataContext="{d:DesignInstance Type={x:Type local:UcTyp1}, IsDesignTimeCreatable=True}"
    9. d:DesignHeight="450" d:DesignWidth="800">
    10. <StackPanel>
    11. <TextBlock Text="{Binding MyType.Text, StringFormat='{}Text: {0}'}"/>
    12. <TextBlock Text="{Binding MyType.Count, StringFormat='{}Count: {0}'}"/>
    13. <ListView ItemsSource="{Binding MyType.Items}"/>
    14. </StackPanel>
    15. </UserControl>


    Und UcTyp2 so:
    Spoiler anzeigen

    C#-Quellcode

    1. public partial class UcTyp2 : UserControl
    2. {
    3. public UcTyp2()
    4. {
    5. InitializeComponent();
    6. DataContext = this;
    7. }
    8. public Typ2 MyType { get => (Typ2)GetValue(MyTypeProperty); set => SetValue(MyTypeProperty, value); }
    9. public static readonly DependencyProperty MyTypeProperty = DependencyProperty.Register(nameof(MyType), typeof(Typ2), typeof(UcTyp2));
    10. }

    XML-Quellcode

    1. <UserControl x:Class="DynamischeUserControls.UcTyp2"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6. xmlns:local="clr-namespace:DynamischeUserControls"
    7. mc:Ignorable="d"
    8. d:DataContext="{d:DesignInstance Type={x:Type local:UcTyp2}, IsDesignTimeCreatable=True}"
    9. d:DesignHeight="450" d:DesignWidth="800">
    10. <StackPanel>
    11. <TextBlock Text="{Binding MyType.Text, StringFormat='{}Text: {0}'}"/>
    12. <TextBlock Text="{Binding MyType.IsEnabled, StringFormat='{}IsEnabled: {0}'}"/>
    13. </StackPanel>
    14. </UserControl>


    Wie kann ich im DataTemplate des ListViews entscheiden, welches UserControl ich für das entsprechende Item verwende?

    Anbei noch mal das komplette Projekt
    Dateien
    Dumm ist der, der dumm ist. Nicht andersrum!
    Hallo

    Du musst im DataTemplate garnicht entscheiden. Das ist ja der Trick.
    Du machst ein DataTemplate für Typ1 und eines für Typ2.

    Allerdings nicht als ItemTemplate sondern in den Resourcen, denn dort wird die WPF fündig wenn sie nach Templates eines Typs sucht.

    Dafür gibts übrigens ein Kapitel in meiner Tutorialreihe. ;)

    XML-Quellcode

    1. <ListView ItemsSource="{ Binding Path=MyList}">
    2. <ListView.Resources>
    3. <DataTemplate DataType="{x:Type local:Typ1}">
    4. <local:UcTyp1/>
    5. </DataTemplate>
    6. <DataTemplate DataType="{x:Type local:Typ2}">
    7. <local:UcTyp2/>
    8. </DataTemplate>
    9. </ListView.Resources>
    10. </ListView>


    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Um an die DependencyProperty MyType zu binden, müsste das dann doch aber auch so aussehen oder ?

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type local:Typ1}">
    2. <local:UcTyp1 MyType="{Binding}"/>
    3. </DataTemplate>


    In dem Fall bekommen ich Binding-Errors.

    Spoiler anzeigen
    "Cannot create default converter to perform 'one-way' conversions between types 'DynamischeUserControls.UcTyp1' and 'DynamischeUserControls.Typ1'. Consider using Converter property of Binding. BindingExpression:Path=; DataItem='UcTyp1' (Name=''); target element is 'UcTyp1' (Name=''); target property is 'MyType' (type 'Typ1')"

    und

    "Value produced by BindingExpression is not valid for target property.; Value='DynamischeUserControls.UcTyp1' BindingExpression:Path=; DataItem='UcTyp1' (Name=''); target element is 'UcTyp1' (Name=''); target property is 'MyType' (type 'Typ1')"
    Dumm ist der, der dumm ist. Nicht andersrum!
    Aufgabe: Fehlermeldung lesen und dann nochmals kurz nachsehen was da nicht stimmen kann. Lese mal die erste Zeile.

    Ansonsten wäre es hald gut wenn man mehr Infos hätte. Welche Datentyp ist dein DP, Auf welchen Datentyp bindest du, usw.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Die DP MyType vom UcTyp1 ist vom Datentyp Typ1. Sollte eigentlich alles im Post 1 stehen. Also mehr Infos als im Eingangspost kann ich nicht bereit stellen, weil es nicht mehr Infos gibt, ist ja quasi das komplette Projekt.

    Die ItemsSource der ListView ist ja ItemsSource="{ Binding Path=MyList}", MyList enthält Elemente, sowohl von Typ1 als auch von Typ2. Demzufolge wäre nach meinem Verständnis <local:UcTyp1 MyType="{Binding}"/> korrekt, da ich ja an ein "komplettes" Item aus MyList binden will.
    Was mich in der Fehlermeldung irritiert, bzw. habe ich keine Lösung dafür...dass hier bei " BindingExpression:Path=; " nichts für Path angegeben ist ?( .

    Wenn ich im DataTemplate das UserControl diskret aufbau, sprich einfach die selben Controls einsetze wie im UserControl selbst, klappt alles ohne Probleme, aber das ist ja nicht Sinn der Sache.

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type local:Typ1}">
    2. <StackPanel>
    3. <TextBlock Text="{Binding Text, StringFormat='{}Text: {0}'}"/>
    4. <TextBlock Text="{Binding Count, StringFormat='{}Count: {0}'}"/>
    5. <ListView ItemsSource="{Binding Items}"/>
    6. </StackPanel>
    7. </DataTemplate>


    Bloß kann ich mir nicht erklären, warum es mit der DP und dem UserControl nicht geht.

    Dumm ist der, der dumm ist. Nicht andersrum!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Schmittmuthelm“ ()

    s. Post#3: DependencyProperty
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ich bin jetzt noch auf diesen Artikel gestoßen. Damit funktioniert dann

    XML-Quellcode

    1. <DataTemplate DataType="{x:Type local:Typ1}">
    2. <local:UcTyp1 MyType="{Binding}"/>
    3. </DataTemplate>
    so wie ich es mit vorgestellt habe.

    Wenn ich also im Konstruktor von UcTyp1 den DataContext nicht mehr so

    C#-Quellcode

    1. public UcTyp1()
    2. {
    3. InitializeComponent();
    4. DataContext = this;
    5. }

    sondern so:

    C#-Quellcode

    1. public UcTyp1()
    2. {
    3. InitializeComponent();
    4. ((FrameworkElement)Content).DataContext = this;
    5. }

    setze, dann funktioniert alles wie geplant.
    Ohne das im Konstruktor festzulegen geht das natürlich auch entsprechend im xaml.
    Statt

    XML-Quellcode

    1. <StackPanel>
    2. <TextBlock Text="{Binding MyType.Text, StringFormat='{}Text: {0}'}"/>
    3. <TextBlock Text="{Binding MyType.Count, StringFormat='{}Count: {0}'}"/>
    4. <ListView ItemsSource="{Binding MyType.Items}"/>
    5. </StackPanel>

    entsprechend

    XML-Quellcode

    1. <StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:UcTyp1}}}">
    2. <TextBlock Text="{Binding MyType.Text, StringFormat='{}Text: {0}'}"/>
    3. <TextBlock Text="{Binding MyType.Count, StringFormat='{}Count: {0}'}"/>
    4. <ListView ItemsSource="{Binding MyType.Items}"/>
    5. </StackPanel>


    Allerdings bringt mich das vom Verständnis her nicht weiter, eher im Gegenteil.
    Dumm ist der, der dumm ist. Nicht andersrum!