Tutorialreihe <WPF lernen/>

    • WPF

    Es gibt 53 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

      Tutorialreihe <WPF lernen/>

      WPF verständlich und anhand von Praxisbeispielen erklärt


      Autor: Sascha Patschka

      Inhaltsverzeichnis (inkrementell)

      ___1. Vorstellung
      _____1.1 Einleitung
      _____1.2 Aufbau der Tutorialreihe

      ___2. Grundlagen der WPF
      _____2.1 Einführung in die WPF
      ________2.1.1 Die wichtigsten Controls und deren Verwendung
      ______________2.1.1.1 Videocast: Die wichtigsten Controls und ihr Verhalten
      ______________2.1.1.2 Videocast: Style, Templates und Trigger
      ______________2.1.1.3 Videocast: Controls eine neue Optik verpassen
      ________2.1.2 XAML Namespaces
      ______________2.1.2.1 Kurze Theorie
      ______________2.1.2.2 Anwendung interessanter Namespaces
      ______________2.1.2.3 Eigene Namespaces erstellen und integieren
      ________2.1.3 Resourcen
      ______________2.1.3.1 Was sind Resourcen, was bringen sie mir
      ______________2.1.3.2 Unterschied StaticResource und DynamicResource
      ________2.1.4 Binding und das Bindingsystem
      ______________2.1.4.1 Was ist DataBinding? Das Konzept dahinter
      ______________2.1.4.2 Binding anhand einfacher Beispiele und Klassen
      ______________2.1.4.3 DesignTime Support für Binding
      ______________2.1.4.4 Binding über Converter
      ______________2.1.4.5 Binding über DataTemplates
      ______________2.1.4.6 Binding an Collections. Warum ICollectionViewSource? Filtern, Sortieren, Gruppieren ohne viel Aufwand
      ______________2.1.4.7 Validierung von Benutzereingaben
      ______________2.1.4.8 Rücksicht nehmen auf die aktuelle Culture
      ________2.1.5 Dependency Properties
      ______________2.1.5.1 Was sind Dependency Properties und wie unterscheiden sie sich von normalen Properties
      ______________2.1.5.2 Wir erstellen und Rendern eine TextEllipse mittels Dependency Properties
      ______________2.1.5.3 Eigene DependencyProperties in einem UserControl implementieren
      ________2.1.6 Markuperweiterungen
      ______________2.1.6.1 Kurze Theorie
      ______________2.1.6.2 Eigene Markuperweiterungen
      ________2.1.7 Attached Properties
      ______________2.1.7.1 Kurze Theorie (wozu Attached Properties)
      ______________2.1.7.2 Eigene Attached Properties erstellen ;-)
      ________2.1.8 Inputs und Commands
      ______________2.1.8.1 Die Input-API
      ______________2.1.8.2 Tastatur und Mausklassen
      ______________2.1.8.3 Eventrouting (Direct, Bubbling, Tunneling)
      ______________2.1.8.4 Touch und Multitouch (wird ja immer wichtiger)
      ______________2.1.8.5 Focus (Der Unterschied zwischen Keyboardfocus und Logicalfocus)
      ______________2.1.8.6 Commands (Integrierte und Eigene)
      ______________2.1.8.7 Die RelayCommand Klasse
      ______________2.1.8.8 CommandBinding und CommandParameter
      ___3. Eine Telefonbuch Applikation unter WPF (ohne Binding)
      _____!! Kapitel wird/wurde gestrichen !!
      ___4. Eine Telefonbuch Applikation unter WPF (mit Binding der CodeBehind)
      _____4.1 Das Model erstellen / was soll die App können
      _____4.2 Die erste View erstellen und Binden
      _____4.3 Die erste View besser strukturieren
      _____4.4 Filtermöglichkeit einbinden (oder doch mehr?)
      _____4.5 Einträge bearbeiten und speichern
      ___5. Das MVVM Pattern
      _____5.1 Was ist das MVVM Pattern?
      _____5.2 Wann MVVM und wann nicht?
      _____5.3 Welchen Mehrwert kann ich aus dem Pattern gewinnen?
      _____5.4 MVVM und CodeBehind - verboten?
      _____5.5 Model - View - ViewModel - Wars das?
      _____5.6 Erstellen einer korrekten MVVM Projektmappe in VisualStudio
      ___6. Unser Telefonbuch in MVVM
      _____6.1 Das Model erstellen
      _____6.2 ViewModel - Der Core - was benötigen wir alles ehe wir anfangen können mit unserem Programm
      _____6.3 Die RelayCommand-Klasse
      _____6.4 Services - Wie Messageboxen, Dialoge, MouseCursor oder TaskbarInfo steuern wenn ich die View nicht kenne?
      _____6.5 Jetzt anfangen? Ne? Warum?
      _____6.6 Das MainViewModel erstellen
      _____6.7 ....
      _____6.8 Fazit zum MVVM Pattern
      ___7. Lokalisierung und Globalisierung
      _____7.1 Lokalisierung nur mit Boardmitteln
      _____7.2 Lokalisierung mit schwung (unter zuhilfenahme von zwei NuGet-Paketen)
      _____7.3 Globalisierung (Datum, Währung, usw.)
      _____7.4 Lokalisieren von Werten aus Fremsystemen (DB, XML usw.)
      _____7.5 Gute Hilfsprogramme und Helferlein
      ___8. UnitTests und IntergrationTests
      _____8.1 Wozu UnitTests?
      _____8.2 Wie schreibe ich Tests (Grundlagen)
      _____8.3 Testen unseres ViewModels möglich?
      _____8.4 ...
      _____8.5 ...
      _____8.6 Fazit
      ___9. Repository (DataAccessLayer)
      _____9.1 Noch einen Schritt weiter? Wozu?
      _____9.2 Besprechen und Aufsetzen eines Repositorys
      _____9.3 Wir bauen unser Telefonbuch abermals neu ;)
      _____9.4 Fazit

      Änderungen und streichungen des Inhaltsverzeichnis bzw. Teile davon vorbehalten

      Für Fragen, Anregungne, Kritik, Lob oder Diskussionen gibt es den Supportthread für dieses Tutorial !!
      Antworten hier werden von den Mods in der Supportthread verschoben.
      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. ##

      Dieser Beitrag wurde bereits 59 mal editiert, zuletzt von „Nofear23m“ ()

      1.0 Vorstellung

      1.0

      Vorstellung




      Mein Name ist Patschka Sascha-Heinz, ich bin 1983 geboren und arbeite als EDV Techniker. Beruflich habe ich fast nichts mit der Programmierung zu tun und komme sohin nur privat dazu mich sowohl weiterzubilden als auch mehr Übung zu bekommen.
      Da ich fast von Anfang an unter WPF programmiere und unter WinForms wirklich nur ca. 2-3 Monate gearbeitet habe gab es für mich von Anfang an nur die Richtung zur WPF. Die WPF ist ein sehr leistungsstarkes Framework welches einem nicht nur in Punkto Optik neue Möglichkeiten eröffnet.

      Anfangs hatte ich keine Ahnung von Pattern wie dem MVVM oder anderen, da ich das meiste einfach per "lerning by doing" gelernt habe. Erst nach einigen Jahren aktiver Programmierung unter WPF kam ich zu dem Pattern MVVM. Erstmals totales Neuland mit vielen verschiedenen Ansätzen und Anfangs schwer zu durchschauen, dachte ich mir nicht das dieses Pattern mich irgendwann dazu bringen könnte eine Aussage wie "wenn möglich verwende ich nur noch MVVM in der WPF" zu tätigen, doch seit einiger Zeit ist dies immer öfter der Fall.

      Ich kann sehr gut nachvollziehen wie frustrierend es sein kann mit der WPF zu arbeiten wenn man bereits längere Zeit mit z.B. WinForms gearbeitet hat. Es kann(!) sehr frustrierend sein wenn man nicht auf Anhieb weiterkommt und im Netz finden sich sowohl was die WPF Ansicht und deren Verwendung angeht viele verschiedene Ansätze also auch was das MVVM angeht. Das kann sehr frustrierend sein. Einige davon mehr oder weniger gut und manche leider auch sehr schlecht und gar nicht skalierbar. Ich selbst habe bereits sicher 20 verschiedene Ansätze der Umsetzung eines mehr oder weniger korrekten MVVM Patterns gesehen. Weiteres über MVVM in einem späteren Kapitel.

      Grüße
      Sascha

      Dateien
      • 1.0.pdf

        (290 kB, 2.392 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      1.1 Einleitung

      1.1

      Einleitung




      Ich werde absichtlich so wenig wie nur möglich mit Fremdwörtern oder kompliziertem Code um mich werfen. Es soll in dieser Tutorialreihe darum gehen den Code zu verstehen.
      Auch Anfänger sollten den Code lesen und nachbauen können. Evtl. wird auch Code auskommentiert werden und darunter eine andere Möglichkeit geboten wie z.B. eine Schleife gegen eine Lambda Expression vereinfacht werden kann, einfach damit auch Personen welche noch nicht mit Lambda gearbeitet haben verstehen was hier passiert.

      Einige Dinge werden in den Folgekapiteln sicher einfacher gehen oder besser gelöst werden können, hierfür steht der Diskussionsthread zur Verfügung stehen. Auch werde ich nur die wichtigsten Zeilen kommentieren damit bei Anfängern der Lerneffekt nicht ausbleibt. Ich werde in VisualStudio 2017 Update 3 schreiben und die .Net Sprache VB.NET verwenden.
      Falls Ihr Fragen zu diesem Tutorial, den Code oder über mich habt freue ich mich über ein Mail oder eine PM von euch. Auch für Kritik bin ich natürlich immer offen. Mails bitte über die Forumsfunktion über mein Profil oder eine PM hier im Forum bzw. im Supportthread. Ich setze in dieser Tutorialreihe Kenntnisse in der objektorientierten Programmierung voraus und gehe davon aus das die Grundkenntnisse und Syntax von VB.Net soweit bekannt sind.

      1.2

      Aufbau dieser Tutorialreihe



      Es wird ca. 1-mal pro Woche ein Beitrag mit mindestens einem Kapitel hier online gestellt. Es kann vorkommen das auch mal 2 oder mehr Kapitel behandelt werden. Je nachdem wie ich dazu komme und Zeit habe. Falls es vorkommen sollte das ich mal eine Woche auslasse entschuldige ich mich bereits im Voraus dafür, bitte habt Verständnis das ich mal in Urlaub fahre oder beruflich etwas mehr Stress habe.
      Diese Tutorialreihe wird als "Hybrid" aufgebaut. Teile werden als normale Beiträge in reinem Text bzw. mit Bildern erstellt, andere Teile aber auch als Videocast.
      Es wird außerdem für jedes Kapitel ein ZIP File bzw. PDF online gestellt welches das Inhaltsverzeichnis und die Kapitel bis zum aktuellen Zeitpunkt enthält. Außerdem mit in dem ZIP File wenn vorhanden die VisualStudio Solution abwärtskompatibel bis Visual Studio 2015, sowie Links zu den Videos sofern vorhanden.
      Sollte ein Beitrag rein als Text ohne Video erstellt worden sein wird ein PDF mit in der ZIP enthalten sein damit jeder auch offline in Ruhe alles lesen kann.

      Sämtliche Verweise in den Solutions werden nur als NuGet Verweise in das jeweilige Projekt eingebunden um sicherzustellen das die Solution nach dem Download auch bei jedem läuft da NuGet automatisch nachgeladen wird wenn nicht vorhanden. Weitere Infos könnt Ihr hier nachlesen. Warum mit Videos? Man könnte jetzt sagen das ist totaler Quatsch und aus einem Video kann ich nichts rauskopieren und ich kann schwer später gezielt zu einem bestimmten Code springen um mir diesen nochmals anzusehen.
      Aber genau das sollte auch vermieden werden. Ich bin kein Freund von Copy&Paste. Nur wenn ich den Code tippe kann ich versuchen ihn zu lernen und zu verstehen. Auch bin ich der Meinung dass über ein Video viel mehr über die Funktionalität von der IntelliSense vermittelt werden kann. Außerdem kann ich in einem Video schöner gewisse Tastenkombinationen und Tricks vermitteln welche einem das tägliche Leben leichter machen oder einem viel Tipparbeit ersparen wie z.B. bei CodeSnippets.

      Das sind alles Gründe warum ich persönlich ein Video einem normalen Text/Bild Beitrag vorziehe. Bitte entschuldigt wenn ich in meinem Video evtl. mal in meinen österreichischen Dialekt falle. Ich werde mich bemühen so gut wie möglich in einem verständlichen Hochdeutsch zu sprechen.

      Grüße
      Sascha
      Dateien
      • 2.0.pdf

        (476,09 kB, 2.357 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Nofear23m“ ()

      2.0 Grundlagen der WPF

      2.0

      Grundlagen der WPF




      Wir kommen zu den Grundlagen. Die WPF bietet eine eigene "Deisgnersprache". XAML (Extensible Application Markup Language); ist eine Markupsprache und ist ähnlich im Aufbau wie XML.
      Der Großteil der Benutzeroberfläche wird in der WPF mit XAML erstellt.

      Was ist XAML?
      Im Grunde vereinfacht XAML das Erstellen einer UI einer .NET Anwendung. Es können sichtbare UI-Elemente im deklarativen XAML-Markup erstellt und anschließend die UI-definition mithilfe von Code-Behind, die über partielle Klassendefinitionen an das Markup geknüpft sind, von der Laufzeitlogik trennen.
      Die Darstellung passiert als Text über XML Dateien welche die Erweiterung .xaml aufweisen. Es kann mit jeder Codierung gearbeitet werden, typisch ist jedoch die Codierung als UTF-8.

      Ich drifte aber zu weit ab, hier ein Beispiel für einen XAML - Code:

      VB.NET-Quellcode

      1. <Window xml:Class="MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. Title="Window with Button"
      5. Width="250" Height="100">
      6. <!-- Button hinzufügen -->
      7. <Button Name="button">Klick mich!</Button>
      8. </Window>


      Hier werden ein Fenster und eine Schaltfläche mithilfe der Elemente Window und Button definiert. Jedes Element wird mit Attributen konfiguriert und korrespondiert mit den jeweiligen Properties des Elements. Zeile zwei und drei beinhalten die per Default eingetragenen XAML Namespaces, dazu kommen wir auch später mal, da möchte ich im Moment nicht so weit vorgreifen. Ein Window hat ein Property Title und kann hier mit dem Attribut Title geändert werden.
      Geben wir also für den Button beim Attribut 'Name' den Wert 'button' an wird dieser Wert in das Property 'Name' geschrieben. In diesem Fall handelt es sich um ein Dependency Property aber dazu kommen wir mal in einem der Videos zu sprechen.

      So würde dieser Code im Programm aussehen:



      Unter Windows 7 so:



      Im Hintergrund passiert nichts anderes als das die WPF die Objekte anhand Ihrer Attribute und deren Werten erstellt.
      Erstellen wir dieses Fenster mal im Code:

      VB.NET-Quellcode

      1. Dim MainWindow As New Window
      2. MainWindow.Title = "Window with Button"
      3. MainWindow.Width = 250
      4. MainWindow.Height = 100
      5. Dim myButton As New Button
      6. myButton.Name = "button"
      7. myButton.Content = "Klick mich!"
      8. AddHandler myButton.Click, AddressOf Button_Click
      9. MainWindow.Content = myButton


      Wenn wir nun den XAML mit dem Code vergleichen fällt uns ziemlich schnell auf wie die WPF das macht.
      In einem <Button/> wird z.B. Dim myButton As New Button erstellt.
      Jedes Attribut steht für ein Property der jeweiligen Klasse.
      z.B. wird myButton.Name = "button" in XAML zu Name="button"

      Wenn man diese Info hat wird man die XAML Syntax gleich viel schneller verstehen.

      Code Behind

      Ich lese immer wieder in Foren das kein Code Behind verwendet werden soll und das man unter WPF nur MVVM verwenden soll, alles andere wäre Quatsch.
      Doch das ist nicht korrekt, auch in Code von Microsoft und diversen großen Herstellern wie DevExpress, welcher einer der größten Komponentenhersteller im .Net Bereich ist, wird immer wieder über Code Behind gearbeitet. Zweifels ohne ist die WPF auf Binding und gewisse Pattern ausgerichtet wodurch man gewisse Vorteile erlangt wenn man diese verwendet.

      Dennoch ist es so dass ich für eine kleinere Anwendung kein MVVM empfehlen würde. Näheres in einem späteren Kapitel.
      Ähnlich wie bei WinForms kann ich nun einen Handler für den Button_Click erzeugen und in diesem meinen Code schreiben. Wir schreiben das Attribut 'Click' in den Button XAML Code und drücken zweimal Tab.Nun wird folgende Codezeile als Button vorhanden sein:

      XML-Quellcode

      1. <Button Name="button" Click="button_Click_1">Klick mich!</Button>


      Visual Studio hat uns nun den Code in der Code Behind des Windows erstellt.
      Mit einem Rechtsklick auf den Code des Click Attributes können wir nun mit "Gehe zu Definition" direkt zu diesem Code springen.

      Hier steht nun folgendes:

      VB.NET-Quellcode

      1. Private Sub button_Click_1(sender As Object, e As RoutedEventArgs)
      2. End Sub


      Und dies kennen wir nun ja wieder aus WinForms.
      Was anders ist, ist der RoutedEventArg, dies erkläre ich allerdings im Kapitel RoutedEvents, da möchte ich jetzt noch nicht vorgreifen.

      Ein weiteres Beispiel mit Attributen:

      XML-Quellcode

      1. <Button Background="Blue" Foreground="Red" Content="This is a button"/>


      Es wird die Schriftfarbe des Controls auf die Farbe "Red" gesetzt und der Hintergrund des Controls auf "Blue".

      Allerdings gibt es auch Eigenschaften eines Objekts welche nicht über die Attributsyntax gesetzt werden da diese z.B. zu komplex sind. Auf diese kann dann über die Eigenschaftenelementsyntax zugegriffen werden.
      Oft ist es aber auch Geschmacksache oder Übersichtlichkeit für welche Art man sich entscheidet.
      Hier ein Beispiel für das umgestalten des oben stehenden Codes unter Verwendung der Eigenschaftenelementsyntax:

      XML-Quellcode

      1. <Button>
      2. <Button.Background>
      3. <SolidColorBrush Color="Blue"/>
      4. </Button.Background>
      5. <Button.Foreground>
      6. <SolidColorBrush Color="Red"/>
      7. </Button.Foreground>
      8. <Button.Content>
      9. This is a button
      10. </Button.Content>
      11. </Button>


      Dies wäre nun derselbe Button nur das die Properties über die Eigenschaftenelementsyntax gesetzt wurden.
      Jetzt könnte man sich denken: "Wozu gibt es denn die Eigenschaftenelementsyntax wenn ich das mit der Attributsyntax ja auch machen kann?" Hierfür gibt es mehrere Gründe. Einer wäre z.B. dass die Background Eigenschaft eines Buttons ja eigentlich einen Brush erwartet. Warum kann man dann Background="Blue" anwenden? Die WPF stellt intern diverse TypeConverter bereit, so kann sie den String "Red" in einen SolidColorBrush mit der Color "Red" umwandeln. Das gleiche gilt für die Eigenschaft Content des Buttons welche von Typ „Object“ ist.
      Hier macht die WPF automatisch einen String daraus.
      Aber warum soll ich die lange Variante schreiben wenn ich die kurze doch auch schreiben könnte?

      In diesem Fall (SolidColorBrush) geht das noch alles über die Attributsyntax da die WPF den String ja in einen SolidColorBrush wandelt.
      Wenn man nun einen Farbverlauf als Hintergrund verwenden möchte muss man aber schon auf die Eigenschaftenelementsyntax zurückgreifen. z.B.:

      XML-Quellcode

      1. <Button>
      2. <Button.Background>
      3. <LinearGradientBrush>
      4. <LinearGradientBrush.GradientStops>
      5. <GradientStop Offset="0.0" Color="Red" />
      6. <GradientStop Offset="1.0" Color="Blue" />
      7. </LinearGradientBrush.GradientStops>
      8. </LinearGradientBrush>
      9. </Button.Background>
      10. <Button.Foreground>
      11. <SolidColorBrush Color="Red"/>
      12. </Button.Foreground>
      13. <Button.Content>
      14. This is a button
      15. </Button.Content>
      16. </Button>


      Hier wird beim ersten Button für die Hintergrundfarbe ein Farbverlauf gewählt.
      Dieser Farbverlauf geht von oben nach unten von Rot nach Blau. Und zwar gleichmäßig.
      Über das Offset könnte man hier Einfluss auf die "Geschwindigkeit" des Verlaufs nehmen.

      Gehen wir gleich zu den XAML Inhaltseigenschaften.
      Diese sind auch ganz interessant. In der WPF gibt es viele Controls welche ein Property Content oder Child enthalten.
      Oft sind diese Properties vom Typ Object. Da ein Object übergeben werden kann, kann dies im Grunde alles mögliche sein. Nehmen wir wieder als Beispiel den guten alten Button.
      Die folgenden Buttons sehen alle völlig gleich aus obwohl man dies auf den ersten Blick vielleicht nicht vermuten mag.

      XML-Quellcode

      1. <StackPanel>
      2. <Button>Test</Button>
      3. <Button>
      4. <Button.Content>
      5. Test
      6. </Button.Content>
      7. </Button>
      8. <Button Content="Test"/>
      9. <Button>
      10. <TextBlock Text="Test"/>
      11. </Button>
      12. <Button>
      13. <Button.Content>
      14. <TextBlock>
      15. <TextBlock.Text>
      16. Test
      17. </TextBlock.Text>
      18. </TextBlock>
      19. </Button.Content>
      20. </Button>
      21. </StackPanel>


      Hier ein Screenshot:


      XML-Quellcode

      1. <Border BorderThickness="2" BorderBrush="Blue" Margin="10">
      2. <StackPanel>
      3. <UniformGrid Columns="2">
      4. <TextBlock HorizontalAlignment="Center">Text1</TextBlock>
      5. <TextBlock HorizontalAlignment="Center"
      6. Grid.Column="1">Text2</TextBlock>
      7. </UniformGrid>
      8. <Button>
      9. <StackPanel Orientation="Horizontal">
      10. <Image Width="100"
      11. Source="http://www.vb-paradise.de/wcf/images/wbbLogo_vbp.png"/>
      12. <TextBlock Text="Gehe online" Margin="20,0,0,0"/>
      13. </StackPanel>
      14. </Button>
      15. </StackPanel>
      16. </Border>


      Hier wieder ein Screenshot:


      Ich denke das war jetzt erstmal genug Theorie, ich finde das man mit "lerning by doing" einfacher das gelernte behält. Sicher ist es gut wenn man weiß wie die Syntax aufgebaut ist, man muss aber auch damit umgehen können. Die wichtigsten Grundlagen der Syntax habe ich ja aufgezeigt, ich würde sagen wir legen jetzt bald mal los. Beim "basteln" der ersten Anwendung werden sicher viele Fragen bereits beantwortet. Ich werde auch versuchen meine Schritte immer zu kommentieren und euch hin und wieder verschiedene Wege zu zeigen um ans Ziel zu kommen.

      Weitere Grundlagen und Infos findet Ihr hier: Gut in verschiedene Kategorien unterteilt und mit vielen Beispielen. Habe diese Pages damals mehrfach gelesen.

      Ich werde in den nächsten Kapiteln eine Applikation mit reinem Code Behind erstellen und absichtlich auch aufzeigen das man auch ohne Binding in der WPF zurechtkommt, allerdings ist der Komfort nicht gegeben welchen die WPF eigentlich bietet.
      Es soll jetzt niemanden Animieren kein Binding zu verwenden, ich möchte nur aufzeigen das auch dies möglich ist und mich langsam an das Binding herantasten.
      So denke ich, kann man besser umdenken und verstehen wie das Binding funktioniert wenn man beide Wege kennt und parallelen ziehen kann.

      Fragen, Diskussionen, Lob und Kritik wieder im Supportthread!


      Im nächsten Kapitel werde ich das erste Video hochladen. Bis dann Leute :thumbup:
      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. ##

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

      Einführung in die WPF

      2.1

      Einführung in die WPF


      In diesem Kapitel werden wir ein paar Beispiele durchgehen. Das Hauptaugenmerk wird hier auf XAML gelegt. Wir werden die wichtigsten Controls kennenlernen, wie wir diese Anwenden und auch untereinander kombinieren können um somit ein völlig neues Aussehen und zum Teil neues Verhalten über Trigger und Animations in das Control bekommen. Außerdem werden wir lernen wie wir ein Fenster so gestallten das dieses völlig dynamisch auf Größenänderungen reagiert.

      Dabei werden die Grundlagen von DataBinding in der WPF durchgehen, erst anhand von Bindings innerhalb des Views wie Beispielsweise wie ich ein Property eines Controls auf ein Property eines anderen Controls Binden kann wo wir auch Converter kennenlernen werden aber auch mit Binding an eine selbst geschrieben Klasse bis zum Designtime-Support.


      Dieses Kapitel wird bereits einige Videos enthalten da in einem Video einfach besser Dinge wie Intellisense zur Geltung kommen. Bitte verzeiht mit nochmals wenn ich anfangs vielleicht nicht so geübt rüberkomme.

      2.1.1
      Die wichtigsten Controls und deren Verwendung (Video)



      Wir erstellen ein WPF Projekt und schreiben XAML, wobei wir aber aus gutem Grund auf Drag&Drop in den Designer verzichten werden, warum erkläre ich im Video.
      Wir lernen die wichtigsten Controls kennen und spielen damit dass sich diese an die Fenstergröße anpassen.

      Vorweg: Ich hatte als ich das Video letzte Woche aufnahm noch ein anderes Inhaltsverzeichnis. Bitte verzeiht mir das ich in der Powerpoint noch eine alte Kapitelnummer stehen habe.
      Ich werde mich bemühen solche Fehler in Zukunft zu unterlassen.





      Bitte nutzt für Fragen, Kritik und Lob wieder den SupportThread da dies ein Inkrementelles Tutorial ist!
      Dateien
      • 2.0.pdf

        (737,21 kB, 2.921 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.1.2 - Styles

      2.1.1.2

      Styles, Templates und Trigger. Heute: Styles




      Wir lernen Styles kennen und sehen uns an was es damit auf sich hat.
      Oft soll die Darstellung von Elementen desselben Typs innerhalb einer UI identisch sein. Wie im Web das CSS gibt es in der WPF hierfür Styles. Die Wiederverwendung von Stilen (Styles) erleichtert das Entwickeln und die Wartung eines UI.

      Hier ein Beispiel für einen Style:

      XML-Quellcode

      1. <Style TargetType="{x:Type Button}">
      2. <Setter Property="VerticalContentAlignment" Value="Top" />
      3. <Setter Property="HorizontalContentAlignment" Value="Right" />
      4. <Setter Property="FontWeight" Value="Bold" />
      5. <Setter Property="Margin" Value="0,0,0,5" />
      6. <Setter Property="Background" Value="Aqua"/>
      7. </Style>


      Styles können in XAML mit einem Key versehen oder über den Typ definiert werden. Gibt man einen Key an so kann man bei jedem Steuerelement über das „Style“ Attribut den Style als StaticResource bzw. DynamicResource angegeben.

      XML-Quellcode

      1. <Window.Resources>
      2. <Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
      3. <Setter Property="VerticalContentAlignment" Value="Top" />
      4. <Setter Property="HorizontalContentAlignment" Value="Right" />
      5. <Setter Property="FontWeight" Value="Bold" />
      6. <Setter Property="Margin" Value="0,0,0,5" />
      7. <Setter Property="Background" Value="Aqua"/>
      8. </Style>
      9. </Window.Resources>
      10. <Grid>
      11. <Button Content="Testbutton" Style="{StaticResource myButtonStyle}"/>
      12. </Grid>


      Styles welchen ein „TargetType“ angegeben wird greifen auf jedes Steuerelement dieses Typs unterhalb der Hierarchie. Gibt man in den Window-Resourcen einen Style mit dem TargetType „Button“ an, greift dieser Style auf jeden Button innerhalb dieses Fensters. Auch wenn sich das Steuerelement in einem UserControl befindet welches sich im Fenster befindet greift das Style auf Buttons innerhalb des UserControls. Stichwort: Vererbung.
      Allerdings können jederzeit einzelne Setter eines Styles überschrieben werden. Wenn ein Style die Hintergrundfarbe von Buttons auf BLAU festlegt sind alle Buttons blau. Möchte ich das für einen Button explizit ändern ohne auf das Style für alle anderen Buttons verzichten zu müssen kann ich bei diesem Button einfach mit Background="Green" diesen einen Setter des Style überschreiben, die anderen Setter bleiben allerdings uneingeschränkt vorhanden.

      Auch für Styles habe ich wieder ein Video für euch. 8o



      Viel Spaß mit dem Video, gerne könnt ihr mir ein Like und/oder ein Kommentar hinterlassen und wieder den SupportThread verwenden um Fragen zu stellen.
      Bis zum nächsten Video

      Liebe Grüße
      Sascha
      Dateien
      • 2.1.1.2.pdf

        (770,53 kB, 2.073 mal heruntergeladen, zuletzt: )
      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. ##

      2.1.1.2 - Templates

      2.1.1.2

      Styles, Templates und Trigger. Heute: Templates




      Bei Template müssen wir zunächst mal zwischen ControlTemplates, DataTemplates, ItemsPanelTemplates und HirachicalDataTemplates unterscheiden. Es ist erstmal wichtig zu wissen welche Art von Template man gerade benötigt um die aktuelle Aufgabenstellung meistern zu können. Nur wenn man weiß was für ein Template man gerade benötigt wird man über die Suchmaschine seiner Wahl auch korrekte Ergebnisse bekommen und erspart sich erstmal die Suche nach dem richtigen Begriff.

      Fangen wir mit den ControlTemplates an.

      Generell sind Steuerelemente in der WPF mit einer gewissen Logik versehen welche States, Styles, Events, Properties und ein Template beinhalten welche das Aussehen des Steuerelements wie z.b. einen Button definieren/steuern.Die Verbindung zwischen dieser Logik und dem Template passiert über Binding. Jedes Steuerelement besitzt ein Standard – Template welches für jede Windows-Version mitgeliefert wird. Unter Windows 7 sieht ein Button Beispielsweise anders aus als unter Windows 8/10.
      Dieses Template ist verpackt in einem Style, dem DefaultStyleKey und kann überschrieben werden. Diesen Style besitzt jedes UI Steuerelement. Ein Template ist definiert über ein Dependency Property mit dem Namen Template. Durch setzen dieses Property kann das Aussehen eines Steuerelementes völlig neu übernommen werden.

      Hier eine sehr gute Grafik von wpftutorials.net:

      Diese Grafik erklärt sehr gut wie ein solches einfaches Template aufgebaut ist und wie es auf Eigenschaftenänderung reagiert. Siehe Properties IsFocused oder IsEnabled.

      Sehen wir uns eine einfache Überschreibung eines Templates anhand eines Buttons an:

      XML-Quellcode

      1. <Style x:Key="MyButtonStyle" TargetType="Button">
      2. <Setter Property="Template">
      3. <Setter.Value>
      4. <ControlTemplate TargetType="{x:Type Button}">
      5. <Grid>
      6. <Path Data="M 0,0 A 100,100 90 0 0 100,100 L 100,100 100,0" Fill="{TemplateBinding Background}"
      7. Stroke="{TemplateBinding BorderBrush}"/>
      8. <ContentPresenter HorizontalAlignment="Center"
      9. VerticalAlignment="Center"/>
      10. </Grid>
      11. </ControlTemplate>
      12. </Setter.Value>
      13. </Setter>
      14. </Style>




      Hier wird ein ganz einfacher Button erstellt. Dieser besitzt weder Hover noch andere Styleelemente welche einen Button auszeichnen. Beispielsweise möchten wir ja wenn wir den Button klicken, dass dieser richtig hineingedrückt wird. Wenn ein Button „Disabled“ ist soll er ausgegraut sein. All diese Funktionalitäten Fehlen hier.

      Im folgenden Video sprechen wir nun darüber wie wir dies bewerkstelligen.
      Außerdem zeige ich euch wie ihr das Default-Template eines jeden Controls erfahren und anpassen könnt. Dies ist insofern Praktisch, wenn Ihr ein anderes Verhalten eines Buttons bewerkstelligen wollt, ohne ein neues Control erstellen zu müssen.

      Nun zu den DataTemplates

      DataTemplates helfen und dabei gewisse Daten oder Klassen so darzustellen wie wir das möchten. Beispielsweise Binden wir eine Property welche Autos beinhaltet an eine ListBox.Autos enthält viele Instanzen von der Klasse Auto. Die ListBox versucht nun mit den Daten etwas anzufangen und sucht nach einem DataTemplate. Erst bei sich selbst, dann in den Resourcen von darüber liegenden Controls, bis in der Hierarchie nichts mehr vorhanden ist, dann sucht sie in der Application.xaml nach einem DataTemplate.

      Nehmen wird eine einfache Klasse,- sagen wir mal diese soll ein Auto enthalten.
      Also erstellen wir uns eine Klasse „Auto“.

      VB.NET-Quellcode

      1. Public Class Auto
      2. Public Sub New()
      3. End Sub
      4. Public Sub New(marke As String, modell As String, ps As Integer)
      5. Me.Marke = marke : Me.Modell = modell : Me.PS = ps
      6. End Sub
      7. Public Sub New(marke As String, modell As String, ps As Integer, logo As Uri)
      8. Me.Marke = marke : Me.Modell = modell : Me.PS = ps : Me.Logo = logo
      9. End Sub
      10. Public Property Marke As String
      11. Public Property Modell As String
      12. Public Property PS As Integer
      13. Public Property Logo As String
      14. End Class


      In der CodeBehind des MainWindow sorgen wird jetzt einfach dafür das eine ObservableCollection (Auflistung) mit verschiedenen Auto`s befüllt wird.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AutoListe = New ObservableCollection(Of Auto)
      3. With AutoListe
      4. .Add(New Auto("Audi", "R8 V10+", 610, "BrandImages/Audi.png"))
      5. .Add(New Auto("VW", "Arteon TDI 4 Motion", 240, "BrandImages/Vw.png"))
      6. .Add(New Auto("Seat", "Leon ST Cupra TSI DSG", 300, "BrandImages/SEAT.png"))
      7. .Add(New Auto("Skoda", "Octavia RS", 230, "BrandImages/Skoda.png"))
      8. .Add(New Auto("Lamborghini", "Aventador LP 700", 700, "BrandImages/Lambo.png"))
      9. .Add(New Auto("Bentley", "Continental Supersports", 630, "BrandImages/Bentley.png"))
      10. End With
      11. Me.DataContext = Me
      12. End Sub
      13. Public Property AutoListe As ObservableCollection(Of Auto)


      Im View (MainWindows.xaml) müssen wir nun nur noch eine ListBox erstellen und diese auf das Property „AutoListe“ binden. Fertig.
      Aber was sehen wir nun genau?

      Hier eine ListBox gebunden an Autos ohne DataTemplate:


      Nicht so schön. Wieder schlägt der TypeConverter der WPF zu.
      Die WPF wirft keinen Fehler sondern versucht die Daten welche ihr (ist die WPF ne Frau?) übergeben werden einfach irgendwie zu Rendern. Was liegt näher als .ToString aufzurufen. Und genau das macht sie.

      Wir könnten nun die ToString-Methode in der Klasse „Auto“ überschreiben um einen „verständlichen“ Text in die ListBox zu bekommen.

      VB.NET-Quellcode

      1. Public Overrides Function ToString() As String
      2. Return $"Marke: {Marke }, Modell: {Modell } Leistung: {PS }"
      3. End Function


      Was uns folgendes Ergebnis liefert:


      OK, schon um einiges besser, aber lange noch nicht zufriedenstellend. Wir befinden uns in der WPF, das geht ja mal sicher viel besser und schöner oder?
      Richtig. Mittels DataTemplate kann man nun bestimmen wie das Template für ein ListItem aussehen soll. Template = Vorlage. Also erstellen wir eine Vorlage und geben der WPF die Info für was (die Klasse Auto) dieses Template verwendet werden soll.

      Hier ein DataTemplate direkt in das ItemTemplate der ListBox:

      XML-Quellcode

      1. <ListBox ItemsSource="{Binding AutoListe}" Margin="10">
      2. <ListBox.ItemTemplate>
      3. <DataTemplate>
      4. <Grid>
      5. <Grid.ColumnDefinitions>
      6. <ColumnDefinition Width="60"/>
      7. <ColumnDefinition Width="*"/>
      8. </Grid.ColumnDefinitions>
      9. <Border Margin="5" BorderBrush="Black" BorderThickness="1">
      10. <Image Source="{Binding Logo}" Stretch="Uniform" Width="50" Height="50" RenderOptions.BitmapScalingMode="HighQuality" />
      11. </Border>
      12. <StackPanel Grid.Column="1" Margin="5">
      13. <StackPanel Orientation="Horizontal">
      14. <TextBlock Text="{Binding Path=Marke}" />
      15. <TextBlock Text="{Binding Path=Modell}" Padding="3,0,0,0"/>
      16. </StackPanel>
      17. <TextBlock FontWeight="Bold" >
      18. <Run Text="{Binding PS,FallbackValue=0}"/>
      19. <Run Text=" PS"/>
      20. </TextBlock>
      21. </StackPanel>
      22. </Grid>
      23. </DataTemplate>
      24. </ListBox.ItemTemplate>
      25. </ListBox>


      Dieses DataTemplate ändert nun das Aussehen eines ListBox-Items wie folgt:



      Mehr im Video weiter unten, aber jetzt gehen wir mal weiter zu dem ItemsPanelTemplates:

      ItemsPanelTemplates

      Diese Art von Templates erlaubt es uns das Layout, wie Items eines ItemControls wie z.b. einer ListBox zu bestimmen. Jeder ItemsControl besitzt von Haus aus ein „Default Panel“.

      Die ListBox z.b. besitzt von Haus aus ein VirtualizingStackPanel als PanelTemplate.
      Dieses ist im Grunde ein normales StackPanel welches ein Zusatzfeature besitzt, welches ermöglicht das nicht alle Elemente sofort gerendert werden sondern erst dann wenn diese z.b. durch Scrollen auftauchen was Resourcen spart und stark auffällt wenn man mehrere tausend Items in der Liste hat.

      Um dieses Template nun zu überschreiben, erstellen wir einfach ein ItemsPanelTemplate und suchen uns ein Panel-Control aus welches uns besser passt.

      XML-Quellcode

      1. <ListBox ItemsSource="{Binding AutoListe}">
      2. <ListBox.ItemsPanel>
      3. <ItemsPanelTemplate>
      4. <UniformGrid Columns="4"/>
      5. </ItemsPanelTemplate>
      6. </ListBox.ItemsPanel>
      7. </ListBox>


      In diesem Fall nehmen wir jetzt mal ein UniformGrid mit vier Spalten. Dieses UniformGrid wird nun die Items auf vier Spalten aufteilen. Das fünfte Element würde dann in eine neue Zeile verschoben werden.

      Mit dem Codeschnipsel von oben würde nun folgendes generiert werden:


      Wir können sehen das wir vier Spalten und zwei Zeilen haben. Wieder hat die WPF unsere .ToString Methode in der Klasse Auto aufgerufen.
      Aber das wir DataTemplates oben bereits durchgenommen haben ist es für uns ja jetzt ein leichtes ein solches DataTemplate abermals zu implementieren.
      Wir können gleich unser DataTemplate von oben auch in dieser ListBox verwenden, mit dem Unterschied das wir das soeben erstellte ItemsPanelTemplate auch mit in der ListBox behalten:

      XML-Quellcode

      1. <ListBox ItemsSource="{Binding AutoListe}">
      2. <ListBox.ItemsPanel>
      3. <ItemsPanelTemplate>
      4. <UniformGrid Columns="4"/>
      5. </ItemsPanelTemplate>
      6. </ListBox.ItemsPanel>
      7. <ListBox.ItemTemplate>
      8. <DataTemplate>
      9. <Grid>
      10. <Grid.ColumnDefinitions>
      11. <ColumnDefinition Width="60"/>
      12. <ColumnDefinition Width="*"/>
      13. </Grid.ColumnDefinitions>
      14. <Border Margin="5" BorderBrush="Black" BorderThickness="1">
      15. <Image Source="{Binding Logo}" Stretch="Uniform" Width="50" Height="50" RenderOptions.BitmapScalingMode="HighQuality" />
      16. </Border>
      17. <StackPanel Grid.Column="1" Margin="5">
      18. <StackPanel Orientation="Horizontal">
      19. <TextBlock Text="{Binding Path=Marke}" />
      20. <TextBlock Text="{Binding Path=Modell}" Padding="3,0,0,0"/>
      21. </StackPanel>
      22. <TextBlock FontWeight="Bold" >
      23. <Run Text="{Binding PS,FallbackValue=0}"/>
      24. <Run Text=" PS"/>
      25. </TextBlock>
      26. </StackPanel>
      27. </Grid>
      28. </DataTemplate>
      29. </ListBox.ItemTemplate>
      30. </ListBox>


      Hierdurch erhalten wir wieder die Optik unseres Items aber in der Anordnung wie wir es im ItemsPanelTemplate vorgegeben hatten:


      Wir sehen, alle Elemente werden so gerendert wie wir das haben wollten und in dem Schema wie wir dies vorgegeben haben. So eröffnen sich tolle möglichkeiten für eine neue Anwendungsoptik und eine bessere und flexiblere Benutzerführung.
      Langsam dürfte wiedermal ein kleiner WOW-Effekt einsetzen was nun alles möglich ist mit ein paar Zeilen XAML ;)

      Last but not least, HierarchialDataTemplates

      Mit HierarchialDataTemplate wie der Name vermuten lässt kann ich bestimmen wie Daten welche in Form einer Hierarchie vorliegen darstellen.
      Tja, wo liegt nun der Einsatz von einem solchen Template? Z.b. bei der Verwendung eines TreeViews oder eines Menus.

      Wir alle kennen ein Menu:


      Erstmal haben wir die erste Ebene (Root) mit „Datei“, „Bearbeiten“, Ansicht“, usw.
      Danach haben wir eine Unbestimmte Anzahl an Ebenen welche wieder eine unbestimmte Anzahl an weiteren Kind-Ebenen haben kann.

      Auch für solch einen Fall hat die WPF vorgesorgt, und ermöglicht uns das Binding.
      Damit wir allerdings auch bestimmen können wie die Darstellung passiert gibt es eben die HierarchialDataTemplates. Besser darstellen lässt sich solch ein unterfangen mit einem TreeView.

      Als Fallbeispiel nehmen wir mal eine Art Stammbaum.
      Wie soll es anders sein nehme ich den Stammbaum der Familie Porsche/Piech:

      Erstmal erstelle ich zwei Klassen. Zum einen die Klasse „Parent“ und zum anderen die Klasse „Child“. Ich denke die Namen sprechen für sich.


      Jedes Parent kann X Childs (Kinder) haben. Jedoch kann jedes ChildElement wieder X Childs (Kinder) haben, usw. Füllen wir diese Klassen mit ein paar Daten von hier und konzentrieren wir uns wieder auf den XAML.
      Wir erstellen ein TreeView und geben ein HierarchialDataTemplate an:

      XML-Quellcode

      1. TreeView DataContext="{Binding}" ItemsSource="{Binding HierarchieDaten}">
      2. <TreeView.ItemTemplate>
      3. <HierarchicalDataTemplate ItemsSource="{Binding Kinder}">
      4. <StackPanel>
      5. <StackPanel Orientation="Horizontal">
      6. <Rectangle Width="10" Height="10" Fill="Red"/>
      7. <TextBlock Text="{Binding VollerName}"/>
      8. </StackPanel>
      9. <TextBlock>
      10. <Run Text="Gegr.: "/>
      11. <Run Text="{Binding GruenderVon}"/>
      12. </TextBlock>
      13. </StackPanel>
      14. </HierarchicalDataTemplate>
      15. </TreeView.ItemTemplate>
      16. </TreeView>


      Das sieht ja gar nicht so kompliziert aus. Das TreeView selbst haben wir an ein Property mit dem Namen „HierachieDaten“ gebunden. Dieses befindet sich in der CodeBehind und ist vom Typ Parent.
      Wie wir im Diagramm sehen konnten hat dieses ein Property Kinder. Dieses kann viele Child halten und Child wiederum auch viele Childs.

      Beim Erstellen des HierarchialDataTemplates geben wird für dieses dann als ItemsSource das Property an welches innerhalb der Elternklasse die Childs hält. Und der Rest ist einfach nur XAML welches bestimmt wie ein TreeViewItem dann für dieses Template aussehen soll.

      Folgende Ansicht wird in diesem Fall generiert:


      Soweit sieht das ja schon ganz gut aus. Wie wir aber sehen können hat nicht jedes Kind der Familie wieder etwas oder eine Marke gegründet. Da wir aber im Template hinterlegt haben das ein Item so gerendert werden soll wird uns der Text „Gegr.:“ angezeigt, da die Klasse Child allerdings kein solches Property besitzt kann die WPF gar nicht darauf Binden.

      Dies macht sich auch im Ausgabefenster bemerkbar:


      OK, wir wissen also das wir nicht am Holzweg sind, aber es hier noch Luft nach oben gibt, es muss ja möglich sein für jede Art von Element eine eigene Optik zu definieren.
      Naja, vorher hatten wir gerade noch die DataTemplates gelernt, das wäre doch eine Möglichkeit gleich das gelernte anhand dieses Beispiels zu versuchen.

      Wir müssen also ein wenig umdenken. Wir möchten mehrere HierarchialDataTemplates definieren, werden jedoch scheitern wenn wir versuchen noch ein Template darunter zu erstellen. Wir versuchen es in den Ressourcen:

      XML-Quellcode

      1. <TreeView DataContext="{Binding}" ItemsSource="{Binding HierarchieDaten}">
      2. <TreeView.Resources>
      3. <HierarchicalDataTemplate ItemsSource="{Binding Kinder}" DataType="{x:Type local:Parent}">
      4. <StackPanel>
      5. <StackPanel Orientation="Horizontal">
      6. <Rectangle Width="10" Height="10" Fill="Red"/>
      7. <TextBlock Text="{Binding VollerName}"/>
      8. </StackPanel>
      9. <TextBlock>
      10. <Run Text="Gegr.: "/>
      11. <Run Text="{Binding GruenderVon}"/>
      12. </TextBlock>
      13. </StackPanel>
      14. </HierarchicalDataTemplate>
      15. <HierarchicalDataTemplate ItemsSource="{Binding Kinder}" DataType="{x:Type local:Child}">
      16. <StackPanel Orientation="Horizontal">
      17. <Rectangle Width="10" Height="10" Fill="Blue"/>
      18. <TextBlock Text="{Binding VollerName}"/>
      19. </StackPanel>
      20. </HierarchicalDataTemplate>
      21. </TreeView.Resources>
      22. </TreeView>


      Wenn wir uns dieses TreeView ansehen werden wir die Stirn runzeln.
      Zwei Templates? JA, das erste Template dient zur Anzeige eines Parent-Objekts. Zu erkennen an der Angabe des DataType: DataType="{x:Type local:Parent}"
      Das zweite Template dient zur Anzeige des Child-Objekts: DataType="{x:Type local:Child}"
      Die WPF entscheidet nun anhand des Übergebenen Typs jedes Knotens wie dieser gerendert werden soll.

      Hier das Ergebnis:


      Man kann schön erkennen das beim Child-Knoten nicht nur das Quadrat in einer anderen Farbe erstrahlt sondern auch das hier der TextBlock mit „Gegr.:“ fehlt, da wir diesen TextBlock ins Child-Template nicht mit eingebaut haben.
      Eine schöne und saubere Sache.

      In folgendem Video gehe ich etwas näher darauf ein und zeige euch verschiedene Templates anhand von einigen Beispielen:



      Ich wünsche euch viel spass mit dem Video, bitte hinterlasst mir ein Like und/oder ein Kommentar, und verwendet für Fragen gerne den Supportthread.
      Anbei die Sulution und wieder das aktuellste PDF.


      Grüße und bis zum nächsten Tutorial.
      Sascha
      Dateien
      • WPF_Templates.zip

        (1,05 MB, 611 mal heruntergeladen, zuletzt: )
      • 2.1.1.2_#2.pdf

        (964,8 kB, 1.900 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.1.2

      Styles, Templates und Trigger. Heute: Trigger




      Auch bei Triggern müssen wir wieder unterscheiden da es hier mehrere Arten von Triggern gibt.
      Folgende Trigger gibt es:
      • Trigger
      • DataTrigger
      • Multitrigger
      • MultiDataTrigger
      • EventTrigger
      Trigger werden überwiegend in Styles oder ControlTemplates verwendet. Er Triggert ein Property auf ein anderes Property dieses Controls.
      Beispiel: Ein Trigger soll die Hintergrundfarbe des Buttons auf ROT setzen wenn das Property IsMouseOver True ist.

      XML-Quellcode

      1. <Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
      2. <Style.Triggers>
      3. <Trigger Property="IsMouseOver" Value="True">
      4. <Setter Property="Background" Value="Red" />
      5. </Trigger>
      6. </Style.Triggers>
      7. </Style>


      Ein DataTrigger wird bei Datenbindung eingesetzt und wird überwiegend in DataTemplates verwendet. Beispielsweise kann ein DataTrigger verwendet werden um die Hintergrundfarbe eines Controls in ROT zu ändern wenn das Property Alert der Klasse True ist. DataTrigger können allerdings auch in ControlTemplates nützlich sein um einen roten Rahmen um ein Controls zu machen wenn in der Klasse Kontoinfo das Property Kontostand weniger als 0 Beträgt. Hierfür kann ein IValueConverter bestimmten das bei negativen Werten der Converter True zurückgibt und sohin der Rahmen Rot gezeichnet wird.

      XML-Quellcode

      1. <DataTrigger Binding="{Binding Alert}" Value="True">
      2. <Setter Property="Background" Value="Red"></Setter>
      3. </DataTrigger>


      MultiTrigger und MultiDataTrigger sind im Grunde dasselbe wie die beiden Triggerarten oben nur das mehrere Bedingungen angegeben werden können. Ein MultiDataTrigger würde dann nur greifen wenn alle Bedingungen erfüllt sind.

      XML-Quellcode

      1. <DataTemplate.Triggers>
      2. <MultiDataTrigger>
      3. <MultiDataTrigger.Conditions>
      4. <Condition Binding="{Binding Path=Picture}" Value="{x:Null}" />
      5. <Condition Binding="{Binding Path=Title}" Value="Waterfall" />
      6. </MultiDataTrigger.Conditions>
      7. <MultiDataTrigger.Setters>
      8. <Setter TargetName="viewImage" Property="Source" Value="/Images/noImage.png"/>
      9. <Setter TargetName="viewImage" Property="Opacity" Value="0.5" />
      10. <Setter TargetName="viewText" Property="Background" Value="Brown" />
      11. </MultiDataTrigger.Setters>
      12. </MultiDataTrigger>
      13. </DataTemplate.Triggers>


      EventTrigger schließlich sind Trigger welche auf ein Event reagieren können.
      Beispiel: Der Hintergrund eines Buttons soll sich ändern wenn sich die Maus über den Button fährt. EventTrigger werden Beispielsweise oft in Storyboards verwendet um z.b. eine Animation zu starten.

      XML-Quellcode

      1. <Border Name="border1" Width="100" Height="30"
      2. BorderBrush="Black" BorderThickness="1">
      3. <Border.Background>
      4. <SolidColorBrush x:Name="MyBorder" Color="LightBlue" />
      5. </Border.Background>
      6. <Border.Triggers>
      7. <EventTrigger RoutedEvent="Mouse.MouseEnter">
      8. <BeginStoryboard>
      9. <Storyboard>
      10. <ColorAnimation Duration="0:0:1"
      11. Storyboard.TargetName="MyBorder"
      12. Storyboard.TargetProperty="Color" To="Gray" />
      13. </Storyboard>
      14. </BeginStoryboard>
      15. </EventTrigger>
      16. </Border.Triggers>
      17. </Border>


      Da Animationen wiederum ein oder mehrere Kapiteln füllen würde, ich darauf aber nicht näher eingehen werde da ich der meinung bis das diese eigentlich relativ selten gebraucht werden, hier ein Link wo Ihr alle Wissenswertes über Animationen gut erklärt bekommt. WPF Basic Animations
      Wer sich hier durchklickt kommt bald zu den Easing Functions, zu den Key Frame Animations und schließlich zu den PathAnimations

      In folgendem Video zeige ich euch nun wie wir mit Triggern umgehen und wie wir das Verhalten von Controls beeinflussen können ohne viele, viele Codezeilen schreiben zu müssen. Selbst wenn wir Converter schreiben bestehen diese meist nur aus 1-5 Zeilen Code. Eine saubere und übersichtliche Sache.



      Viel Spaß mit dem Video, gerne könnt ihr mir ein Like und/oder ein Kommentar hinterlassen und wieder den SupportThread verwenden um Fragen zu stellen.
      Bis zum nächsten Video

      Liebe Grüße
      Sascha
      Dateien
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ () aus folgendem Grund: Anhänge eingefügt und Links korrigiert.

      2.1.1.3

      Controls eine neue Optik verpassen, inkl. deren Verhalten




      In den vorigen drei Kaptiteln haben wir erfahren wie wir mittels Styles, Triggern und Templates Controls anpassen. In jedem dieser Kapitel haben wir mehr oder weniger entweder das Aussehen oder das Verhalten eines Controls beeinflussen können. Nun ist es an der Reihe das erlernte wissen umzusetzen und ein Control komplett zu überarbeiten.

      Ich habe mir die Checkbox ausgesucht, einfach weil dies ein Control ist welchen von Haus aus nicht unbedingt sehr gut aussieht und meisst als erstes abgeändert werden möchte.
      Was wollen wir erreichen:

      Jeder kennt eine Checkbox:


      Diese ist nicht sonderlich schön. Ich dachte mir, wir machen eine Kopie des SwitchButton aus dem MobileOS Android:


      In folgendem Video erkläre ich euch wie Ihr eine Checkbox in genau dieser Optik erstellt. Ohne eine Zeile VB Code. Inkl. Animation des "Knopfes". Also das wenn die Checkbox ihren State ändert oder der User darauf klickt der Knopf nach links oder nach rechts "slidet".
      Wie Ihr euch sicher vorstellen könnt kann all dies über ein ControlTemplate erledigt werden.

      Viel spass mit dem Video:



      Für die die mit einem Video nicht viel anfangen habe ich natürlich wieder das Projekt angehängt.


      Viel Spaß mit dem Video, gerne könnt ihr mir ein Like und/oder ein Kommentar hinterlassen und wieder den SupportThread verwenden um Fragen zu stellen.
      Bis zum nächsten Video

      Liebe Grüße
      Sascha
      Dateien
      • 2.1.1.3_ControlOptik.zip

        (253,58 kB, 454 mal heruntergeladen, zuletzt: )
      • 2.1.1.3.pdf

        (997,96 kB, 1.844 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.2

      XAML Namespaces




      In diesem Thema widmen wir uns den XAML-Namespaces.
      Wir klären was es mit den Namespaces auf sich hat und wie wir diese Verwenden und eigene Namespaces definieren.

      Was ist ein XAML-Namespace?
      Wie in XML-Namespaces ist ein XAML-Namespace eine Erweiterung dieses Konzepts.
      Eine etwas technische Beschreibung des Begriffs gibt es auf MSDN unter folgenden Link: msdn.microsoft.com/de-de/library/ms747086(v=vs.110).aspx
      Ich möchte es allerdings etwas verständlicher beschreiben. Im Grunde kann man sich einen Namespaceimport vorstellen wie in VB einen "Import" im Code. Eine ganz gute Beschreibung findet man auch hier im Forum vom User @ErfinderDesRades unter folgendem Link: XAML Syntax
      Im Grunde kann jeder CLR Namespace auch in XAML eingebunden werden. Beispiel:

      In VB.Net kann auch ein Import eines Namespace mit einem Präfix definiert werden.

      VB.NET-Quellcode

      1. Imports FileImport = System.IO
      2. Module Module1
      3. Sub Main()
      4. If FileImport.File.Exists(path) Then
      5. 'Do something
      6. End If
      7. End Sub
      8. End Module


      Solch ein Import im Code könnte wie folgt in XAML aussehen:

      XML-Quellcode

      1. xmlns:FileImport="clr-namespace:System.IO;assembly=mscorlib"


      Der Sinn eines Imports des System.IO Namespace sei jetzt mal dahingestellt, damit soll nur gezeigt werden das wirklich jeder beliebige Namespace eingebunden werden könnte wie folgender Screenshot zeigt:



      In einem Window sehen wir auch immer einen Namespace (im obigen Beispiel in der zweiten Zeile) welcher keinen Präfix hat.

      XML-Quellcode

      1. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


      Dieser Namespace wird als „Default“ gesehen. Es muss also kein Präfix vorangestellt werden. Beispielsweise befinden sich alle WPF Controls in diesem Namespace wie z.b. ein Button weshalb man in XAML einfach <Button/> schreiben kann um einen Button zu erstellen.
      Jetzt fällt uns an diesem Beispiel aber auch auf das wir Namespaces über den CLR Namen eingebunden haben und andere wiederum mit einer URL. Aber was hat die URL zu bedeuten?
      Dazu später mehr...

      Erstellung eigener Namespaces

      Wir erstellen uns im Projekt eine Klasse mit dem Namen FahrzeugeVm sowie eine Klasse Fahrzeug.
      Die FahrzeugeVm-Klasse enthält ein Property mit dem Namen Fahrzeuge vom Typ ObservableCollection(Of Fahrzeug).
      Fahrzeuge kann also viele Instanzen von Fahrzeug enthalten. Beide Klassen befinden sich im Namespace ViewModel unseres Projekts.
      Ich setzte jetzt mal voraus das jeder Namespaces in VB.Net kennt und weis was es mit Namespaces auf sich hat und wie diese funktionieren. Ansonsten kann dies hier in den Microsoft Docs nachgelesen werden.

      Zur Vereinfachung habe ich hier beide Klassen in eine Datei geschrieben.

      VB.NET-Quellcode

      1. Imports System.Collections.ObjectModel
      2. Namespace ViewModel
      3. Public Class FahrzeugeVm
      4. Public Sub New()
      5. Fahrzeuge = New ObservableCollection(Of Fahrzeug) From {
      6. New Fahrzeug() With {.Marke = "Volkswagen", .Modell = "Passat", .LeistungPs = 128},
      7. New Fahrzeug() With {.Marke = "Seat", .Modell = "Ibiza", .LeistungPs = 89},
      8. New Fahrzeug() With {.Marke = "Audi", .Modell = "A3", .LeistungPs = 150}}
      9. End Sub
      10. Public Property Fahrzeuge As ObservableCollection(Of Fahrzeug)
      11. End Class
      12. Public Class Fahrzeug
      13. Public Property Marke As String
      14. Public Property Modell As String
      15. Public Property LeistungPs As Integer
      16. Public Overrides Function ToString() As String
      17. Return $"{Marke} {Modell} hat {LeistungPs} PS"
      18. End Function
      19. End Class
      20. End Namespace


      In einem Window möchten wir die Fahrzeuge in einer ListBox darstellen damit wir alle Fahrzeuge sehen können. Wir erstellen uns in unserem Fenster eine ListBox.

      XML-Quellcode

      1. <Window x:Class="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:_2_1_2_2_XamlNamespaces"
      7. mc:Ignorable="d"
      8. Title="MainWindow" Height="150" Width="200">
      9. <Grid>
      10. <ListBox SelectedIndex="0"/>
      11. </Grid>
      12. </Window>


      Als nächstes möchten wir für unser Window einen Datencontext (DataContext) festlegen damit wir die ListBox darauf binden und sohin die Fahrzeuge anzeigen lassen können.
      Wir fügen also erstmal unseren Namespace ViewModel in dem XAML Namespaces an und vergeben einen Präfix. Ich entscheide mich jetzt mal für den Präfix vm.

      XML-Quellcode

      1. xmlns:vm="clr-namespace:_2_1_2_2_XamlNamespaces.ViewModel"


      Dann sieht unser XAML Code folgendermaßen aus:

      XML-Quellcode

      1. <Window x:Class="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:_2_1_2_2_XamlNamespaces"
      7. xmlns:vm="clr-namespace:_2_1_2_2_XamlNamespaces.ViewModel"
      8. mc:Ignorable="d" Title="MainWindow" Height="150" Width="200">
      9. <Grid>
      10. <ListBox SelectedIndex="0"/>
      11. </Grid>
      12. </Window>


      Auch hier haben wir IntelliSense wenn wir zwischendurch mal Kompiliert haben.
      Einfach: xmlns:vm= eingeben und es erscheint eine Liste von verfügbaren Namespaces.
      Hier muss der Namespace auch nicht mühselig rausgesucht werden. Einfach beginnen „viewmodel“ einzugeben und schon werden die Ergebnisse gefiltert. Siehe Video zu diesem Kapitel.



      Praxistip: Habt ihr mal einen Namespace oder ein Property nicht in der IntelliSense kann dies daran liegen dass zwischenzeitlich nicht kompiliert wurde. Hin und wieder mal das komplette Projekt zu kompilieren
      (im Menu Erstellen -> Projektmappe erstellen oder mit der Tastenkombination STRG + SHIFT + B kann oft „wunder“ wirken.



      Jetzt haben wir unseren eigenen Namespace in XAML eingebunden und können nun unseren DataContext für unser Window setzen, denn unsere FahrzeugeVm-Klasse möchten wir nun als Grundlage für unser Fenster verwenden.
      Das machen wir indem wir den DataContext unseres Fensters setzen mit:

      XML-Quellcode

      1. <Window.DataContext>
      2. <vm:FahrzeugeVm/>
      3. </Window.DataContext>


      Ab jetzt hat das Fenster, und durch die Vererbung alle dem Fenster untergeordneten Objekte unsere FahrzeugeVm Klasse als Datenkontext und können somit auf dessen Eigenschaften zugreifen was in unserem Fall ja „nur“ die Eigenschaft „Fahrzeuge“ ist.
      Unser Window sieht nun wie folgt aus:

      XML-Quellcode

      1. <Window x:Class="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:_2_1_2_2_XamlNamespaces"
      7. xmlns:vm="clr-namespace:_2_1_2_2_XamlNamespaces.ViewModel"
      8. mc:Ignorable="d" Title="MainWindow" Height="150" Width="200">
      9. <Window.DataContext>
      10. <vm:FahrzeugeVm/>
      11. </Window.DataContext>
      12. <Grid>
      13. <ListBox SelectedIndex="0"/>
      14. </Grid>
      15. </Window>


      Wenn wir nun unsere ListBox an unser Property in der FahrzeugeVm Klasse binden möchten müssen wir nur das ItemsSource Property der ListBox an das Property Fahrzeuge in unserem DatenContext Binden.

      Es wird euch sicher aufgefallen sein das wir hier kein IntelliSense hatten als wird Das Binding eingegeben haben. Das liegt daran das wir zwar den DatenContext für die Laufzeit eingegeben haben aber keinen für die DesignTime (Entwicklungszeit).
      Dies kommt zwar noch genauer in einem späteren Kapitel aber ich zeige es hier schon mal schnell vor:
      Innerhalb des Window-Knoten geben wir wie folgt den DesigntTime-DataContext an:

      XML-Quellcode

      1. d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True, Type={x:Type vm:FahrzeugeVm}}"


      Wie man sieht machen wir auch hier wieder Gebrauch von unserem Namespace.
      Jetzt haben wir auch zum Binden die IntelliSense zur Verfügung. Starten wir nun unser Programm sehen wir die Einträge in der ListBox:



      Vorhin in diesem Kapitel haben wir ja bereits gesehen das es auch Namespaces gibt welche mit einer URI statt mit dem CLR Namespace angegeben werden, dies kann mehrere Vorteile haben.
      Der wohl größte ist das man hierbei mehrere Namespaces zusammenführen kann.

      Beispiel: Der per Default ohne Präfix in jedem Window vorhandene Namespace enthält alle Controls welche es für die WPF von Microsoft Seite gibt. Aber ihr könnt euch vorstellen dass sich nicht alle Controls in ein und demselben CLR Namespace befinden.
      Damit man nicht X Namespaces in jedem Window immer und immer wieder einbinden muss gibt es diese Möglichkeit. Beim Namespace http://schemas.microsoft.com/winfx/2006/xaml/presentation beispielsweise sind z.b. unter anderem die Namespaces System.Windows und System.Windows.Controls eingebunden.
      Geben wir die URL in den Browser ein bekommen wir entweder eine 404 Antwort (Die Seite kann nicht gefunden werden) oder wir bekommen einfach einen Text das die Seite nicht verfügbar ist.
      Aber wie weis die WPF was man mit der URL meint, und viel wichtiger, woher kommt die URL?

      Dies wird klarer, wenn wir selbst versuchen einen solchen Namespace zu definieren. Im folgenden Video werden wir das soeben gelesene nochmals durchgehen und auch Namespaces mit URI definieren und einsetzen.

      Viel spass mit dem Video:



      Für die die mit einem Video nicht viel anfangen habe ich natürlich wieder das Projekt angehängt.


      Viel Spaß mit dem Video, gerne könnt ihr mir ein Like und/oder ein Kommentar hinterlassen und wieder den SupportThread verwenden um Fragen zu stellen.
      Bis zum nächsten Video

      Liebe Grüße
      Sascha
      Dateien
      • 2_1_2_2_XamlNamespaces.zip

        (296,88 kB, 526 mal heruntergeladen, zuletzt: )
      • 2.1.2.pdf

        (1,13 MB, 1.801 mal heruntergeladen, zuletzt: )
      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. ##

      2.1.3 Resourcen

      2.1.2

      Resourcen



      Was sind Resourcen und was bringen sie mir

      Grundsätzlich gibt es in einer Anwendung zwei Arten von Resourcen. Die XAML Resourcen und die Anwendungsresourcen.
      Eine Resource ist ein Objekt welches an unterschiedlichen Stellen einer Anwendung erneut verwendet werden kann. Die Anwendungsresourcen kennt Ihr sicher aus WinForms auch, dies sind nicht ausführbare Datendateien die eine Anwendung zur Laufzeit benötigt wie z.b. ein Icon oder Bild.
      Ein Beispiel für XAML Resourcen sind Brushes oder Styles. Diese können an einer Stelle definiert werden und stehen anschließend zur Verfügung. In XAML besitzt jedes Framework-Element (FrameworkElement oder FrameworkContentElement) eine Resources-Eigenschaft. Alle Elemente welche von FrameworkElement erben besitzen somit auch die Resources Eigenschaft. Hier ist interessant zu wissen das Resourcen "vererbt" werden. Vererben ist zwar nicht ganz das richtige Wort jedoch kann man sich darunter schon mal ungefähr etwas vorstellen. Angenommen wir haben ein Window. In diesem Window befindet sich ein DockPanel mit zwei Controls.
      Einer Statusleiste unten und einem StackPanel oben.
      Definieren wir Resourcen innerhalb des StackPanels kann das StackPanel sowie auch alle darunter liegenden Elemente auf diese Resourcen zugreifen. Sowohl die Statusbar als auch das DockPanel kann nicht darauf zugreifen da sich die Resourcen ja unterhalb dieses Elements befinden.

      Folgender XAML Code setzt diesen Test um:

      XML-Quellcode

      1. <DockPanel LastChildFill="True">
      2. <StatusBar DockPanel.Dock="Bottom">
      3. <Label Content="Status"/>
      4. </StatusBar>
      5. <StackPanel Margin="10">
      6. <StackPanel.Resources>
      7. <LinearGradientBrush x:Key="MyTestBackgroundBrush">
      8. <GradientStop Color="LightBlue"/>
      9. <GradientStop Color="Blue" Offset="1"/>
      10. </LinearGradientBrush>
      11. </StackPanel.Resources>
      12. <CheckBox Content="MeineCheckbox" Background="{StaticResource MyTestBackgroundBrush}"/>
      13. <Button Content="MainButton" Background="{StaticResource MyTestBackgroundBrush}"/>
      14. </StackPanel>
      15. </DockPanel>


      Folgendes Window wird im Designer angezeigt:


      Versuchen wir nun Beispielsweise dem Label in der Statusbar diese Resource zuzuweisen zeigt hier bereits der Designer einen Fehler dass die gesuchte Resource nicht gefunden werden kann.



      Das lässt sich allerdings trotz angezeigtem Fehler kompilieren und starten. Warum?
      Es kann ja gut sein das diese Resource zur Laufzeit zur Verfügung steht; Zum einen können Resourcen auch im Code erstellt werden zum anderen können XAML Files auch zur Laufzeit dynamisch nachgeladen werden oder es werden Assemblys geladen welche Resourcen enthalten. Wird dies allerdings nicht gemacht quittiert uns das der Debugger mit einer schönen 'XamlParseException'.


      Hier auch die Info: Angabe eines Werts für "System.Windows.StaticResourceExtension" führte zu einer Ausnahme."

      Selbst wenn wir nun auf "Continue" klicken wird das Debugging beendet. Also wo könnten wir nun die Resource definieren um diese sowohl in den Controls des Stackpanels als auch in den Controls der Statusbar zur Verfügung zu haben? Sowohl die Statusbar als auch das Stackpanel befinden sich im DockPanel. Also ist das Dockpanel der "tiefste" Punkt an welchem diese Resource definiert werden kann damit all die genannten Controls auch auf diese Resource zugreifen können.
      Ist eine Resource in den Resourcen des Window definiert gilt diese folglich für alle Elemente welches sich in diesem Window befinden. Spätestens hier fragen sich schon einige wo den der "höchste" Punkt für Resourcen wäre.

      Denn immer wieder erneut alles im Window zu definieren ist ja nicht unbedingt das Gelbe vom Ei. Der höchste Punkt an sich sind die verwalteten Resourcen des Betriebssystems. Denn in MS Windows gibt es seit Windows XP auch verschiedene Themes. Für unser Programm wäre es allerdings die Application.xaml welche sich im Hauptknoten unserer Anwendung befindet.



      Dieses File beinhaltet im Grunde nur ein leeres ResourceDictionary.
      Alle Resources, Styles und DataTemplates welche hier definiert werden sind von der ganzen Anwendung zugänglich. Wir haben ja bereits gelernt das wenn beispielsweise für einen Style kein Key sondern lediglich ein TargetType angegeben wird dieser für alle Elemente dieses Typs gelten.
      Haben wir z.b. einen Style ohne Angabe eines Keys welcher im Setter das Margin eines Buttons auf "15" festgelegt gilt dieses Margin für alle Elemente darunter. Ist dieses Style in der Application.xaml angegeben gilt dieses für alle Button des Projekts.

      Aber Moment mal!
      "Für dieses Projekt" würde doch bedeuten das, wenn ich Beispielsweise Controls oder Usercontrols in eine DLL auslagere diese das Margin nicht übernehmen richtig?
      Ja, teilweise.

      Erstelle ich in einer Assembly ein Control mit einem Button bekommt dieser Button erstmal diesen Style nicht zugeordnet. Packe ich dieses Usercontrol allerdings dann innerhalb meiner App (in welcher das Margin definiert ist) in ein Window oder wiederum in ein UserControl bekommt der Button sehr wohl das Margin da dieser sich nun innerhalb der Anwendung befindet wo im Style ein Margin für alle Buttons definiert ist.
      Folgende Vorgehensweise zeigt das Verhalten wie ich finde ganz gut.
      Wir haben ein UserControl erstellt welches wir gerne immer wieder verwenden möchten um Kundendaten anzuzeigen. Da wir es in mehreren Projekten verwenden möchte erstellen wir und ein neues Projekt vom Typ "WPF Benutzersteuerelementbibliothek" am besten innerhalb dieser Projektmappe. Hier erstellen wir uns nun ein UserControl mit dem Namen "uclCustomerData".

      Hier die Projektmappe wie diese nun aussieht:



      Und hier unser UserControl:



      Einigen wird nun sicher auffallen das hier kein Binding auf das Text-Property der TextBoxen erfolgt ist.
      Die Eingegebenen Daten können also nie irgendwohin übertragen werden. Da es hier in diesem Kapitel allerdings rein um Resourcen geht wollte ich darauf jetzt verzichten.
      Wir sehen in den Grid-Resourcen einen Style welcher auf einer Resource basiert. Und zwar erbt dieser Style von dem für eine TextBox definiertem Style. Wir nehmen also, falls eine Ebene vor unserem UserControl einen Style für eine TextBox definiert hat, diesen her und ändern Ihn ab. In unserem Fall überschreiben wir den Setter für die Eigenschaft "Margin". Hier greifen wir also bereits auf Resourcen zu. Um das UserControl nun in unserem Hauptprojekt verwenden zu können benötigen wir vom Hauptprojekt einen Verweis auf das Projekt mit unserem UserControl. Diesen fügen wir hinzu.

      Nun können wir das UserControl in unserem "MainWindow" der Hauptapplikation einfügen:



      Wir möchten allerdings, dass in unserer Anwendung alle TextBoxen mit einem grauem Hintergrund versehen werden. Also definieren wir in unseren Application.xaml-Resourcen das dies der Fall sein soll.



      Sehen wir uns nun unser MainWindow nochmals an sehen wir das diese Änderung nicht nur auf eine TextBox auswirkt welche direkt in unserer Anwendung definiert worden wäre sondern auch auf eine TextBox welche sich in einem UserControl befindet welches in einer völlig anderen Bibliothek definiert wurde.



      Was wenn wir aber ein UserControl erstellen wo wir dieses Verhalten NICHT haben möchten?
      - Dann sagen wir der WPF einfach nicht das es den Style von den TextBox-Resourcen erben soll und nehmen das BasedOn raus (im folgenden Screenshot zum besseren vergleich auskommentiert und darunter neu definiert):



      Nun erbt es den Style nicht mehr! Mir ist klar dass ich jetzt wieder ein wenig in den Styles Bereich gekommen bin, nur ist es wichtig zu verstehen das im Grunde auch ein Style eine Resource ist und wie die Vererbung funktioniert. Kommen wir nun wieder zurück zum wesentlichen.

      Unterschied zwischen StaticResource und DynamicResource

      In XAML gibt es zwei Arten eine Resource zuzuweisen. Als StaticResource oder als DynamicResource.
      Die Unterschiede zeige ich hier mal anhand einer Tabelle:

      Statische Resource
      Dynamische Resource
      Syntax:
      <object property="{StaticResource key}" .../>
      <object>
      <object.property>
      <StaticResource ResourceKey="key" .../>
      </object.property>
      </object>
      Syntax:
      <object property="{DynamicResource key}" .../>
      <object>
      <object.property>
      <DynamicResource ResourceKey="key" .../>
      </object.property>
      </object>
      Der Wert wird beim kompilieren zugewiesen und zur Laufzeit fest einkompiliert.
      Der Wert wird zur Laufzeit in dem Moment geladen wenn er benötigt wird.
      Werteänderungen zur Laufzeit werden ignoriert.
      Der aktuelle Wert wird beim laden des Objekts zugewiesen. Hat sich der Wert vorher geändert wird der geänderte Wert zugewiesen.
      Für die gesamte Lebensdauer der Applikation wird immer der selbe Wert verwendet

      Optimal wenn sich ein Wert nicht ändert oder nicht geändert werden soll.
      Optimal wenn sich der Wert über Code Behind zur Laufzeit ändern lassen soll.


      Sehen wir uns das ganze mal anhand eines simplen Beispiels an.
      Wir definieren in einer Window-Resource eine Color und einen SolidColorBrush.

      XML-Quellcode

      1. <Window.Resources>
      2. <Color x:Key="myRedBackgroundBrush">Red</Color>
      3. <SolidColorBrush x:Key="myBackgroundBrush" Color="{StaticResource myRedBackgroundBrush}"/>
      4. </Window.Resources>


      Damit man nun den großen Unterschied zwischen einer Statischen und einer Dynamischen Resource sieht erstelle ich zwei Buttons in einem StackPanel.

      XML-Quellcode

      1. <StackPanel Name="mystackPanel" Margin="10">
      2. <Button Content="Statische Resource"
      3. Background="{StaticResource myBackgroundBrush}"
      4. Margin="5"/>
      5. <Button Content="Dynamische Resource"
      6. Background="{DynamicResource myBackgroundBrush}"
      7. Margin="5"/>
      8. </StackPanel>


      Demnach sehen beide Buttons - bis auf den Content – gleich aus.



      Wo der Unterschied liegt erkennen wir wenn wir nun mit folgendem CodeBehind die Color des SolidColorBrush mit dem Namen „myBackgroundBrush“ auf LightBlue ändern.

      VB.NET-Quellcode

      1. Class MainWindow
      2. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      3. mystackPanel.Resources("myBackgroundBrush") = Brushes.LightBlue
      4. End Sub
      5. End Class


      Folgendes Ergebnis erscheint uns zur Laufzeit:



      Ich habe mich absichtlich dafür entschieden die Resource über die Resourcen des StackPanels zu ändern und zu zeigen dass man auf die Resourcen auch über ein untergeordnetes Objekt Zugriff bekommt.
      Die Resource „myBackgroudBrush“ wurde in den Window-Resourcen definiert, trotzdem greife ich über das StackPanel darauf zu und ändere diese.

      Diesmal gibt es zum Abschluss eines Kapitels mal kein Video.
      Ich denke hier konnte ich alles Nötige ohne Video vermitteln.
      Probiert es aus und spielt mit Resourcen herum.

      Erstellt auch gerne mal ein UserControl in einem anderen Projekt und seht ob es sich evtl. verändert wenn Ihr es in ein anderes Projekt mit Projektweiten Resourcen einbindet.

      Gerne könnt ihr mir trotzdem ein Like und/oder ein Kommentar auf YouTube hinterlassen oder meinen Kanal abonnieren.
      Verwendet auch gerne wieder den
      SupportThread um Fragen zu stellen.

      Bis zum mal mit mit einem sehr interessantem Thema. BINDING !!
      Das Kapitel wird etwas länger und wieder in mehreren Teilen Online gehen da dies das wohl wichtigste Thema in der WPF ist. ;)

      Liebe Grüße
      Sascha
      Dateien
      • 2.1.3.pdf

        (1,33 MB, 1.730 mal heruntergeladen, zuletzt: )
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.4.1 - Was ist Databinding - Das Konzept dahinter

      2.1.4.1

      DataBinding


      Was ist Binding - Das Konzept dahinter

      Durch das Binden wird eine Verbindung zwischen der UI und der Geschäftslogik hergestellt.
      Durch ein Benachrichtigungssystem werden Daten zwischen einer Logik (z.b. einer Klasse) und dem UI (z.b. eine TextBox) hin und her synchronisiert. Beispielsweise ist das Text-Property einer TextBox auf ein String-Property einer Klasse gebunden.
      Tippt der Benutzer einen Text in die TextBox wird der eingegebene Text automatisch in das Property der Klasse gereicht und umgekehrt. Wie und wann synchronisiert werden soll unterstützt das Bindingsystem durch mehrere Eigenschaften welche gesetzt werden können.

      Hier ein Modell für ein Binding (Quelle: Microsoft Docs):

      In der Abbildung dargestellt, die Brücke zwischen Bindungsziel und Bindungsquelle.
      Ein Bindungsziel wäre Beispielsweise eine TextBox (DependencyObject) und die Abhängigkeitseigenschaft wäre somit z.b. das Text-Property der TextBox.


      Die Richtung des Datenflusses (Quelle: Microsoft Docs):

      Wie bereits erwähnt kann die Richtung in welche die Daten synchronisiert werden sollen beeinflusst werden.
      Wie in der Abbildung durch die Pfeile Dargestellt gibt es drei Arten der Synchronisierung.
      OneWay, TwoWay und OneWayToSource. Tatsächlich ist die Abbildung allerdings ein wenig unvollständig da man in der Intellisense bei der Angabe des Modes noch zwei weitere Vorschläge angezeigt bekommt. OneTime und Default.
      • OneWay
        Ein Binding mit dem Mode OneWay für dazu das Daten von der gebundenen Klasse an das Property des Controls übergeben werden sobald dieses geändert wird, jedoch nicht zurück. Ändert der User den Wert (z.b. den Text in einer TextBox) wird die Änderung des Textes nicht an die gebundene Klasse übergeben. Der Wert des Properties innerhalb der Klasse bleibt also unverändert.
      • TwoWay
        Dies führ dazu das in beide Richtungen synchronisiert wird. Wird in der gebundenen Klasse der Wert des Properties geändert wird die Änderung an die UI, also an das Property des Controls übertragen. Ändert der Benutzer den Wert wie z.b. den Text in einer TextBox wird der neue Text in die gebundene Klasse übertragen.
      • OneWayToSource
        Kann als Umkehrung des OneWay Bindings gesehen werden. Nun wird nur noch eine Änderung wie der Text in einer TextBox an die gebundene Klasse übergeben, Änderungen innerhalb der Klasse (wenn z.b. eine Prozedur innerhalb der Klasse das Property ändert) werden allerdings nicht an das Text-Property der TextBox übertragen.
      • OneTime
        Hier wird ein OneWay Binding erstellt welches allerdings nur beim initialisieren des UI synchronisiert wird. Änderungen werden später nicht mehr aktualisiert. Im Grunde eine Momentaufnahme.
      • Default
        Das ist der per Standard eingestellte Wert. DependencyObjekte verfügen in der Regel über ein DependencyProperty auf welches gebunden wird. Diese DependencyProperties besitzen Metadaten in welchen Dinge wie der Standartwert und der Default-Binding-Mode festgelegt sind. Ich habe bisher noch keine gute Dokumentation gefunden aus welcher hervorgeht bei welchem Property eines Controls welcher Bindingmodus der Default ist. OneWay oder TwoWay weshalb ich immer empfehle den Modus mit anzugeben sollte man sich unsicher sein.
      Woher weis die WPF nun das sich ein Wert in der Klasse geändert hat oder umgekehrt?

      Hierfür gibt es im Bindingsystem die Eigenschaft UpdatesourceTrigger. Der Trigger bestimmt wann eine Änderungsbenachrichtigung vom UI zur gebundenen Klasse erfolgt. Wie dieser Trigger von der Klasse aus erfolgt erfahren wir später.


      Die UpdateSourceTrigger-Eigenschaft (Quelle: Microsoft Docs):

      Es gibt 3 Werte für die Eigenschaft UpdateSourceTrigger.
      LostFocus, PropertyChanged und Explizit.


      Für die meisten Controls und die meisten Eigenschaften dieser ist der Standardwert dieser Eigenschaft PropertyChanged, ein gutes Beispiel wo dies nicht der Fall ist, ist das Text Property der TextBox.
      Hier ist der Standardwert LostFocus. Dies hat einfach Performancegründe. Wo es beim Checked-Property einer CheckBox völlig in Ordnung ist bei jeder Änderung (aktiviert oder nicht aktiviert) eine Synchronisation anzustoßen ist dies beim Text-Property der TextBox nicht von Vorteil ja bei jedem Tastendruck immer synchronisiert werden würde was in den meisten Fällen unnötig ist.

      LostFocus bewirkt das Beispielsweise erst beim Verlassen der TextBox der Wert in die Klasse geschrieben werden würde.
      In die andere Richtung, also von der Klasse zum UI wird dies im Code gesteuert, hierfür muss das Interface INotifyPropertyChanged implementiert werden. Im Setter des jeweiligen Properties muss das Event PropertieChanged geworfen werden wobei diesem PropertyChangedEventArgs übergeben werden - welche einen String mit dem Namen des Propertys erwarten - übergeben werden. Aber hierzu kommen wir im nächsten Video noch genauer.

      Aber wie wird nun gebunden?
      Da die WPF was das Binding betrifft überaus flexibel ist kann auf verschiedene Arten gebunden werden sowie auf verschiedene Datentypen, es gibt die Möglichkeit der Datenkonvertierung und Strandardkonvertierungen als auch die Datenvalidierung. All dies werde ich in den nächsten Videos erläutern da dies wie ich finde wieder mit praxisnahen Beispielen besser vermittelt werden kann.

      Im nächsten Kapitel (2.1.4.2) probieren wir das Binding in einem VideoCast aus da ich dies besser finde als hier als Text das ganze einfach nur zu beschreiben.
      Bis zum nächsten mal.

      Liebe Grüße
      Sascha
      Dateien
      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. ##

      2.1.4.2 - Binding anhand einfacher Beispiele

      2.1.4.2

      Binding anhand einfacher Beispiele


      Hallo Leute,

      wie schon erwähnt erkläre ich euch Binding anhand eines Videos da man hier viel besser rüberbringen kann um was es geht und mit Praxisbeispielen arbeiten kann.

      Wir werden an Eigenschaften anderer Steuerelemente Binden und dann an eine eigene Klasse. Zuerst Binden wir Steuerelemente in einem Window an die eigene Code Behind bevor wir an eine selbst geschriebene Klasse binden, und auch hierfür gibt es wieder mehrere Möglichkeiten. Zum Schluss zeige ich euch auch noch wie man direkt an My.Settings binden kann.

      Viel spass mit dem Video:



      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich. 8-)
      Entweder im Supportthread oder auf meinem YouTube Kanal.

      Dateien
      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. ##

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Nofear23m“ ()

      2.1.4.3 - DesignTime-Support für Binding

      2.1.4.3

      Designtime-Support für Binding



      Binding ist in der WPF das wohl wichtigste Feature und sollte wirklich aus dem FF beherrscht werden da man sich sonst ständig wegen irgendeinem kleinen Problemchen ärgern muss.
      Beim Binding ist zudem noch darauf zu achten das hier auf die Groß-Kleinschreibung Rücksicht genommen wird, Binding ist also Case Sensitive.

      Heute sind wir es mittlerweile gewohnt von einer Entwicklungsumgebung wie VisualStudio nicht nur Feedback darüber zu bekommen das wir uns gerade vertippt haben oder wir eine Funktion falsch verwenden sondern das wir so gut wie überall auch Intellisense nutzen dürfen. Wir bekommen immer alles Mögliche vorgeschlagen und drücken anschließend nur noch Tab und ersparen uns zum einen Tipparbeit und zum anderen machen wir so viel weniger Fehler.

      Tja, aber wie sieht das nun mit dem Binding aus. Der Designer zeigt uns unser Fenster oder Usercontrol an und wir können über Binding auf ein Property binden aber wenn wir wissen möchten ob das Binding funktioniert, wir uns nicht vertippt haben oder ob wir überhaupt in der richtigen Ebene unterwegs sind müssen wir unsere App kompilieren und starten.
      Das dies sehr mühsam werden kann muss ich wohl niemanden sagen.

      Stellt euch vor ihr habt einen View in den tiefen eures Programms versteckt wie z.b. die Einstellungen, habt vielleicht noch einen Login in eurem Programm usw.
      Ihr müsst also eurer Programm jedes Mal starten, einloggen und in die Einstellungen navigieren nur damit Ihr wisst ob das was Ihr gemacht habt funktioniert. Das ist weder angenehm noch Zeitgerecht.
      Mal ganz zu schweigen das es Zeit raubt welche Ihr verwenden könnt um aktiv zu entwickeln.
      Der DesignTime-Mode macht es euch möglich hier um einiges produktiver zu sein. Nicht nur das Ihr plötzlich Intellisense zu Verfügung habt, ihr könnt auch Beispieldaten laden lassen um Anhand „echter“ Daten das verhalten eurer Steuerelemente oder des ganzen Views zu sehen und dementsprechend abzuändern.

      Nehmen wir wieder unsere DayInfo Beispielklasse aus dem letzten Kapitel her und erstellen einen View für diese wie wir ihn im letzten Kapitel auch bereits gesehen hatten.
      Nehmen wir nun Beispielsweise mal die TextBox welche wir auf den Begrüßungstext GreetingsText binden möchten:

      XML-Quellcode

      1. <TextBlock Text="{Binding GreetingText}"/>

      Auch über den sogenannten „Binding-Picker“ über das Eigenschaftenfenster des jeweiligen Controls haben wir nur die Möglichkeit manuell ein Binding zu setzen aber der Designer weis nicht welche Properties die Klasse hat.
      Wie auch, er weis nicht nicht mal von welcher Klasse wir überhaupt reden.


      Wir haben keine IntelliSense!


      Auch über BindingPicking des Eigenschaftenfensters haben wir keine Vorschläge vom Designer!


      Der Designer kann somit auf für die Listbox keinerlei Daten anzeigen!

      Um dem Abhilfe zu schaffen kann man dem Designer bekanntgeben an welchen Typ wir zur Designtime binden möchten damit dieser weis was wir möchten und welche Eigenschaften unsere Klasse im Endeffekt besitzt.
      Beim Anlegen eines Windows oder eines Usercontrols befinden sich per Default immer 5 importierte Namespaces in Form von XAML in unserem Objekt.

      XML-Quellcode

      1. <Window x:Class="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:_2_1_4_3_DesignTimeSupport"
      7. mc:Ignorable="d"
      8. Title="MainWindow" Height="450" Width="800">
      9. <Grid>
      10. </Grid>
      11. </Window>

      Hier ein normales Fenster mit einem Default-Namespace und vier benannten.

      Wie werden nun gleich den mit einem „d“ benannten Namespace sowie noch den mit „x“ benannten Namespace verwenden und müssen uns noch einen eigenen hinzuholen da wir den Namespace in welchem unsere DayInfo-Klasse liegt auch noch benötigen um Zugriff darauf zu haben.

      Innerhalb des „d“ Namespaces haben wir Zugriff auf ein Property mit dem Namen DataContext:



      Dieser Erwartet eine DesignInstance welcher wir wiederum einen Typ übergeben müssen.
      Ich versuche dies Anhand von Screenshots darzustellen, werde aber hierzu auch noch ein Video erstellen.





      Sind wir fertig mit unserer Eingabe meckert die IDE allerdings das DayInfo in einem WPF Projekt nicht unterstützt wird. Schade, WPF kann kein DayInfo. Wie jetzt?
      OK, die Fehlermeldung kann etwas verwirrend sein, es liegt einfach daran das im Default-Namespace die Klasse DayInfo nicht vorhanden ist und die WPF DayInfo somit nicht finden kann. Wir müssen also unseren Namesapce in welchem die DayInfo Klasse liegt in den View holen. Dabei unterstützt uns die IDE und wir können dies mit STRK + . tun.



      Genau das machen wir nun mit unserem UserControl welches nun wie folgt (gekürzte Ansicht) aussieht:

      XML-Quellcode

      1. <UserControl x:Class="uclTestDesigntime"
      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:_2_1_4_3_DesignTimeSupport"
      7. xmlns:testClasses="clr-namespace:_2_1_4_3_DesignTimeSupport.TestClasses"
      8. mc:Ignorable="d" d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True,Type={x:Type testClasses:DayInfo}}"
      9. d:DesignHeight="341" d:DesignWidth="394">
      10. <Grid>
      11. <Grid.Resources>
      12. <Viewbox …
      13. </Viewbox>
      14. </Grid.Resources>
      15. <ScrollViewer>
      16. <Grid>
      17. ….
      18. </Grid>
      19. </ScrollViewer>
      20. </Grid>
      21. </UserControl>


      Und siehe da, nun zeigt der Designer auch gleich viel mehr an und ich sehe wie dieser View wohl zur Laufzeit ausehen wird:



      Auch die Intellisense ist nun vorhanden:



      So ist ein Arbeiten mit Binding schon mal viel angenehmer.
      Aber von wo kommen diese Beispieldaten? Muss ich diese immer selbst implementieren und hier immer Beispieldaten angeben oder kann ich mir die Arbeit sparen auch?
      Fangen wir damit an was der Designer macht wenn wir einen Designtime DatenContext angeben.

      Dadurch das wir beim instanzieren des DesignTime-Datencontexts angegeben haben das die Instanz von DayInfo zu Designtime generiert werden kann ruft der Designer den paramaeterlosen Konstruktor dieser Klasse auf.
      Achtung: Wenn echte Beispieldaten verwendet werden sollen MUSS die Klasse einen parameterlosen Konstruktor aufweisen da dies sonst der Designer mit einer entsprechenden Fehlermeldung quittiert.

      Folgenden Code habe ich im parameterlosen Konstruktor der Klasse:

      VB.NET-Quellcode

      1. Public Sub New()
      2. If DesignerProperties.GetIsInDesignMode(New DependencyObject) Then
      3. GreetingText = "Hallo, wie geht es dir zur Designtime?"
      4. CurrentDate = DateTime.Today()
      5. MeetingsToday = New ObservableCollection(Of Meeting) From {
      6. New Meeting("Besprechung mit Franz", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 9, 30, 0), TimeSpan.FromMinutes(60)),
      7. New Meeting("Quartalsmeeting", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 13, 0, 0), TimeSpan.FromMinutes(90)),
      8. New Meeting("Abendessen mit Chef", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 16, 0, 0), TimeSpan.FromMinutes(15))
      9. }
      10. TodayWeather = New WeatherInfo() With {.CurrentTemp = 23.5, .WindDirection = WindDirection.North, .Windspeed = 15}
      11. Else
      12. GreetingText = "Hallo, wie geht es dir an diesem schönen Tag?"
      13. CurrentDate = DateTime.Today()
      14. MeetingsToday = New ObservableCollection(Of Meeting) From {
      15. New Meeting("Besprechung mit Franz", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 9, 30, 0), TimeSpan.FromMinutes(60)),
      16. New Meeting("Quartalsmeeting", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 13, 0, 0), TimeSpan.FromMinutes(90)),
      17. New Meeting("Abendessen mit Chef", New Date(CurrentDate.Year, CurrentDate.Month, CurrentDate.Day, 16, 0, 0), TimeSpan.FromMinutes(15))
      18. }
      19. TodayWeather = New WeatherInfo() With {.CurrentTemp = 23.5, .WindDirection = WindDirection.North, .Windspeed = 15}
      20. End If
      21. End Sub


      Wir unterscheiden also zwischen Designtime und Runtime. OK, aber muss ich jetzt für jede Klasse extra Code schreiben wo Beispieldaten generiert werden nur damit ich Intellisense habe?
      Nein, nur wenn ich mit Beispieldaten testen möchte muss ich dies tun, gebe ich Beispielsweise beim Instanzieren des Designtime-DataContext an das die Klasse nicht zur Designzeit erstellbar ist wird der Code aus dem Konstruktor ignoriert und es wird von der IDE selbst etwas generiert. Nämlich wird der Name der Eigenschaft als Wert geschrieben bzw. werden bei Auflistungen drei Beispieleinträge – jeweils mit dem Standartwert der Eigenschaften geschrieben.



      Das selbe passiert im übrigen auch wenn der Wert beim Designer die Option „Projektcode deaktivieren“ aktiv ist wie folgende Abbildung zeigt:


      So kann man selbst entscheiden ob man die Beispieldaten generiert oder ob man mit der automatischen Generierung zufrieden ist.

      Ich habe das UserControl nun in ein Window gepackt und starte das Programm nun.
      Doch was ist los, zur Designzeit haben wir die Daten drinnen, es sollte doch alles funktionieren.
      Warum sehen wir nun keine Daten :?:
      Das liegt daran das wir den DesignTime-Datenkontext angegeben haben und der Designer nun weis was Sache ist, aber wir haben ja bislang kein Databinding zur Laufzeit angegeben also erstelle ich folgenden Code in der CodeBehind des MainWindow:

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. Me.DataContext = New DayInfo
      3. End Sub


      Nun sieht unser UserControl zur Laufzeit genauso aus wie zur Designzeit:


      Im nächsten Beitrag lade stelle ich dann das Video Online und gehe Schritt für Schritt mit euch nochmals durch diese Vorgänge und erkläre dabei auf was es ankommt und wie Ihr euch hier viele Nerven sparen könnt.

      Fazit: In den meissten Büchern, wie auch in dem Buch welches ich hier bei mir liegen habe wird auf über 1200 Seiten reiner WPF kein Wort von einem DesignTime-Datenkontext erwähnt. Ich finde das extrem schade, gerade mit dieser Option spare ich mir nicht nur extrem viel Zeit und die Zusammenarbeit mit z.b. einem Designer wird damit viel einfacher wenn dieser Beispieldaten serviert bekommt. Instellisense rundet das ganze nochmals ab.
      Schade das hier von vielen Seiten nicht darauf eingegangen wird.


      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      Bilder
      • App1.PNG

        24,39 kB, 648×336, 409 mal angesehen
      • BindingIntellisense_9.PNG

        18,27 kB, 852×216, 410 mal angesehen
      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. ##

      2.1.4.3 - DesignTime-Support für Binding

      Nachtrag zum vorigen Beitrag:

      Wie versprochen hier das Video zum vorigen Beitrag inkl. dem PDF zum Downloaden und der Solution.
      Ich musste diesen Beitrag leider Teilen da nur 15 Anhänge erlaubt sind.



      Schöne Grüße und viel spaß mit dem Video.
      Gerne könnt ihr mir ein Like und/oder ein Kommentar hinterlassen und wieder den SupportThread verwenden um Fragen zu stellen.


      Bis zum nächsten Kapitel
      Grüße
      Sascha
      Dateien
      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. ##

      2.1.4.4 - Binding über Converter

      2.1.4.4

      Binding über Converter



      Es wird vorkommen das man Daten an die View Binden möchte welche so nicht zu Binden sind. Beispielsweise haben Sie eine Eigenschaft in Ihrer zugrundeliegenden Klasse vom Typ Boolean vorliegen, möchten allerdings das im View abhängig von deren Wert ein Element ein oder ausgeblendet wird. Das ist so ohne weiteres nicht möglich. In der WPF ist das zuständige Property welches diesen Zustand ändert das Property Visible welches vom Typ Visibility ist.
      Der Typ Visibilty ist eine Enumeration mit den Werten: Collapsed, Hidden und Visible.

      Visible blendet das Element ein. - Zwischen Collapsed und Hidden gibt es allerdings einen Unterschied welchen es unter WinForms nicht gab.
      Collapsed zeigt das Element nicht an und reserviert auch keinen Platz.
      Hidden zeigt das Element nicht an, reserviert allerdings den Platz.

      Wir können also nicht einfach so darauf Binden. Jetzt kommen viele auf die Idee das ganze mittels Trigger (DataTrigger) zu lösen und einfach wenn per Binding ein True herauskommt das Control.Visible auf Visible zu setzen und bei False auf Collapsed. Auch eine möglichkeit ist das ich ein Property in der zugrunde liegenden Klasse schaffe welches mit diese Umwandlung macht.

      Von beidem ist abzuraten. Warum?
      In beiden Fällen wiederholt man seinen Code immer und immer wieder wenn ein solches Problem auftaucht. Egal ob in XAML oder in der Klasse als VB oder C# Code.
      Möchte ich meine Logik mal ändern muss ich das an jeder Stelle tun und darf keine vergessen.
      Weiters werden wir später, wenn wir uns mit MVVM beschäftigen sehen das ein Property mit dem Datentyp Visibility nichts im Code zu suchen hat. Um sollche Dinge soll sich alleine die View kümmern.

      Gut, aber wie machen wir das jetzt?
      Wir schreiben einen Converter. Converter sind in der Regel sehr einfach gestrickte kleine Klassen welche so wenig Logik wie möglich beinhalten sollten.
      Eine solche Konverterklasse muss das Interface IValueConverter implementieren!
      Wir erstellen uns eine Klasse mit dem Namen BooleanToVisibilityConverter (per Standardkonvention sollte der Name einer Konverterklasse immer beschreiben von welchem Typ in welchem diese umwandelt, gefolgt von „Converter“) und Implementieren die Schnittstelle IValueConverter. Ich empfehle wie immer einen eigenen Namespace zu erstellen. Nun sieht unsere Klasse wie folgt aus.

      VB.NET-Quellcode

      1. Imports System.Globalization
      2. Namespace Converter
      3. Public Class BooleanToVisibilityConverter
      4. Implements IValueConverter
      5. Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
      6. Throw New NotImplementedException()
      7. End Function
      8. Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      9. Throw New NotImplementedException()
      10. End Function
      11. End Class
      12. End Namespace


      Die Funktion Convert wird von der WPF aufgerufen wenn von der Klasse in Richtung View der Wert konvertiert werden soll.
      Wird der Konverter im XAML eingebunden ruft die WPF diese Funktion auf um zu versuchen den Wert zu Konvertieren. Im Parameter value ist also der Wert enthalten welcher von der Klasse kommt, also der Wert des Property‘s auf welches gebunden wurde. In unserem Fall ist dies ein Wert vom Typ Boolean. Wichtig ist hier immer ob der Wert welcher hier hereingereicht wird nicht vielleicht Nothing ist.
      Anschließend müssen wir den Wert von einem Boolean in einen Wert vom Typ Visibility umwandeln.

      VB.NET-Quellcode

      1. Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
      2. If value Is Nothing Then Return Visibility.Hidden
      3. If CBool(value) Then
      4. Return Visibility.Visible
      5. Else
      6. Return Visibility.Hidden
      7. End If
      8. End Function

      • Ist der Wert in value Nothing geben wir den Wert Hidden des Typs Visibility zurück.
      • Ist der Wert in value True geben wir den Wert Visible des Typs Visibility zurück.
      • Ist der Wert in value False geben wir den Wert Hidden des Typs Visibility zurück.

      Soweit, war das gar nicht schwer. Was ist aber mit den anderen Parametern der Funktion?
      TargetType gibt uns den Typ zurück welchen die WPF von uns erwartet. In unserem Fall wird dies ein Visibility sein.
      Parameter kann in XAML definiert werden und wird uns hier hereingereicht. Parameter ist kein Dependency Property und kann somit nicht gebunden werden, dazu kommen wir aber noch.
      CultureInfo enthält die Informationen über die aktuellen Sprach und lokalisierungeinstellungen welche im Projekt oder im View festgelegt wurden.

      Die Funktion Convertback ist für die entgegengesetzte Richtung zuständig. In unserem Fall würden wir diese Methode nicht benötigen weil es unwarscheinlich sein wird das wir für Visibility ein TwoWay Binding haben werden, möglich ist allerdings auch das.
      Wir werden die Methode also für diese Beispiel auch ausprogrammieren.Wird im View der Wert gesetzt. Beispielsweise über ein Dropdown welches an den Enumerator gebunden ist und wir hier umstellen, will die WPF (wenn in TwoWay gebunden wurde) den Wert wieder vom View zurück in die Klasse also in das Property der Klasse schreiben.
      Die WPF bemüht also abermals den Converter weil Sie weis – was in die eine Richtung erledigt werden muss gilt auch für die andere. Hier bekommen wir in value nun aber einen Wert vom Typ Visibility hereingereicht und müssen diesen nun zurück in einen Wert vom Typ Boolean konvertieren.
      Wie wir das genau machen obligt uns, wir werden uns für dieses Beispiel nun dafür entscheiden was wir Hidden und Collapsed zu einem False wandeln und Visible zu einem True.

      VB.NET-Quellcode

      1. Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      2. If value Is Nothing Then Return False
      3. Select Case DirectCast(value, Visibility)
      4. Case Visibility.Collapsed, Visibility.Hidden
      5. Return False
      6. Case Else
      7. Return True
      8. End Select
      9. End Function


      Wie binden wir nun einen solchen Konverter im XAML ein.
      Wir wechseln zu unserem XAML Code und Binden den Namespace der Converter in unserem XAML ein und vergeben einen Key.

      z.b.:

      XML-Quellcode

      1. <UserControl.Resources>
      2. <Converter:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
      3. </UserControl.Resources>


      Nun können wir beim Binden diesen als Statische Resource eingebundenen Konverter verwenden:

      XML-Quellcode

      1. <Label Content="Test" Visibility="{Binding MyBoolPropertyInClass, Converter={StaticResource BooleanToVisibilityConverter}}"/>


      Oben hatte ich bereits die Converterparameter angesprochen, über setzen des Paramters können wir zusätzliche Informationen an den Converter übergeben.
      Welche Informationen bleibt im Grunde uns überlassen, nur ist zu beachten das hier kein Binding möglich ist da Converterparameter kein DependecyProperty ist.

      XML-Quellcode

      1. <Label Content="Test" Visibility="{Binding MyBoolPropertyInClass, Converter={StaticResource BooleanToVisibilityConverter},ConverterParameter=reverse}"/>


      Beispielsweise kann es sein das wir diesen Konverter auch umdrehen möchten.
      Möchten wir also auch den Fall das wir umgekehrt ragiert möchten (True = Hidden und False = Visible) abdecken, könnten wir hier als Parameter z.b. „reverse“ mit übergeben und der Konverter soll uns das Verhalten umkehren. So müssen wir keinen zweiten Konverter schreiben, sondern können uns einfach den einen Konverter erweitern.

      VB.NET-Quellcode

      1. Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
      2. If value Is Nothing Then Return Visibility.Hidden
      3. If CBool(value) Then
      4. If parameter?.ToString().ToLower() = "reverse" Then Return Visibility.Hidden
      5. Return Visibility.Visible
      6. Else
      7. If parameter?.ToString().ToLower() = "reverse" Then Return Visibility.Visible
      8. Return Visibility.Hidden
      9. End If
      10. End Function
      11. Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
      12. If value Is Nothing Then Return False
      13. Select Case DirectCast(value, Visibility)
      14. Case Visibility.Collapsed, Visibility.Hidden
      15. If parameter?.ToString().ToLower() = "reverse" Then Return True
      16. Return False
      17. Case Else
      18. If parameter?.ToString().ToLower() = "reverse" Then Return False
      19. Return True
      20. End Select
      21. End Function


      Mehr zu Konvertern und mehrere Praxisbeispiele gibt es wieder in meinem Video und für alle die das Video aufmerksam und zu Ende sehen gibt es diesmal noch eine kleinen aber sehr nüztlichen Extratip!!





      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

      Grüße
      Sascha

      Dateien
      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. ##

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Nofear23m“ () aus folgendem Grund: Anhänge (Projekt und PDF) hinzugefügt

      2.1.4.5 - Binding über DataTemplates

      2.1.4.5

      Binding über DataTemplates



      Wie der Titel dieses Kapitels vermuten lässt geht es um DataTemplates. Wieder!
      Ja, dem aufmerksamen leser wird auffallen das es bereits in Kapitel 2.1.1.2 - Styles,Templates und Trigger um Templates ging und das wir hier bereits einiges gelernt haben was diese Vorlagen (Templates auf Deutsch = Vorlagen) betrifft.
      Da ich allerdings der Meinung bin das DataTemplates eine besondere Aufmerksamkeit geschänkt werden sollte greife ich dieses Thema nun nochmals auf um das Wissen nochmals zu festigen und hier auch ein weiteres Beispiel für den Einsatz von DataTemplates zeigen zu können. Gerade wenn es dann später mal um MVVM oder andere Pattern geht sind DataTemplates ein wichtiges Thema und können einem auch viel Arbeit ersparen.
      Die WPF ist hier so leistungsstark das plötzlich Dinge möglich werden welche vorher kaum vorstellbar gewesen wären.

      Aber fangen wir am besten an.
      Da ich davon ausgehen darf das die Funktion von DataTemplates bereits bekannt ist gehe ich gleich über zu einem Praxisbeispiel.
      Wir stellen uns eine Anwendung vor in welcher wir Fahrzeuge verwalten möchten. Erstmal überlegen wir und was für Eigenschaften Fahrzeuge haben können um zu wissen wie wir welche Daten in unserer Anwendung anzeigen möchten.
      Was gibt es denn für Fahrzeugtypen? Es gibt Autos, Flugzeuge, Hubschrauber, Schiffe usw.! Die Frage ist nun wie wir diese unter „einen Hut bekommen“ wollen. Klar, eine Basisklasse muss her. Welche Eigenschaften haben all diese Fahrzeugtypen gemein?

      Einige wären Marke, Modell, Gewicht und vielleicht die Höchstgeschwindigkeit. Bestimmt hätten alle Fahrzeugtypen mehr gemeinsam, ich erstelle für dieses Beispiel mal diese 4 Eigenschaften in einer Basisklasse TransportBase.

      VB.NET-Quellcode

      1. Namespace Classes
      2. Public MustInherit Class TransportBase
      3. Public Property Brand As String
      4. Public Property Model As String
      5. Public Property Weight As Double
      6. Public Property TopSpeed As Double
      7. End Class
      8. End Namespace


      Jetzt haben wir eine Basisklasse welche als MustInherit (Abstract in C#) deklariert ist was bedeutet das diese Klasse lediglich als Basisklasse verwendet werden darf und nicht direkt erstellt werden kann.
      Als nächstes geht es darum mit einem Fahrzeugtyp anzufangen. Wir nehmen uns mal das Auto her und erstellen eine Klasse Car welche von TransportBase erbt.

      VB.NET-Quellcode

      1. Namespace Classes
      2. Public Class Car
      3. Inherits TransportBase
      4. End Class
      5. End Namespace


      Durch die ableitung von TransportBase besitzt die Klasse Car nun automatisch die Eigenschaften von TransportBase ohne das wir etwas tun müssen. Hauchen wir unserer Klasse etwas mehr Leben ein und verpassen wir der Klasse Car noch zwei Eigenschaften. Die Anzahl der Zylinder des Motors eines Autos und die Sonderausstattung vielleicht noch. Damit wir die Sonderausstattung nicht einfach nur als String haben erstellen wir uns noch eine Klasse CarEquipment und haben nun folgendes:

      VB.NET-Quellcode

      1. Namespace Classes
      2. Public Class Car
      3. Inherits TransportBase
      4. Public Property Cylinders As Integer
      5. Public Property Equipment As List(Of CarEquipment)
      6. End Class
      7. Public Class CarEquipment
      8. Public Property Caption As String
      9. Public Property Description As String
      10. End Class
      11. End Namespace


      Nun besitzt ein Auto noch die Eigenschaft Cylinders und Equipment wobei Equipment viele Ausstattungen mit Bezeichnung und einer Beschreibung dazu beinhalten kann.
      Das selbe können wir nun auch mit anderen Fahrzeugtypen machen wie einem Flugzeug. Nur das bei einem Flugzeug andere Daten wichtig sind. Anders als bei einem Fahrzeug interessiert uns vielleicht die Ausstattung nicht sondern wieviele Passagiere befördert werden können oder die maximale Reiseflughöhe. Und statt der Anzahl der Cylinder ist die Anzahl der Turbinen vielleicht interessant. Bei einem Schiff wäre es vermutlich der maximale Tiefgang der uns interessiert.

      Aber jetzt wird es interessant. Wir möchten das alle Fahrzeugtypen mit Ihren Eigenschaften sowohl in einer Listbox dargestellt werden als auch wenn ein Eintrag selektiert wird, dieser unterhalb der Listbox in groß angezeigt wird. Und zwar mit all den Eigenschaften welche der jeweilige Fahrzeugtyp besitzt. Aber wie? Einige kommen dann auf die Idee (wir erinnern uns an Trigger) alle Eigenschaften welche es geben kann zu erstellen. Also für jede Eigenschaft welche ein Fahrzeug besitzen kann einen TextBlock welcher diese Eigenschaft anzeigt und dann per DataTrigger den TextBlock ein oder ausgeblendet wird.
      Extrem umständlich, sehr schlecht wartbar und unübersichtlich hoch 5. Da wir in der WPF DataTemplates zur Verfügung haben ist dies viel einfacher.

      Erstmal erstelle ich ein UserControl mit einer ListBox und binde dieses auf eine Klasse in welcher ich eine reihe von Fahrzeugen lade. Die Klasse besitzt zwei Eigenschaften AllTransport von Typ ObservableCollection(Of TransportBase) und SelectedTransport vom Typ TransportBase.

      XML-Quellcode

      1. <Grid>
      2. <Grid.RowDefinitions>
      3. <RowDefinition Height="1*"/>
      4. <RowDefinition Height="1*"/>
      5. </Grid.RowDefinitions>
      6. <ListBox ItemsSource="{Binding AllTransports}"
      7. HorizontalContentAlignment="Stretch"
      8. SelectedItem="{Binding SelectedTransport}"/>
      9. <Border Grid.Row="1" Background="#C6559655">
      10. <ContentControl Content="{Binding SelectedTransport}">
      11. </ContentControl>
      12. </Border>
      13. </Grid>


      Unter der ListBox haben wir noch ein ContentControl welches auf SelectedTransport gebunden ist um das aktuell selektierte Fahrzeug in groß darstellen zu können.
      Das sieht im Moment noch eher unspektakulär aus:



      Die WPF versucht den Typ korrekt zu Rendern und ruft die Methode ToString() des jeweiligen Objekts auf was dazu führt das der Klassentyp den Objekts angezeigt wird.
      Ich weis was Ihr nun denkt. Jetzt muss ich mir für jeden Typ ein DataTemplate erstellen damit es korrekt angezeigt wird. Aber was ist wenn ich das gar nicht möchte?

      Angenommen ich möchte in der ListBox nur Daten anzeigen welche in der Basisklasse vorhanden sind wie Marke und Modell, hier müsste ich gar nicht 4 verschiedene Templates erstellen. Wenn die WPF für den jeweiligen Typ ein Template sucht, aber nicht fündig wird, sucht sie sogar für den Basistyp weiter.

      Beispiel: Für den Eintrag vom Typ Airplane (an Platz drei in der Liste) sucht die WPF nach einem DataTemplate für den Typ Airplane, wird sie nicht fündig sucht sie nach einem DataTemplate vom Typ TransportBase, würde sie hier auch nicht fündig werden sucht sie nach einem DataTemplate vom Typ Objekt und hier wird sie fündig da dieses von MS implementiert ist und wir in dem oben stehenden Screenshot sehen.
      Möchten wir also nicht für jeden Fahrzeugtypen ein Template erstellen können wir auch ein Allgemeines erstellen. Damit wir dieses allerdings nicht nur innerhalb der ListBox zur Verfügung haben machen wir das in den Resourcen des UserControls, so profitiert auch das ContentControl davon:

      XML-Quellcode

      1. <UserControl x:Class="uclShowTransports"
      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:_2_1_4_5_DataTemplates"
      7. xmlns:classes="clr-namespace:_2_1_4_5_DataTemplates.Classes"
      8. mc:Ignorable="d d:DesignHeight="273.623" d:DesignWidth="424.364">
      9. <UserControl.Resources>
      10. <DataTemplate DataType="{x:Type classes:TransportBase}">
      11. <Border BorderBrush="Black" BorderThickness="1">
      12. <TextBlock FontWeight="Bold"
      13. FontSize="18"
      14. Foreground="#375D7E">
      15. <Run Text="{Binding Brand}"/>
      16. <Run Text="{Binding Model}"/>
      17. </TextBlock>
      18. </Border>
      19. </DataTemplate>
      20. </UserControl.Resources>
      21. <Grid>
      22. <Grid.RowDefinitions>
      23. <RowDefinition Height="1*"/>
      24. <RowDefinition Height="1*"/>
      25. </Grid.RowDefinitions>
      26. <ListBox ItemsSource="{Binding AllTransports}"
      27. HorizontalContentAlignment="Stretch"
      28. SelectedItem="{Binding SelectedTransport}"/>
      29. <Border Grid.Row="1" Background="#C6559655">
      30. <ContentControl Content="{Binding SelectedTransport}">
      31. </ContentControl>
      32. </Border>
      33. </Grid>
      34. </UserControl>




      Das Ergebniss dieser kleinen Umbauarbeit innerhalb der UserControl-Resourcen bewirkt bereits einiges! Nun haben wir Marke und Model sowohl in der ListBox als auch darunter inkl. einer Umrandung in der Detailansicht stehen. Das ist aber noch nicht zufriedenstellend. Nun möchten wir das ein Fahrzeug in der Detailansicht anders dargestellt wird als in der ListBox. Hierfür müssen wir nur Beispielsweise in den ListBox-Resourcen ein DataTemplate definieren. Dies führt dazu das dieses DataTemplate das Template in den UserControl-Resourcen überschreibt da dieses weiter unten im Element-Tree definiert ist.

      Wir gehen aber noch einen Schritt weiter, wir möchten auch gesondert darauf eingehen was für ein Fahrzeugtyp gerade angezeigt wird, also in der ListBox selektiert wurde. Hierfür müssen wir aber nun für jeden Fahrzeugtyp ein Template definieren.

      XML-Quellcode

      1. <ContentControl Content="{Binding SelectedTransport}">
      2. <ContentControl.Resources>
      3. <DataTemplate DataType="{x:Type classes:Car}">
      4. <local:uclCar/>
      5. </DataTemplate>
      6. <DataTemplate DataType="{x:Type classes:Helicopter}">
      7. <local:uclHeli/>
      8. </DataTemplate>
      9. <DataTemplate DataType="{x:Type classes:Airplane}">
      10. <local:uclAirplane/>
      11. </DataTemplate>
      12. <DataTemplate DataType="{x:Type classes:Ship}">
      13. <local:uclShip/>
      14. </DataTemplate>
      15. </ContentControl.Resources>
      16. </ContentControl>


      Das Ergebniss kann sich nun bereits sehen lassen, wir haben eine Detailansicht wie folgendes Bild zeigt inkl. schönen Symbolen und einem Fahrzeugbild im Hintergrund und einem Expander welcher mit den Austattungen eines Fahrzeugs befüllt ist. Auch für die anderen Fahrzeugtypen habe ich Vorlagen erstellt. Diese sind alle in einzelne UserControls unterteilt damit hier der XAML nicht zu lange wird.



      Einen Schritt weiter machen wir noch, wir entscheiden uns dafür das wir auch für die ListBox-Einträge für jeden Datentyp eine eigene Vorlage haben. Ich habe hierfür fast den selben Code wie für die Detailansicht verwendet nur das die Spezifikationen Horizontal dargestellt werden. Unser „allgemeines“ Template kann trotzdem im XAML-Code bleiben wo es ist. So ist sichergestellt das wenn ein neuer Fahrzeugtyp hinzukommen würde dieser trotzdem angezeigt werden könnte, zwar nur mit Marke und Modell aber es würde etwas angezeigt werden. Das kann man also wie eine Art „Versicherung“ sehen.



      Ihr merkt schon, ohne das Ihr jetzt komplizierten Code schreiben oder diverse Umwege gehen müsst habt ihr übersichtlich die Anzeige von Fahrzeugen in XAML definiert. Hier der komplette XAML des UserControls:

      XML-Quellcode

      1. <UserControl x:Class="uclShowTransports"
      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:_2_1_4_5_DataTemplates"
      7. xmlns:classes="clr-namespace:_2_1_4_5_DataTemplates.Classes"
      8. mc:Ignorable="d" d:DataContext="{d:DesignInstance IsDesignTimeCreatable=True,Type=local:AllTransports}"
      9. d:DesignHeight="273.623" d:DesignWidth="424.364">
      10. <UserControl.Resources>
      11. <DataTemplate DataType="{x:Type classes:TransportBase}">
      12. <Border BorderBrush="Black" BorderThickness="1">
      13. <TextBlock FontWeight="Bold" FontSize="18" Foreground="#375D7E">
      14. <Run Text="{Binding Brand}"/>
      15. <Run Text="{Binding Model}"/>
      16. </TextBlock>
      17. </Border>
      18. </DataTemplate>
      19. <DataTemplate DataType="{x:Type classes:Car}">
      20. <local:uclCarListItem/>
      21. </DataTemplate>
      22. <DataTemplate DataType="{x:Type classes:Helicopter}">
      23. <local:uclHeliListItem/>
      24. </DataTemplate>
      25. <DataTemplate DataType="{x:Type classes:Airplane}">
      26. <local:uclAirplaneListItem/>
      27. </DataTemplate>
      28. <DataTemplate DataType="{x:Type classes:Ship}">
      29. <local:uclShipListItem/>
      30. </DataTemplate>
      31. </UserControl.Resources>
      32. <Grid>
      33. <Grid.RowDefinitions>
      34. <RowDefinition Height="1*"/>
      35. <RowDefinition Height="1*"/>
      36. </Grid.RowDefinitions>
      37. <ListBox ItemsSource="{Binding AllTransports}"
      38. HorizontalContentAlignment="Stretch"
      39. SelectedItem="{Binding SelectedTransport}"/>
      40. <Border Grid.Row="1" Background="#C6559655">
      41. <ContentControl Content="{Binding SelectedTransport}">
      42. <ContentControl.Resources>
      43. <DataTemplate DataType="{x:Type classes:Car}">
      44. <local:uclCar/>
      45. </DataTemplate>
      46. <DataTemplate DataType="{x:Type classes:Helicopter}">
      47. <local:uclHeli/>
      48. </DataTemplate>
      49. <DataTemplate DataType="{x:Type classes:Airplane}">
      50. <local:uclAirplane/>
      51. </DataTemplate>
      52. <DataTemplate DataType="{x:Type classes:Ship}">
      53. <local:uclShip/>
      54. </DataTemplate>
      55. </ContentControl.Resources>
      56. </ContentControl>
      57. </Border>
      58. </Grid>
      59. </UserControl>


      Natürlich erkläre ich dies alles auch wieder interaktiv in einem Video wo ihr besser sehen könnt wie einfach und übersichtlich man mit DataTemplates arbeiten kann. Spätestens jetzt sollte man die stärken der WPF erkannt haben würde ich meinen.
      Die Solution und alle Kapitel und Downloads sind im Tutorialthread enthalten, viel spaß mit dem Video.



      Hier die Solution: 2_1_4_5_DataTemplates.zip
      Und wie immer auch das PDF für euer E-Book: Tutorialreihe WPF lernen.pdf


      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

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

      Binding an Collections mit CollectionViewSource Teil 1 von 2

      2.1.4.6

      Binding an Collections mit CollectionViewSource



      Wie Ihr in den vorigen Kapitel bereits gesehen habt kann man direkt an eine Collection binden, dabei ist es im Grunde völlig gleich ob diese eine List(Of T) , eine ObservableCollection(Of T) oder gar nur ein Array ist.
      Was hier aber irgendwie fehlt sind Funktionen wie Filtern, Sortieren oder auch Gruppieren. Aber auch die Informationen welches Item einer Auflistung nun gerade Beispielsweise in einer ListBox selektiert ist fehlt uns.

      Letzteres Feature kann man noch herstellen indem man eine Eigenschaft in der Klasse definiert und die SelectedItem Eigenschaft der ListBox auf diese bindet. Es geht aber einfacher.

      Die CollectionView in der WPF ist eine Instanz einer Klasse welche das Interface ICollectionView implementiert. Im folgenden sehen wir uns mal diese ICollectionView an um zu verstehen was diese so besonders macht.

      Wie unter Microsoft Docs schön zu sehen ist implementiert ICollectionView wiederum zwei Schnittstellen IEnumerable und INotifyCollectionChanged durch die bereits schon einiges an Funktionalität geboten wird. Zusätzlich verfügt die Schnittstelle noch über einige Eigenschaften und Methoden welche wir uns zu nutze machen. Hier die wichtigsten welche uns im laufe dieses Kapitels interessieren werden.

      Eigenschaften

      CurrentItem, GroupDescriptions, Filter, Groups, SortDescriptions, SourceCollection

      Methoden

      MoveCurrent….() und Refresh()

      Wir sehen, hier wird einiges geboten. OK, aber wie erhalten wir nun eine CollectionView?
      Wir rufen die statische Methode GetDefaultView der Klasse CollectionViewSource auf. Diese gibt uns einen DefaultView zurück. Existiert noch kein DefaultView wird mit GetDefaultView eine erzeugt. Welcher Typ eines ICollectionView von GetDefaultView genau zurückgegeben wird hängt davon ab welcher CollectionTyp der Methode übergeben wird. Für eine IList Collection ist es z.b. eine ListCollectionView.

      Aber ich komme wieder zu sehr in die Theorie, wir möchten dies anhand von Beispielen lernen, das macht mehr spass und prägt sich meiner Meinung nach besser ein.
      Es gibt zwei Möglichkeiten eine CollectionView zu erstellen. Entweder definieren wir diese in XAML oder per Code. Definieren wir die CollectionViewSource in XAML können wir hier direkt angeben wie wir Beispielsweise sortieren oder Gruppieren möchten, beide Methoden haben ihre Vorteile, beide aber auch ihre Nachteile.
      Machen wir dies mal anhand eines Beispiels und fangen damit an das wir die CollectionViewSource innerhalb unseres XAML Codes definieren:

      Wir erstellen ein neues WPF Projekt und erstellen uns eine neue Klasse „Person“ welche wie in der WPF üblich INotifyPropertyChanged implementiert.

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. Imports System.Runtime.CompilerServices
      3. Public Class Person
      4. Implements INotifyPropertyChanged
      5. Public Sub New()
      6. End Sub
      7. Public Sub New(firstname As String, lastname As String, Optional gender As PersonGender = PersonGender.Male)
      8. Me.FirstName = firstname : Me.LastName = lastname : Me.Gender = gender
      9. End Sub
      10. Private _firstName As String
      11. Public Property FirstName() As String
      12. Get
      13. Return _firstName
      14. End Get
      15. Set(ByVal value As String)
      16. _firstName = value
      17. RaisePropertyChanged()
      18. RaisePropertyChanged(NameOf(FullName))
      19. End Set
      20. End Property
      21. Private _lastName As String
      22. Public Property LastName() As String
      23. Get
      24. Return _lastName
      25. End Get
      26. Set(ByVal value As String)
      27. _lastName = value
      28. RaisePropertyChanged()
      29. RaisePropertyChanged(NameOf(FullName))
      30. End Set
      31. End Property
      32. Public ReadOnly Property FullName As String
      33. Get
      34. Return $"{FirstName} {LastName}"
      35. End Get
      36. End Property
      37. Private _gender As PersonGender = PersonGender.Male
      38. Public Property Gender() As PersonGender
      39. Get
      40. Return _gender
      41. End Get
      42. Set(ByVal value As PersonGender)
      43. _gender = value
      44. RaisePropertyChanged()
      45. End Set
      46. End Property
      47. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
      48. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      49. End Sub
      50. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      51. End Class


      Innerhalb der selben Datei können wir nun den Enumerator auch erstellen:

      VB.NET-Quellcode

      1. Public Enum PersonGender
      2. Male = 0
      3. Female = 1
      4. End Enum


      Wir öffnen nun die MainWindow.vb und implementieren hier ebenfalls die INotifyPropertyChanged Schnittstelle sowie eine ObservableCollection(Of Person) um eine Liste von Personen halten zu können:

      VB.NET-Quellcode

      1. Imports System.Collections.ObjectModel
      2. Imports System.ComponentModel
      3. Imports System.Runtime.CompilerServices
      4. Class MainWindow
      5. Implements INotifyPropertyChanged
      6. Private _allPersons As ObservableCollection(Of Person)
      7. Public Property AllPersons() As ObservableCollection(Of Person)
      8. Get
      9. Return _allPersons
      10. End Get
      11. Set(ByVal value As ObservableCollection(Of Person))
      12. _allPersons = value
      13. RaisePropertyChanged()
      14. End Set
      15. End Property
      16. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
      17. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      18. End Sub
      19. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      20. End Class


      Jetzt müssen wir noch einige Personen zum Testen einfügen. Hierfür bietet sich das Loaded-Ereigniss des Window an.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AllPersons = New ObservableCollection(Of Person)
      3. AllPersons.Add(New Person("Max", "Mustermann"))
      4. AllPersons.Add(New Person("Susi", "Sorglos", PersonGender.Female))
      5. AllPersons.Add(New Person("Maria", "Musterfrau", PersonGender.Female))
      6. AllPersons.Add(New Person("Peter", "Hilflos"))
      7. Me.DataContext = Me
      8. End Sub


      Nun können wir bereits mit unserem View beginnen und probehalber einfach mal ein DataGrid auf die AllPersons-Eigenschaft binden um zu sehen ob wir hier bereits Daten sehen.

      XML-Quellcode

      1. <Grid>
      2. <DataGrid ItemsSource="{Binding AllPersons}" CanUserAddRows="False">
      3. </DataGrid>
      4. </Grid>


      In der Tat sehen wir unsere Namen welche wir im Loaded Ereigniss der Auflistung hinzufügen.Warum also eine CollectionView erstellen? Oft will man die Einträge der Auflistung statisch oder dynamisch Sortieren, Gruppieren oder Filtern. Außerdem bietet die CollectionView Eigenschaften wie CurrentItem oder CurrentIndex welche es uns ermöglichen im Code oder per Binding abzufragen welche Person ausgewählt wurde. Genau genommen wird im Hintergrund durch die WPF automatisch eine CollectionView erstellt welche wir nun nicht zur Gesicht bekommen.



      Wir definieren in den Resourcen des Fensters eine CollectionViewSource mit einer SortDescription um eine Sortierung der Einträge vorzugeben. Um SortDescription zur Verfügung zu bekommen müssen wir allerdings den ComponentModel-Namespace importieren:

      XML-Quellcode

      1. xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
      2. <Window.Resources>
      3. <CollectionViewSource Source="{Binding AllPersons}" x:Key="personsView">
      4. <CollectionViewSource.SortDescriptions>
      5. <componentModel:SortDescription PropertyName="LastName"/>
      6. </CollectionViewSource.SortDescriptions>
      7. </CollectionViewSource>
      8. </Window.Resources>


      Allerdings ändert sich nun noch nichts.
      Wir haben nun eine CollectionViewSource erstellt diese allerdings noch nicht an das DataGrid gebunden. Aber wie binden wir nun diese – als Resource definierte CollectionViewSource an ein DataGrid?
      Indem wir beim Binding an die statische Resource verweisen:

      XML-Quellcode

      1. <Grid>
      2. <DataGrid ItemsSource="{Binding Source={StaticResource personsView}}" CanUserAddRows="False">
      3. </DataGrid>
      4. </Grid>


      OK, die CollectionViewSource hat nun als Quelle unsere ObservableCollection der CodeBehind das MainWindow und das DataGrid hat als Quelle für die Items diese CollectionViewSource.In der CollectionViewSource haben wir eine SortDescription für die Eigenschaft „LastName“ angegeben damit diese dafür sorgt das alle Einträge nach dem Nachnamen der Person sortiert werden. Probieren wir dies aus indem wir unsere App starten:



      Wir sehen bereits den Unterschied, die Einträge sind nun tatsächlich sortiert.
      Gehen wir weiter und versuchen nun die Einträge nach dem Geschlecht zu Gruppieren.
      Hierfür müssen wir nur eine „GroupDescription“ zu unserer CollectionViewSource hinzufügen und die Eigenschaft nach welcher Gruppiert werden soll angeben. In unserem Fall die Eigenschaft „Gender“ unserer Person Klasse.

      XML-Quellcode

      1. <CollectionViewSource Source="{Binding AllPersons}" x:Key="personsView">
      2. <CollectionViewSource.GroupDescriptions>
      3. <PropertyGroupDescription PropertyName="Gender" />
      4. </CollectionViewSource.GroupDescriptions>
      5. <CollectionViewSource.SortDescriptions>
      6. <componentModel:SortDescription PropertyName="LastName"/>
      7. </CollectionViewSource.SortDescriptions>
      8. </CollectionViewSource>


      Starten wir nun unsere App nochmals wird uns auffallen das nun zwar Gruppiert wurde, aber irgendwie passt die sortierung nicht mehr. Stimmt nicht ganz, es wurde schon nach den Nachnamen sortiert allerdings innerhalb der Gruppierung.



      Die Gruppierung sieht jetzt nicht unbedingt sehr toll aus und zeigt nicht wirklich gut an das nun innerhalb dieser Liste Gruppiert wurde. Das liegt daran das MS keinen GroupStyle definiert hat, also machen wir dies schnell mal damit wir die Gruppierung besser erkennen können:

      XML-Quellcode

      1. <Grid>
      2. <DataGrid ItemsSource="{Binding Source={StaticResource personsView}}"
      3. CanUserAddRows="False">
      4. <DataGrid.GroupStyle>
      5. <GroupStyle>
      6. <GroupStyle.ContainerStyle>
      7. <Style TargetType="{x:Type GroupItem}">
      8. <Setter Property="Margin" Value="0,0,0,5"/>
      9. <Setter Property="Template">
      10. <Setter.Value>
      11. <ControlTemplate TargetType="{x:Type GroupItem}">
      12. <Expander IsExpanded="True"
      13. BorderThickness="1,1,1,5">
      14. <Expander.Header>
      15. <DockPanel>
      16. <TextBlock FontWeight="Bold"
      17. Text="{Binding Path=Name}"
      18. Margin="5,0,0,0"
      19. Width="100"/>
      20. <TextBlock FontWeight="Bold"
      21. Text="{Binding Path=ItemCount}"/>
      22. </DockPanel>
      23. </Expander.Header>
      24. <Expander.Content>
      25. <ItemsPresenter />
      26. </Expander.Content>
      27. </Expander>
      28. </ControlTemplate>
      29. </Setter.Value>
      30. </Setter>
      31. </Style>
      32. </GroupStyle.ContainerStyle>
      33. </GroupStyle>
      34. </DataGrid.GroupStyle>
      35. </DataGrid>
      36. </Grid>


      Mit diesem Style sehen wir jetzt schön wie Gruppiert wird, wobei innerhalb der Gruppierung auch die Sortierung nach dem Nachnamen korrekt ausgeführt wird.



      Wie weiter oben in diesem Kapitel angesprochen hat diese Art der erstellung (innerhalb des XAML) auch Nachteile. Ich muss einen „Umweg“ gehen um das ganze dynamisch zu machen.
      Will ich nun per Code-Behind die Sortierung ändern oder eine neue Sortierung hinzufügen, Filtern oder die Gruppierung ändern kann ich dies nicht ohne weiteres machen da die CollectionViewSource inkl. ihren Eigenschaften in XAML festgelegt ist.
      Wir müssen uns also die CollectionViewSource in die CodeBehind hereinholen.
      Dies können wir über FindResource machen. Eine Resource bekommt man indem man die Methode FindResource der Window Klasse aufruft und den Key der Resource als Parameter übergibt. Der Key unserer Resource ist „personsView“.

      VB.NET-Quellcode

      1. Dim colView As CollectionViewSource = CType(Me.FindResource("personsView"), CollectionViewSource)


      Die Rückgabe von FindResource muss man Casten da FindResource immer ein Objekt von Typ Object zurückgibt.
      Nun können wir im Code die SortDescription Auflistung zurücksetzen und eine neue SortDescription hinzufügen um Beispielsweise nach dem Vornamen der Personen sortieren zu können.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AllPersons = New ObservableCollection(Of Person)
      3. AllPersons.Add(New Person("Max", "Mustermann"))
      4. AllPersons.Add(New Person("Susi", "Sorglos", PersonGender.Female))
      5. AllPersons.Add(New Person("Maria", "Musterfrau", PersonGender.Female))
      6. AllPersons.Add(New Person("Peter", "Hilflos"))
      7. Me.DataContext = Me
      8. Dim colView As CollectionViewSource = CType(Me.FindResource("personsView"), CollectionViewSource)
      9. colView.SortDescriptions.Clear()
      10. colView.SortDescriptions.Add(New SortDescription("FirstName", ListSortDirection.Ascending))
      11. End Sub


      Wie der folgende Screenshot zeigt wurde nun nach Vornamen sortiert. Nun kann man auch schön sehen das uns die WPF auch die Sortierung der Gruppierung abgeändert hat.


      Ich persönlich finde es nicht sehr flexibel die CollectionViewSource in XAML zu definieren, ein weiteres Problem ist das wenn man die CollectionViewSource aus dem XAML entfernt oder den Key abändert der Compiler dies nicht mitbekommt. Das Programm kompiliert ganz normal und zu Laufzeit bekommt man eine ResourceReferenceKeyNotFoundException um die Ohren geworfen.




      Natürlich erkläre ich dies alles auch wieder interaktiv in einem Video wo ihr besser sehen könnt wie einfach und übersichtlich man mit DataTemplates arbeiten kann. Spätestens jetzt sollte man die stärken der WPF erkannt haben würde ich meinen.
      Die Solution und alle Kapitel und Downloads sind im Tutorialthread enthalten, viel spaß mit dem Video.



      Hier die Solution: 2_1_4_6_Collections.zip
      Und wie immer auch das PDF für euer E-Book:
      2.1.4.6.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

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

      Binding an Collections mit CollectionViewSource Teil 2 von 2

      2.1.4.6 Teil 2

      Binding an Collections mit CollectionViewSource



      Also kommen wir zur zweiten Methode welche ich oben bereits angesprochen hatte. Die Erstellung und Manipulation einer CollectionViewSource per Code.
      Hier werde ich auch auf ein paar Funktionen der CollectionViewSource auf welche ich bisher nicht eingegangen bin zu sprechen kommen, ihre Verwendung ist allerdings die gleiche für beide Arten der Erstellung.
      Hierfür erstelle ich abermals ein neues Projekt und füge dort meine Person Klasse hinzu.
      Auch unseren XAML übernehme ich weitestgehend damit ich den GroupStyle nicht neu schreiben muss nur das mein Binding der ItemsSource-Eigenschaft des DataGrid nun wieder lediglich auf AllPersons verweist da es unsere Resource nun ja nicht gibt.
      Innerhalb der CodeBehind unseres MainWindows implementieren wir wieder die INotifyPropertyChanged Schnittstelle und erstellen unsere [i]ObservableCollection(Of Person)[/i].In Window_Loaded füllen wie abermals die Collection mit Daten.

      VB.NET-Quellcode

      1. Class MainWindow
      2. Implements INotifyPropertyChanged
      3. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      4. AllPersons = New ObservableCollection(Of Person)
      5. AllPersons.Add(New Person("Max", "Mustermann"))
      6. AllPersons.Add(New Person("Susi", "Sorglos", PersonGender.Female))
      7. AllPersons.Add(New Person("Maria", "Musterfrau", PersonGender.Female))
      8. AllPersons.Add(New Person("Peter", "Hilflos"))
      9. Me.DataContext = Me
      10. End Sub
      11. Private _allPersons As ObservableCollection(Of Person)
      12. Public Property AllPersons() As ObservableCollection(Of Person)
      13. Get
      14. Return _allPersons
      15. End Get
      16. Set(ByVal value As ObservableCollection(Of Person))
      17. _allPersons = value
      18. RaisePropertyChanged()
      19. End Set
      20. End Property
      21. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
      22. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      23. End Sub
      24. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      25. End Class


      Nun haben wir wieder unseren Ausgangspunkt wie wir Ihn bereits hatten:

      Um nun eine CollectionViewSource zu erstellen und darauf zu Binden erstellen wir uns in der MainWindow.vb ein Property „AllPersonsView“ von Typ ICollectionView.

      VB.NET-Quellcode

      1. Private _allPersonsView As ICollectionView
      2. Public Property AllPersonsView() As ICollectionView
      3. Get
      4. Return _allPersonsView
      5. End Get
      6. Set(ByVal value As ICollectionView)
      7. _allPersonsView = value
      8. RaisePropertyChanged()
      9. End Set
      10. End Property


      Im MainWindow_Loaded werden wir nun unser Property instanzieren und zwar nutzen wir hierfür wie schon weiter oben angesprochen die statischen Methode GetDefaultView.
      Woraufhin wir nun unser Binding korrigieren können. Statt auf AllPersons können wir nun auf AllPersonsView binden. Soweit haben wir nun direkt an eine CollectionView gebunden welche wir im Code erstellt haben. Sortieren, Gruppieren und Filtern werden wir nun in die Hand nehmen.

      Im vorigen Beispiel haben wir bereits gesehen wie wir dies bewerkstelligen können. Der einzige unterschied ist das wir nun direkt auf das Property zugreifen können und uns die CollectionView nicht aus den Resourcen holen müssen.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AllPersons = New ObservableCollection(Of Person)
      3. AllPersons.Add(New Person("Max", "Mustermann"))
      4. AllPersons.Add(New Person("Susi", "Sorglos", PersonGender.Female))
      5. AllPersons.Add(New Person("Maria", "Musterfrau", PersonGender.Female))
      6. AllPersons.Add(New Person("Peter", "Hilflos"))
      7. AllPersonsView = CollectionViewSource.GetDefaultView(AllPersons)
      8. AllPersonsView.SortDescriptions.Add(New SortDescription("LastName", ListSortDirection.Ascending))
      9. AllPersonsView.GroupDescriptions.Add(New PropertyGroupDescription("Gender"))
      10. Me.DataContext = Me
      11. End Sub


      Gehen wir nun über zum Filtern. Auch zum Filtern bietet die CollectionView einen Mechanismus welcher im Grunde sehr leicht handzuhaben ist. Hierfür besitzt sie eine Eigenschaft mit dem Namen „Filter“ welcher ein Predicate(Of Object) erwartet.
      AllPersonsView.Filter = AddressOf AllPersonsView_Filter

      VB.NET-Quellcode

      1. Private Function AllPersonsView_Filter(obj As Object) As Boolean
      2. End Function


      Jetzt müssen wir uns die Frage stellen was wir Filtern möchten. Im Normalfall hätten wir bestimmt eine Textbox im View in welche der User etwas eintippen kann woraufhin gefiltert werden soll. Das werden wir auch machen. Der Benutzer tippt etwas in die TextBox woraufhin direkt gefiltert werden soll. Hierfür benötigen wir ein Property in MainWindow.vb welches unseren Text der Textbox hält und welches wir die TextBox binden.

      VB.NET-Quellcode

      1. Private _filterText As String
      2. Public Property FilterText() As String
      3. Get
      4. Return _filterText
      5. End Get
      6. Set(ByVal value As String)
      7. _filterText = value
      8. RaisePropertyChanged()
      9. End Set
      10. End Property


      Unser View ändern wir so ab das wir im oberen Teil eine TextBox haben welche auf diese Eigenschaft gebunden ist wobei wir beim Binding die UpdateSourceTrigger Eigenschaft auf PropertyChanged festlegen damit nach jedem Tastenanschlag gefiltert wird.

      XML-Quellcode

      1. <Window x:Class="MainWindow"
      2. ...
      3. mc:Ignorable="d"
      4. Title="MainWindow" Height="450" Width="800">
      5. <DockPanel>
      6. <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5">
      7. <Label Content="Filter:"/>
      8. <TextBox Text="{Binding FilterText,UpdateSourceTrigger=PropertyChanged}" Width="100" VerticalContentAlignment="Center"/>
      9. </StackPanel>
      10. <DataGrid ItemsSource="{Binding AllPersonsView}" CanUserAddRows="False">
      11. ...
      12. </DataGrid>
      13. </DockPanel>
      14. </Window>


      Immer wenn nun etwas in die TextBox eingegeben wird, wird der Setter des Property durchlaufen was auch ein guter Zeitpunkt ist um das Filtern anzustoßen. Um dies zu tun müssen wie lediglich die Methode Refresh() der CollectionView aufrufen, alles andere erledigt die CollectionView bzw. unser Delegat (welcher noch leer ist) für uns.

      VB.NET-Quellcode

      1. Private _filterText As String
      2. Public Property FilterText() As String
      3. Get
      4. Return _filterText
      5. End Get
      6. Set(ByVal value As String)
      7. _filterText = value
      8. AllPersonsView.Refresh()
      9. RaisePropertyChanged()
      10. End Set
      11. End Property


      Also müssen wir nun nur noch unseren Delegaten mit leben füllen.Was passiert nun in unserer Filter-Methode. Die CollectionView ruft diese für JEDES Element auf und Fragt uns quasi ob das Element gefiltert werden soll oder nicht!
      Man würde vermuten das in einer Methode „Filter“ danach „gefragt“ wird ob man ein Element Filtern möchte, dies ist allerdings nicht der Fall. Jedes Element für welches man True zurück gibt wir NICHT gefiltert. Also ist dieser Filter eher wie eine Suche zu verstehen als ein Filter.
      Wir möchten das unser Filter (oder auch Suche) mit dem Namen funktioniert und zwar auch mit Teilen davon.

      VB.NET-Quellcode

      1. Private Function AllPersonsView_Filter(obj As Object) As Boolean
      2. If String.IsNullOrEmpty(FilterText) Then Return True
      3. Dim currentPerson As Person = CType(obj, Person)
      4. Return currentPerson.FullName.ToLower.Contains(FilterText.ToLower)
      5. End Function


      Zuerst prüfen wir ob unser FilterText-Property nicht Nothing oder leer ist. Ist dies der Fall geben wir alle Elemente direkt zur anzeige frei.
      Wir Casten das hereingereichte Objekt in ein Person-Objekt und prüfen ob der FilterText im FullName der Person enthalten ist wobei wir hier die Groß – Kleinschreibung außer acht lassen.Folgender Screenshot zeigt das Ergebnis:



      Der Filter funktioniert und arbeitet wie gewollt. Wenn Ihr das Beispiel ausprobiert setzt mal einen Haltepunkt auf das AllPersons Property und seht dort mal nach welche Einträge dort vorhanden sind wenn der Filter aktiv ist. Ihr werdet sehen das dort IMMER alle Elemente verbleiben. Es wird lediglich in der CollectionView gefiltert, die zugrundeliegende Collection bleibt wie sie ist und bleibt auch von der Sortierung und der Gruppierung unberührt. Was sehr von Vorteil für die Weiterbearbeitung sein kann. Fast immer ist es so das es für den Programmierer unerheblich ist wie im View die Daten gerade angezeigt werden, wichtiger ist diesem meist das die Daten so bleiben wie sie sind damit sich Beispielsweise der Index eines Elements nicht ändert nur weil der Benutzer gerade die Sortierung geändert hat. Für den Programmierer bleibt „seine“ Liste immer wie sie ist was ich sehr schön finde.

      Natürlich erkläre ich dies alles auch wieder interaktiv in einem Video wo ihr besser sehen könnt wie einfach und übersichtlich man mit DataTemplates arbeiten kann. Spätestens jetzt sollte man die stärken der WPF erkannt haben würde ich meinen.
      Die Solution und alle Kapitel und Downloads sind im Tutorialthread enthalten, viel spaß mit dem Video.




      Hier die Solution: 2_1_4_6_Collections_2.zip
      Und wie immer auch das PDF für euer E-Book:
      2.1.4.6#2.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

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

      2.1.4.7 Validieren von Benutzereingaben

      2.1.4.7

      Validieren von Benutzereingaben



      Eine oft unterschätzte aber meiner Meinung nach sehr wichtige Aufgabe eines Programms ist die Validierung von Benutzereingaben auf ihre Richtigkeit. Hat der Benutzer einen gültigen Wert angegeben?
      Ich sehe es immer wieder Programme welche Benutzereingaben schlichtweg nicht prüfen bzw. nur beim Speichern prüfen. Soll bedeuten, der Benutzer kann eingeben was er möchte und beim Speichern erhält dieser eine Meldung das die Eingaben nicht korrekt wären. Oft sogar ohne jegliche Information wo nun die Falscheingabe vorliegt oder wo genau der Fehler liegt. Ich sehe immer wieder Meldungen wie „Bitte korrigieren Sie die Eingaben“. Das ist natürlich keine gute Benutzerführung.

      Die WPF bietet hierfür ValidationRules. Um die vom Benutzer eingegebenen Daten zu validieren, besitzt das Binding-Objekt eine [i]ValidationRules[/i]-Property. Alle dort hinterlegten Regeln greifen allerdings nur wenn ein Wert von der Target-Property zur Source-Property geschrieben wird. Also im TwoWay-Mode bzw. im OneWayToSource-Mode eines Bindings.

      Folgende Abbildung zeigt das Modell der Validierung:


      Wie gut zu sehen greift die Validierung genau im Bindingmechanismus ein. Werden Daten nicht zurückgeschrieben wird auch nicht Validiert! Das bedeutet allerdings auch das eine Validierung bei einem Binding mit dem UpdateSourceTrigger=LostFocus erst ausgeführt wird wenn die TextBox den Focus verliert.
      Zur ValidationRules Property werden Instanzen vom Typ ValidationRule hinzugefügt. Die ValidationRule Klasse selbst ist Abstrakt und enthält die abstrakte Validate Methode.Die Validate Methode kann von Subklassen überschrieben werden. Die WPF bringt von Haus aus zwei Subklassen. Die ExceptionValidationRule und die DataErrorValidationRule, aber natürlich können auch eigene ValidationRules erstellt werden was wir im Zuge dieses Kapiteln auch machen werden.
      Betrachten wir uns erst eine BuildIn ValidationRule anhand eines Beispiels um danach eine eigene ValidationRule-Klasse zu erstellen. Zu guter Letzt werden wir uns die DataErrorValidationRule ansehen und was es hiermit genau auf sich hat.

      Sehen wir uns anhand eine Beispiels die Funktionsweise von ValidationRules an indem wir ein neues Projekt erstellen und den Preis eines Fahrzeugs eingeben. Üblicherweise kann bei einem Preis für ein Auto kein Negativer Preis eingegeben werden.

      XML-Quellcode

      1. <Window x:Class="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:_2_1_4_7_Validierung"
      7. mc:Ignorable="d"
      8. Title="MainWindow" Height="127.34" Width="386.383" Language="de">
      9. <Window.DataContext>
      10. <local:Car Price="2379.99"/>
      11. </Window.DataContext>
      12. <Grid Margin="5">
      13. <Grid.RowDefinitions>
      14. <RowDefinition Height="Auto"/>
      15. <RowDefinition Height="Auto"/>
      16. <RowDefinition Height="*"/>
      17. </Grid.RowDefinitions>
      18. <StackPanel Orientation="Horizontal">
      19. <Label Content="_Preis"/>
      20. <TextBox x:Name="txtPrice"
      21. Width="60"
      22. Text="{Binding Price,UpdateSourceTrigger=PropertyChanged}"
      23. VerticalContentAlignment="Center"/>
      24. </StackPanel>
      25. <StackPanel Orientation="Horizontal" Grid.Row="1">
      26. <TextBlock Text="Aktueller Preis des Fahrzeugs: "/>
      27. <TextBlock Text="{Binding Price, StringFormat=\{0:C\}}"/>
      28. </StackPanel>
      29. </Grid>
      30. </Window>




      In diesem Feld kann nun ein Negative Wert eingegeben werden, dies gilt es nun zu verhindern da dies nicht möglich sein soll.
      Wir fügen also unserem Binding der TextBox eine ExceptionValidationRule hinzu.

      XML-Quellcode

      1. <TextBox x:Name="txtPrice"
      2. Width="60"
      3. VerticalContentAlignment="Center">
      4. <Binding Path="Price" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
      5. <Binding.ValidationRules>
      6. <ExceptionValidationRule/>
      7. </Binding.ValidationRules>
      8. </Binding>
      9. </TextBox>


      Aber noch immer tut sich nichts. Das liegt daran das keine Exception geworfen wird. Der Datentyp Decimal lässt ja negative Werte zu. Wir müssen also eine Exception werfen falls ein Negativer Wert eingegeben wurde. Gehen wir hierfür in die Car-Klasse und sorgen dafür dass eine Exception geworfen wird, gesetzt den Fall das ein Negativer Wert übertragen wird.

      VB.NET-Quellcode

      1. Private _price As Decimal
      2. Public Property Price() As Decimal
      3. Get
      4. Return _price
      5. End Get
      6. Set(ByVal value As Decimal)
      7. If value < 0 Then
      8. Throw New ArgumentOutOfRangeException("Der Preis darf nicht negativ sein!")
      9. End If
      10. _price = value
      11. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Price"))
      12. End Set
      13. End Property


      Geben wir nun einen Negativen Wert in die TextBox ein indem wir einfach ein – vor den Preis setzen läuft der Debugger in genau diese Exception, wird drücken F5 damit das Programm weiterläuft und sehen das wir nun eine rot umrandete TextBox zu Gesicht bekommen, sowie das der Preis nicht übertragen wurde.



      Außerdem sehen wir in der Ausgabe von VisualStudio das es hier einen DataError gab, inkl. unseres Textes.

      System.Windows.Data Error: 8 : Cannot save value from target back to source. BindingExpression:Path=Price; DataItem='Car' (HashCode=58328727); target element is 'TextBox' (Name='txtPrice'); target property is 'Text' (type 'String') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Das angegebene Argument liegt außerhalb des gültigen Wertebereichs.Parametername: Der Preis darf nicht negativ sein!

      Als nächsten werden wir eine eigene ValidationRule erstellen und diese einbinden.
      Um eine ValidationRule zu erstellen erzeugen wir einfach eine Klasse welche von ValidationRule erbt und die Validate-Methode überschreibt.
      Üblicherweise folgt der Klassenname einer ValidationRule der Konvention <Validatorname>ValidationRule.Wir können hier nun auf alle Eventualitäten prüfen und dementsprechend ein ValidationResult zurückgeben.

      VB.NET-Quellcode

      1. Public Class CarPriceValidationRule
      2. Inherits ValidationRule
      3. Public Overrides Function Validate(value As Object, cultureInfo As CultureInfo) As ValidationResult
      4. Dim val As Decimal
      5. If Decimal.TryParse(value.ToString, val) Then
      6. If val < 0 Then Return New ValidationResult(False, "Der Preis darf nicht kleiner als 0 sein!")
      7. Return ValidationResult.ValidResult
      8. Else
      9. Return New ValidationResult(False, "Ungültiger Wert!")
      10. End If
      11. End Function
      12. End Class


      HINWEIS: Bei der Verwendung einer eigenen ValidationRule muss der Setter der Car-Klasse nicht verändert werden so dass dieser eine Exception wirft. Das ist gerade dann gut wenn man evtl. keinen Zugriff auf die Datenklasse hat.
      Das Ergebnis im View sieht genau gleich aus. Die TextBox wird rot umrandet und zeigt uns somit eine Falscheingabe an. Der einzige Unterschied hier ist im Moment das der Debugger die Ausführung des Programms nicht anhält, da keine Exception geworfen wurde.

      Zu guter Letzt kommen wir nun zur Validierung mit der DataErrorValidationRule.
      Die dritte Möglichkeit basiert auf dem seit .Net 1.0 existierenden Interface IDataErrorInfo welches zwei Property enthält. Wir implementieren also in unsere Car-Klasse das IDataErrorInfo-Interface wodurch uns zwei Eigenschaften erzeugt werden:

      VB.NET-Quellcode

      1. Public Class Car
      2. Implements INotifyPropertyChanged
      3. Implements IDataErrorInfo
      4. Private _price As Decimal
      5. Public Property Price() As Decimal
      6. Get
      7. Return _price
      8. End Get
      9. Set(ByVal value As Decimal)
      10. _price = value
      11. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Price"))
      12. End Set
      13. End Property
      14. Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item
      15. Get
      16. Throw New NotImplementedException()
      17. End Get
      18. End Property
      19. Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
      20. Get
      21. Throw New NotImplementedException()
      22. End Get
      23. End Property
      24. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      25. End Class


      Von der WPF wird lediglich die Item-Property genutzt. Die Error-Property kann also ruhig Nothing zurückgeben.
      Die Item-Property wird jedes Mal von der WPF aufgerufen wenn sich ein Wert in einem Feld ändert welches eine DataErrorValidationRule im Binding implementiert oder innerhalb des Binding die Eigenschaft ValidatesOnDataError auf True gesetzt wurde und übergibt als Parameter (columnName) den Namen des Binding, was in unserem Fall im Moment nur „Price“ sein kann.
      In diesem Fall können wir nun wieder die wesentlich kürzere Attributsyntax verwenden um das Binding zu setzen:

      XML-Quellcode

      1. <TextBox x:Name="txtPrice"
      2. Width="60"
      3. VerticalContentAlignment="Center"
      4. Text="{Binding Price,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}">
      5. </TextBox>


      Allerdings hat die Validierung über IDataErrorInfo auch einen Nachteil. Der Wert wird erst validiert nachdem dieser gesetzt wurde, in unserem Fall wird also der negative Wert in unserer Car-Klasse persistiert, nun müssen wir auf jeden Fall das Speichern verhindern.



      Mir persönlich ist dennoch diese Methode die liebste da ich hier sehr flexibel bin und die kurze Schreibweise vom Binding über die Attributsyntax bevorzuge. Aber wofür schrieben wir die ganze Zeit eine Errormeldung wenn die WPF diese überhaupt nicht anzeigt? Wir sehen lediglich einen roten Rahmen der anzeigt das es irgendeinen Fehler gibt. Damit der rote Rahmen angezeigt wird nutzt die TextBox AttachedProperties der Validation-Klasse. Die folgenden AttachedProperties stehen hier zur Verfügung: Errors, HasError und ErrorTemplate.
      Wir werden nun einen Style für die ErrorTemplate-Eigenschaft definieren um das ErrorTemplate etwas informativer zu gestalten und dem User einige mehr Infos mit auf dem Weg zu geben.
      Hier ist eure Fantasie gefragt, wichtig ist in einem Style für ein ErrorTemplate das dieses ein AdornerElementPlaceholder Objekt enthält. Dieses gilt als Platzhalter für die TextBox.
      Wir legen uns also einen Style für eine TextBox in den Window-Resourcen an und überschreiben den Style für die Eigenschaft Validation.ErrorTemplate welcher auch gleich der Error-Text in einem Tooltip über AdornedElement.(Validation.Errors)[0].ErrorContent anzeigen kann.

      XML-Quellcode

      1. <Style TargetType="TextBox">
      2. <Setter Property="Validation.ErrorTemplate">
      3. <Setter.Value>
      4. <ControlTemplate>
      5. <DockPanel LastChildFill="True">
      6. <Ellipse DockPanel.Dock="Right"
      7. ToolTip="{Binding ElementName=myTextbox, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
      8. Margin="-20,0,2,0" Height="18" StrokeThickness="1" Fill="Red" >
      9. <Ellipse.Stroke>
      10. <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
      11. <GradientStop Color="#FFFA0404" Offset="0"/>
      12. <GradientStop Color="#FFC9C7C7" Offset="1"/>
      13. </LinearGradientBrush>
      14. </Ellipse.Stroke>
      15. <Ellipse.Triggers>
      16. <EventTrigger RoutedEvent="FrameworkElement.Loaded">
      17. <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}"/>
      18. </EventTrigger>
      19. </Ellipse.Triggers>
      20. </Ellipse>
      21. <TextBlock DockPanel.Dock="Right"
      22. ToolTip="{Binding ElementName=myControl,
      23. Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
      24. Foreground="White"
      25. FontSize="11pt"
      26. Margin="-14,0,0,0" FontWeight="Bold" VerticalAlignment="Center">!
      27. <TextBlock.Triggers>
      28. <EventTrigger RoutedEvent="FrameworkElement.Loaded">
      29. <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}"/>
      30. </EventTrigger>
      31. </TextBlock.Triggers>
      32. </TextBlock>
      33. <Border BorderBrush="Red" BorderThickness="1">
      34. <AdornedElementPlaceholder Name="myControl"/>
      35. </Border>
      36. </DockPanel>
      37. </ControlTemplate>
      38. </Setter.Value>
      39. </Setter>
      40. <Style.Triggers>
      41. <Trigger Property="Validation.HasError" Value="true">
      42. <Setter Property="ToolTip"
      43. Value="{Binding RelativeSource={x:Static RelativeSource.Self},
      44. Path=(Validation.Errors)[0].ErrorContent}"/>
      45. </Trigger>
      46. </Style.Triggers>
      47. </Style>


      Damit das UI entwas intreressanter wird habe ich noch eine Animation erstellt welche die Ellipse alle 200ms blinken lässt.

      XML-Quellcode

      1. <Storyboard x:Key="FlashErrorIcon">
      2. <ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
      3. Storyboard.TargetProperty="(UIElement.Visibility)">
      4. <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}"/>
      5. <DiscreteObjectKeyFrame KeyTime="00:00:00.2000000" Value="{x:Static Visibility.Visible}"/>
      6. <DiscreteObjectKeyFrame KeyTime="00:00:00.4000000" Value="{x:Static Visibility.Hidden}"/>
      7. <DiscreteObjectKeyFrame KeyTime="00:00:00.6000000" Value="{x:Static Visibility.Visible}"/>
      8. <DiscreteObjectKeyFrame KeyTime="00:00:00.8000000" Value="{x:Static Visibility.Hidden}"/>
      9. <DiscreteObjectKeyFrame KeyTime="00:00:01" Value="{x:Static Visibility.Visible}"/>
      10. </ObjectAnimationUsingKeyFrames>
      11. </Storyboard>




      Jetzt bleibt noch eine oft gestellte Frage über. Wie kann man ALLE aktuellen Validierungsfehler anzeigen lassen? Oft möchte man dem Benutzer irgendwo im UI alle Fehler anzeigen.
      Gibt es mehrere Felder welche Validierungsfehler haben könnten wäre es interessant dem User etwas in der Art anzeigen zu lassen:
      • Der Preis darf nicht kleiner als 0 sein
      • Die Marke ist ein Pflichtfeld
      • Die Farbe darf keine Sonderzeichen enthalten
      Für solche Anzeigen habe ich bereits sehr viele Lösungen gesehen und in jedem Buch findet man glaube ich weitere. Jede für sich hat Ihre Vorteile, jede auch ihre Nachteile. Oft kommt man auf die Nachteile erst wenn man sich später mit MVVM beschäftigt und somit keine Möglichkeit mehr hat auf das Window zuzugreifen da das Window die Eigenschaft BindingGroup besitzt und man über diese auch Fehler auslesen kann, gesetzt den Fall man hat vorher BindingGroups definiert. Die meisten Beispiele in Büchern stützen sich auf diese BindingGroups wobei ich dies nicht unterstützen möchte da wir später abermals umlernen müssten. Außerdem hat man bei den meisten Lösungen nicht die Möglichkeit innerhalb der Klasse die Fehler abzufragen da fast alle Lösungen rein auf der View-Ebene stattfinden. Da ich eher jemand bin der gerne vom Code aus über die View Bescheid weis finde ich diese Lösungen eher suboptimal. Ich stelle hier eine Lösung vor welche vielleicht am Anfang etwas umständlicher erscheint aber bei genauerem Hinsehen werdet Ihr erkennen das dies nicht der Fall.

      Erstmal passen wir unsere Car-Klasse an und fügen noch die Eigenschaften Marke und Modell hinzu damit wir mehrere Eigenschaften haben welche wir auf Ihre Gültigkeit prüfen können.Wir fügen folgende Eigenschaften ein:

      VB.NET-Quellcode

      1. Private _marke As String
      2. Public Property Marke() As String
      3. Get
      4. Return _marke
      5. End Get
      6. Set(ByVal value As String)
      7. _marke = value
      8. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Marke"))
      9. End Set
      10. End Property
      11. Private _modell As String
      12. Public Property Modell() As String
      13. Get
      14. Return _modell
      15. End Get
      16. Set(ByVal value As String)
      17. _modell = value
      18. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Modell"))
      19. End Set
      20. End Property


      Auch die View wird angepasst um diese neuen Eigenschaften anzeigen zu können, weiter fügen wir ein ItemsControl hinzu um später die Fehler anzeigen zu können:

      XML-Quellcode

      1. <Window x:Class="MainWindow"
      2. ...
      3. Title="MainWindow" Height="202.755" Width="385.147" Language="de">
      4. <Window.Resources>
      5. <Storyboard x:Key="FlashErrorIcon">
      6. ...
      7. </Storyboard>
      8. <Style TargetType="TextBox">
      9. ...
      10. </Style>
      11. </Window.Resources>
      12. <Window.DataContext>
      13. <local:Car Price="-2379.99" Marke="Volkswagen" Modell="Golf"/>
      14. </Window.DataContext>
      15. <Grid Margin="5">
      16. <Grid.RowDefinitions>
      17. <RowDefinition Height="Auto"/>
      18. <RowDefinition Height="Auto"/>
      19. <RowDefinition Height="Auto"/>
      20. <RowDefinition Height="Auto"/>
      21. <RowDefinition Height="*"/>
      22. </Grid.RowDefinitions>
      23. <StackPanel Orientation="Horizontal">
      24. <Label Content="M_arke"/>
      25. <TextBox x:Name="txtMarke"
      26. Width="80"
      27. VerticalContentAlignment="Center"
      28. Text="{Binding Marke,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}">
      29. </TextBox>
      30. </StackPanel>
      31. <StackPanel Orientation="Horizontal" Grid.Row="1">
      32. <Label Content="_Modell"/>
      33. <TextBox x:Name="txtModell"
      34. Width="80"
      35. VerticalContentAlignment="Center"
      36. Text="{Binding Modell,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}">
      37. </TextBox>
      38. </StackPanel>
      39. <StackPanel Orientation="Horizontal" Grid.Row="2">
      40. <Label Content="_Preis"/>
      41. <TextBox x:Name="txtPrice"
      42. Width="80"
      43. VerticalContentAlignment="Center"
      44. Text="{Binding Price,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}">
      45. </TextBox>
      46. </StackPanel>
      47. <ItemsControl Grid.Row="3" Foreground="DarkRed"/>
      48. </Grid>
      49. </Window>


      Damit haben wir unser View mal angepasst.
      Fügen wir nun eine Funktion in unsere Car-Klasse ein welche uns eine List(Of ValidationResult) zurückgibt:

      VB.NET-Quellcode

      1. Public Function ValidationErrors() As List(Of ValidationResult)
      2. Dim valRet As New List(Of ValidationResult)
      3. Return valRet
      4. End Function


      Diese ist noch ohne „Leben“ was wir allerdings gleich ändern werden, vorher allerdings passen wir den Code von unserem Item-Property an welches wir weiter oben vom IDataErrorInfo Interface implementiert hatten an.

      VB.NET-Quellcode

      1. Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item
      2. Get
      3. Dim valRes As ValidationResult = ValidationErrors.Where(Function(v) v.MemberNames.Contains(columnName) = True).FirstOrDefault
      4. If valRes Is Nothing Then Return Nothing
      5. Return valRes.ErrorMessage
      6. End Get
      7. End Property


      Vorher hatten wir unseren Code zum Validieren der Felder innerhalb dieses Getters. Dies überlassen wir ab sofort der neu hinzugefügten Funktion „ValidationErrors“ und geben hier lediglich das erste Vorkommen eines Fehlers des gesuchten Feldes zurück.
      Nun prüfen wir in „ValidationErrors“ alle Eigenschaften auf Richtigkeit:

      VB.NET-Quellcode

      1. Public Function ValidationErrors() As List(Of ValidationResult)
      2. Dim valRet As New List(Of ValidationResult)
      3. If String.IsNullOrEmpty(Marke) Or String.IsNullOrEmpty(Modell) Then
      4. Dim fieldList As New List(Of String)
      5. If String.IsNullOrEmpty(Marke) Then fieldList.Add(NameOf(Marke))
      6. If String.IsNullOrEmpty(Modell) Then fieldList.Add(NameOf(Modell))
      7. valRet.Add(New ValidationResult("Die Pflichtfelder müssen alle ausgefüllt werden!", fieldList))
      8. Else
      9. If Marke.Length < 3 Then valRet.Add(New ValidationResult($"'{NameOf(Marke)}' muss aus mindestens 3 Zeichen bestehen!", New List(Of String) From {NameOf(Marke)}))
      10. If Modell.Length < 3 Then valRet.Add(New ValidationResult($"'{NameOf(Modell)}' muss aus mindestens 3 Zeichen bestehen!", New List(Of String) From {NameOf(Modell)}))
      11. End If
      12. If Price < 0 Then valRet.Add(New ValidationResult($"'{NameOf(Price)}' darf nicht kleiner als 0 sein!", New List(Of String) From {NameOf(Price)}))
      13. Return valRet
      14. End Function


      Ich denke der Code erklärt sich von selbst, wobei dieser alles andere als optimiert ist. Bitte denkt daran - ich versuche nicht hier den saubersten Code zu schreiben, es soll für euch verständlich und übersichtlich sein. Alle Eigenschaften werden auf Ihre Gültigkeit überprüft, wobei für Marke und Modell die minimale Textlänge geprüft wird und für Price wie schon gehabt auf Negative Werte geprüft wird.
      Da wir auf die Funktion vom View aus nicht direkt binden können fügen wir jetzt noch ein ReadOnly Property ein welches uns die Fehler (List(Of ValidationError)) als String zurückgibt indem wie aus ValidationErrors rein die ErrorMessage selektieren.

      VB.NET-Quellcode

      1. Public ReadOnly Property ErrorsList As List(Of String)
      2. Get
      3. Return ValidationErrors.Select(Function(e) e.ErrorMessage).ToList()
      4. End Get
      5. End Property


      Damit die WPF die ErrorList Eigenschaft immer neu abfragt sollte sich etwas ändern, werden wir diese darüber benachrichtigen indem wie das PropertyChanged-Event werfen.
      Hierfür fügen wir noch eine Zeile im Item-Property ein so dass wir nun folgenden Code im Getter haben:

      VB.NET-Quellcode

      1. Dim valRes As ValidationResult = ValidationErrors.Where(Function(v) v.MemberNames.Contains(columnName) = True).FirstOrDefault
      2. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(ErrorsList)))
      3. If valRes Is Nothing Then Return Nothing
      4. Return valRes.ErrorMessage


      Damit haben wir alles erledigt und können im View darauf Binden:

      XML-Quellcode

      1. <ItemsControl Grid.Row="3" ItemsSource="{Binding ErrorsList}" Foreground="DarkRed"/>


      Das Ergebnis ist genau das was wir wollten. In eine Anwendung könnten wir nun Beispielsweise einen Speichern-Button deaktivieren wenn ValidationErrors.Any() True zurückgeben würde.
      Ein speichern wäre dann erst gar nicht möglich solange noch Fehler vorhanden sind.
      Ein weiterer Vorteil an dieser Methode gegenüber der Validierung rein im View ist das ich hier im Code auf andere Dinge eingehen kann wie Beispielsweise ob es das Fahrzeug bereits in der Datenbank gibt oder ob eine Fahrgestellnummer gültig ist. Das kann die View nicht da hierfür Logik notwendig wäre. Auch diese Fehler könnte ich anschließend anzeigen lassen.



      Ich hoffe das ich euch damit auf den Geschmack gebracht habe so dass Ihr in Zukunft Wert auf eine gute Validierung von Daten legt. Das ist meiner Meinung nach ein sehr wichtiger Aspekt in der Softwareentwicklung und unterstützt den Benutzer ungemein bei diversen Eingaben.

      Zum abschluss die Car-Klasse hier nochmal gespoilert:
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. Imports System.ComponentModel.DataAnnotations
      3. Public Class Car
      4. Implements INotifyPropertyChanged
      5. Implements IDataErrorInfo
      6. Private _marke As String
      7. Public Property Marke() As String
      8. Get
      9. Return _marke
      10. End Get
      11. Set(ByVal value As String)
      12. _marke = value
      13. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Marke"))
      14. End Set
      15. End Property
      16. Private _modell As String
      17. Public Property Modell() As String
      18. Get
      19. Return _modell
      20. End Get
      21. Set(ByVal value As String)
      22. _modell = value
      23. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Modell"))
      24. End Set
      25. End Property
      26. Private _price As Decimal
      27. Public Property Price() As Decimal
      28. Get
      29. Return _price
      30. End Get
      31. Set(ByVal value As Decimal)
      32. _price = value
      33. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Price"))
      34. End Set
      35. End Property
      36. Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item
      37. Get
      38. Dim valRes As ValidationResult = ValidationErrors.Where(Function(v) v.MemberNames.Contains(columnName) = True).FirstOrDefault
      39. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(ErrorsList)))
      40. If valRes Is Nothing Then Return Nothing
      41. Return valRes.ErrorMessage
      42. End Get
      43. End Property
      44. Public Function ValidationErrors() As List(Of ValidationResult)
      45. Dim valRet As New List(Of ValidationResult)
      46. If String.IsNullOrEmpty(Marke) Or String.IsNullOrEmpty(Modell) Then
      47. Dim fieldList As New List(Of String)
      48. If String.IsNullOrEmpty(Marke) Then fieldList.Add(NameOf(Marke))
      49. If String.IsNullOrEmpty(Modell) Then fieldList.Add(NameOf(Modell))
      50. valRet.Add(New ValidationResult("Die Pflichtfelder müssen alle ausgefüllt werden!", fieldList))
      51. Else
      52. If Marke.Length < 3 Then valRet.Add(New ValidationResult($"'{NameOf(Marke)}' muss aus mindestens 3 Zeichen bestehen!", New List(Of String) From {NameOf(Marke)}))
      53. If Modell.Length < 3 Then valRet.Add(New ValidationResult($"'{NameOf(Modell)}' muss aus mindestens 3 Zeichen bestehen!", New List(Of String) From {NameOf(Modell)}))
      54. End If
      55. If Price < 0 Then valRet.Add(New ValidationResult($"'{NameOf(Price)}' darf nicht kleiner als 0 sein!", New List(Of String) From {NameOf(Price)}))
      56. Return valRet
      57. End Function
      58. Public ReadOnly Property ErrorsList As List(Of String)
      59. Get
      60. Return ValidationErrors.Select(Function(e) e.ErrorMessage).ToList()
      61. End Get
      62. End Property
      63. Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
      64. Get
      65. Return Nothing
      66. End Get
      67. End Property
      68. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      69. End Class



      Natürlich erkläre ich dies alles auch wieder interaktiv in einem Video wo ihr besser sehen könnt wie einfach und übersichtlich man mit DataTemplates arbeiten kann. Spätestens jetzt sollte man die stärken der WPF erkannt haben würde ich meinen.
      Die Solution und alle Kapitel und Downloads sind im Tutorialthread enthalten, viel spaß mit dem Video.




      Hier die Solution: 2_1_4_7_Validierung.zip
      Und wie immer auch das PDF für euer E-Book:
      2.1.4.7.pdf

      Ich freue mich wie immer über Kommentare, Kritik und Vorschläge sowie natürlich auch likes/hilfreich.
      Entweder im Supportthread oder auf meinem YouTube Kanal.
      :thumbsup:

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

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