WPF DataTemplate

WPF DataTemplate
WPF DataTemplate

WPF DataTemplate

Du möchtest mehr über ein WPF DataTemplate, bzw. DataTemplates lernen und verstehen wie Diese angewendet werden können?

Lerne jetzt, wie Du einzelne Templates deklarieren & anwenden kannst, um deinen Controls unterschiedliche „Touches“ zu verpassen.

Vielleicht hast Du später noch an diesen anderen WPF-Beiträgen Interesse: WPF StackPanel, Mahapps Metro, Mahapps Metro Projekt aufsetzen.

Zu WinForms-Zeiten

Zuerst einmal schauen wir uns an, wie man zu „damaligenWinFormsZeiten noch etwas Ähnliches wie DataTemplates realisieren konnte.

Leider hat man in WinForms nicht so tolle Möglichkeiten, die Ansicht sauber von den Daten zu trennen.

Meistens hat man Sachen wie die Folgenden umgesetzt, um z. B. verschiedene Views anzuzeigen.

Via TabControl

Eine vor allem für Navigationen bekannte Möglichkeit, ist die Verwendung eines TabControls.

Das TabControl erlaubt dem Nutzer das Anzeigen anderer Ansichten, durch Klicken auf ein jeweiliges Kopf-Element.

Im nächsten Beispiel zeige ich Dir, wie Du die Anzeige des TabControls anhand eines bestimmten Datentyps anpassen kannst.

Dazu habe ich ein kleines Programm gebastelt, Welches eine passende Oberfläche, je nach zufällig generierten Typ anzeigt.

Darstellung einer normalen Task

Dieses Bild bekommst Du angezeigt, wenn das Programm per Zufall eine „normale Aufgabe“ auswählt.

TabControl DataTemplate Beispiel mit TabPages 1
TabControl DataTemplate Beispiel mit TabPages 1 – Normale Aufgabe (Task)

Darstellung einer erweiterten Task

Dieses exemplarisch andere Bild bekommst Du angezeigt, sobald das Programm eine „erweiterte Aufgabe“ zufällig auswählt.

TabControl DataTemplate Beispiel mit TabPages 2
TabControl DataTemplate Beispiel mit TabPages 2 – Erweiterte Aufgabe (Task)

Man könnte natürlich noch eine Verbesserung, wie z. B. das Ausblenden der TabPage-Header hinzufügen.

Damit wäre es vermutlich noch deutlicher, dass das „Templateweniger durch den Nutzer, sondern durch das Programm gewählt wird.

Via UserControl

Die nächste sich anbietende Möglichkeit, wäre die Nutzung eines UserControls, um ein passendes View je nach Situation anzuzeigen.

Auch hier werde ich das obige Task-Beispiel verwenden, um nicht zu sehr vom bisherigen Inhalt abzuweichen.

Visuelle Darstellung – TaskView UserControl

Hier zeige ich Dir analog zum obigen Beispiel eine Darstellung vom normalen AufgabenUserControl.

Task UserControl Template Beispiel
Task UserControl Template Beispiel

Visuelle Darstellung – ExtendedTaskView UserControl

Nun kommt das Beispiel eines UserControls, Welches zur Darstellung einer erweiterten Task verwendet wird.

ExtendedTask UserControl Template Beispiel
ExtendedTask UserControl Template Beispiel

Modern – Mit WPF DataTemplate

Im letzten und finalen Beispiel kommen wir zu dem WPF DataTemplate, Welches uns eine frische und moderne Möglichkeit bietet.

Damit lösen wir die obigen Probleme einfacher und effizienter, sowie durch WPF gestützte Technologie wesentlich performanter.

Gehen wir also nun Schritt für Schritt die einzelnen Schritte durch, um zu erfahren wie man das Ganze aufbauen könnte.

DataContext festlegen

Damit überhaupt irgendwelche Daten vom „ViewModel“ ins View wandern, legen wir den „DataContext“ des „MainWindows“ fest.

Dazu erstellen wir zuerst einmal die „MainViewModel„-Klasse:

    Public Class MainViewModel

        Public Property Tasks As ObservableCollection(Of Task)

        Sub New()
            Tasks = New ObservableCollection(Of Task)
            LoadTasks()
        End Sub

        Private Sub LoadTasks()
            Tasks.Add(New Task("Normal Task", Date.Now))
            Tasks.Add(New ExtendedTask("Extended Task", Date.Now, "The description"))
        End Sub

    End Class
