WPF DataTemplate
Inhaltsverzeichnis
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 „damaligen“ WinForms–Zeiten 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.
Darstellung einer erweiterten Task
Dieses exemplarisch andere Bild bekommst Du angezeigt, sobald das Programm eine „erweiterte Aufgabe“ zufällig auswählt.
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 „Template“ weniger 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 Aufgaben–UserControl.
Visuelle Darstellung – ExtendedTaskView UserControl
Nun kommt das Beispiel eines UserControls, Welches zur Darstellung einer erweiterten Task verwendet wird.
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:
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.
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 „TaskTemplate“ innerhalb 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>
Wenn Du möchtest, dass die Items über die ganze Breite gezogen werden, kannst Du die „HorizontalContentAlignment„- und die „VerticalContentAlignment“-Eigenschaften auf „Stretch“ stellen.
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}" />