using System;
using System.Collections.ObjectModel;

namespace WpfApp2
{
    public class MainViewModel
    {

        public ObservableCollection<Task> Tasks { get; set; }

        public MainViewModel()
        {
            Tasks = new ObservableCollection<Task>();
            LoadTasks();
        }

        private void LoadTasks()
        {
            Tasks.Add(new Task("Normal Task", DateTime.Now));
            Tasks.Add(new ExtendedTask("Extended Task", DateTime.Now, "The description"));
        }

    }
}

Und weisen eine Instanz dieser Klasse dann dem „DataContext“ des „MainWindows“ zu:

Class MainWindow

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        DataContext = New MainViewModel()
    End Sub

End Class
using System.Windows;

namespace WpfApp2
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = new MainViewModel();
        }

    }
}

Simple Auflistung ohne jegliches Template

Schauen wir uns dafür im ersten Schritt die obigen Aufgaben in einer simplen Auflistung an.

Dort wirst Du sehen, dass eine jeweilige Task ohne großartiges „Template„, sprich ohne jegliche visuelle Sinnhaftigkeit dargestellt wird.

Stattdessen wird eine Aufgabe mit der standardmäßigen „ToString„-Entsprechung dargestellt:

WPF ListView Binding mit Aufgaben
WPF ListView Binding mit Aufgaben

Letztendlich sehen wir hier nur die String-Darstellung des vollen Klassen-Pfades – nicht wirklich prickelnd.

Auflistung mit ToString-Überschreibung

Nun sehen wir einmal wie sich das Beispiel verhält, wenn wir die „ToString„-Funktion der Task-Klassen überschreiben.

Dazu gehen wir in die jeweilige Klasse und überschreiben die „ToString„-Funktionen wie folgt:

Task-Klasse

Public Class Task

    Public Property Subject As String

    Public Property [Date] As Date

    Sub New(subject As String, [date] As Date)
        Me.Subject = subject
        Me.Date = [date]
    End Sub

    Public Overrides Function ToString() As String
        Return $"{[Date]:dd.MM.yy HH:mm}: {Subject}"
    End Function

End Class
using System;

namespace WpfApp2
{
    public class Task
    {

        public string Subject { get; set; }

        public DateTime Date { get; set; }

        public Task(string subject, DateTime date)
        {
            Subject = subject;
            Date = date;
        }

        public override string ToString()
        {
            return $"{Date:dd.MM.yy HH:mm}: {Subject}";
        }

    }
}

ExtendedTask-Klasse

Public Class ExtendedTask
    Inherits Task

    Public Property Description As String

    Sub New(subject As String, [date] As Date, description As String)
        MyBase.New(subject, [date])
        Me.Description = description
    End Sub

    Public Overrides Function ToString() As String
        Return $"[ET] {[Date]:dd.MM.yy HH:mm}: {Subject}"
    End Function

End Class
using System;

namespace WpfApp2
{
    public class ExtendedTask : Task
    {

        public string Description { get; set; }

        public ExtendedTask(string subject, DateTime date, string description) : base(subject, date)
        {
            Description = description;
        }

        public override string ToString()
        {
            return $"[ET] {Date:dd.MM.yy HH:mm}: {Subject}";
        }

    }
}

Aktuelles Ergebnis

Hier siehst Du nun das aktuelle Ergebnis, also mit überschriebenen „ToString“-Funktionen der Klassen.

WPF ListView Binding mit Aufgaben ToString überschrieben
WPF ListView Binding mit Aufgaben ToString überschrieben

Mit WPF DataTemplate

Ganz nach dem „das Beste kommt zum Schluss“-Motto, schauen wir uns nun das Beispiel mit DataTemplates an.

Wir können das Template dazu z. B. auf Ressourcen-Ebene erstellen und darauf referenzieren, oder andererseits praktisch inline definieren.

Template verwenden

Template als Window-Ressource definieren

Definiere mit diesem Snippet ein Template namens „TaskTemplateinnerhalb des MainWindows.

    <Window.Resources>
        <DataTemplate x:Key="TaskTemplate">
            <DockPanel Background="WhiteSmoke">
                <TextBlock Text="{Binding Date, StringFormat={}{0:dd.MM.yy HH:mm}}" />
                <TextBlock Text="{Binding Subject}" />
            </DockPanel>
        </DataTemplate>
    </Window.Resources>
ListView erstellen

Erstelle danach ein neues ListView innerhalb des MainWindow-Grids und setze die ItemsSource-, sowie die ItemTemplate-Eigenschaft.

<Grid>
    <ListView ItemsSource="{Binding Tasks}" ItemTemplate="{StaticResource TaskTemplate}" />
</Grid>
WPF ListView Binding mit DataTemplate
WPF ListView Binding mit DataTemplate

Wenn Du möchtest, dass die Items über die ganze Breite gezogen werden, kannst Du die „HorizontalContentAlignment„- und die „VerticalContentAlignment“-Eigenschaften auf Stretchstellen.

Jetzt wäre es natürlich nur noch cool, wenn wir dem jeweiligen Task-Typ unterschiedliche Templates verpassen könnten – und das können wir!

Mit DataTemplateSelector

Im hier drüber befindlichen Beispiel haben wir für die Darstellung der beiden Tasks das Selbe Template verwendet.

Das ist eventuell nicht das, was wir in erster möchten, denn die verschiedenen Task-Klassen sollen auch ggf. unterschiedliche Templates verwenden.

Damit wir zu unserem Ziel kommen gewisse Templates für entsprechende Datentypen zu wählen, brauchen wir einen eigenen „DataTemplateSelector“.

Eigenen Selector erstellen

Um einen eigenen „DataTemplateSelector“ zu erstellen, legen wir z. B. eine passende Klasse namens „TaskItemTemplateSelector“ an.

Darin müssen wir dann die „SelectTemplate„-Funktion überschreiben, verwende dazu z. B. „Strg+.“->“Überschreibungen generieren“ innerhalb der Code Datei.

In der Überschreibung selbst, holen wir uns zuerst den zugrunde liegenden Datentyp des „items“.

Danach typecasten“ wir den Typ des containers zu einem „FrameworkElement“ und haben dadurch die Möglichkeit, die Templates aus dem View abzugreifen.

Dann prüfen wir, ob es sich bei dem jeweiligen „itemType“ z. B. um den „Task„-Typ handelt, wenn ja, geben wir die passende Template-Ressource zurück.

Anschließend machen wir das Gleiche nur noch für die „ExtendedTask“ und verlassen wenn nichts zutrifft einfach die Funktion mit dem Rückgabewert der Base-Funktion.

Public Class TaskItemTemplateSelector
    Inherits DataTemplateSelector

    Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
        Dim itemType = item.GetType()
        Dim frameworkElement = CType(container, FrameworkElement)

        If itemType Is GetType(Task) Then
            Dim taskTemplate = frameworkElement.FindResource("TaskTemplate")
            Return taskTemplate
        End If

        If itemType Is GetType(ExtendedTask) Then
            Dim extendedTaskTemplate = frameworkElement.FindResource("ExtendedTaskTemplate")
            Return extendedTaskTemplate
        End If

        Return MyBase.SelectTemplate(item, container)
    End Function

End Class
using System.Windows;
using System.Windows.Controls;

namespace WpfApp2
{
    public class TaskItemTemplateSelector : DataTemplateSelector
    {

        public TaskItemTemplateSelector()
        {
        }

        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var itemType = item.GetType();
            var frameworkElement = (FrameworkElement)container;

            if (itemType == typeof(Task))
            {
                var taskTemplate = (DataTemplate) frameworkElement.FindResource("TaskTemplate");
                return taskTemplate;
            }

            if (itemType == typeof(ExtendedTask))
            {
                var extendedTaskTemplate = (DataTemplate)frameworkElement.FindResource("ExtendedTaskTemplate");
                return extendedTaskTemplate;
            }

            return base.SelectTemplate(item, container);
        }
    }
}

DataTemplate-Selector einsetzen

Lege nun oben eine Ressource für den jeweiligen „Template-Selector“ an, damit wir Diesen im XAML-Code verwenden können:

<Window.Resources>
    <local:TaskItemTemplateSelector x:Key="TaskItemTemplateSelector" />
</Window.Resources>

Final kannst Du den folgenden Code für das ListView verwenden, um den eben erstellten „TemplateSelector“ zu verwenden:

<ListView ItemsSource="{Binding Tasks}" ItemTemplateSelector="{StaticResource TaskItemTemplateSelector}" />

Downloads

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert