Tutorialreihe <WPF lernen/>

    • WPF

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

      4.2 - Die erste View erstellen

      4.2

      Das erste View erstellen



      In diesem Kaptitel werden wir unsere erste View mit einer Klasse verbinden. Wie im letzten Kapitel bereits erwähnt werde ich hier nicht direkt mit den Modelklassen arbeiten, ihr werdet im Verlauf der nächsten Kapitel – aber auch in diesem – sehen warum dies von Vorteil sein kann.
      Als legen wir uns einen Order für unsere „ViewModel“-Klassen an. Ich nenne diese ViewModel-Klassen auch wenn es hier nicht um MVVM geht. Das eine hat mit dem anderen ja nicht viel zu tun.
      Jede Klasse welche mit einem View verbunden wird kann als ViewModel-Klasse bezeichnet werden.

      Als erste Klasse lege ich eine Basisklasse an damit wir nicht für jede einzelne Klasse INotifyPropertyChanged implementieren müssen sondern eine Basisklasse haben von welcher wir immer wieder erben können. Das spart uns viel Tipparbeit. Die ViewModel-Klasse packe ich alle in einen seperaten Namespace „ViewModel“.

      VB.NET-Quellcode

      1. Namespace ViewModel
      2. Public MustInherit Class ViewModelBase
      3. Implements INotifyPropertyChanged
      4. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional prop As String = "")
      5. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      6. End Sub
      7. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      8. End Class
      9. End Namespace


      Nun können wir bereits beginnen die erste ViewModel-Klasse anzulegen. Die ContactViewModel-Klasse. Diese Klasse hat die Aufgabe die Eigenschaften der Model-Klasse „Contact“ für die View aufzubereiten. Wie schon erwähnt machen es viele so das sie INotifyPropertyChanged in der Modelklasse implementieren um sich so diese Zwischenklasse zu „sparen“. Das ist natürlich ein Weg den man gehen kann um dem dont repeat yourself Grundsatz zu folgen, ich habe aber eben die Erfahrung gemacht das sich diese zusätzlicher Arbeit durchaus auszahlt und sogar eher komplexität herausnimmt – auch wenn dies auf den ersten Blick nicht so aussieht - speziell auf Hinblick auf späteres MVVM, aber das ist ein späteres Thema, ihr werdet auch in diesem Kapitel bereits sehen das dies von Vorteil sein kann.

      Erstellen wir erstmal eine Klasse, ich erkläre hierzu gleich ein paar Dinge:

      VB.NET-Quellcode

      1. Namespace ViewModel
      2. Public Class ContactViewModel
      3. Inherits ViewModelBase
      4. Private ReadOnly _modelContact As Model.Contact
      5. Public Sub New()
      6. _modelContact = New Model.Contact
      7. ContactData = New ObservableCollection(Of ContactDataViewModel)
      8. End Sub
      9. Friend Sub New(model As Model.Contact)
      10. _modelContact = model
      11. ContactData = New ObservableCollection(Of ContactDataViewModel)
      12. model.ContactData.ForEach(Sub(x) ContactData.Add(New ContactDataViewModel(x)))
      13. End Sub
      14. Public Property FullName() As String
      15. Get
      16. Return $"{_modelContact.FirstName} {_modelContact.LastName}"
      17. End Get
      18. Set(ByVal value As String)
      19. Dim fullname = value.Split(" ")
      20. _modelContact.FirstName = fullname.First
      21. If fullname.Length > 1 Then _modelContact.LastName = fullname.Last
      22. RaisePropertyChanged()
      23. End Set
      24. End Property
      25. Public Property ImagePath As String
      26. Get
      27. Return _modelContact.ImagePath
      28. End Get
      29. Set(value As String)
      30. _modelContact.ImagePath = value
      31. RaisePropertyChanged()
      32. End Set
      33. End Property
      34. Public Property Birthday As Date?
      35. Get
      36. Return _modelContact.Birthday
      37. End Get
      38. Set(value As Date?)
      39. _modelContact.Birthday = value
      40. RaisePropertyChanged()
      41. End Set
      42. End Property
      43. Public Property Note As String
      44. Get
      45. Return _modelContact.Note
      46. End Get
      47. Set(value As String)
      48. _modelContact.Note = value
      49. RaisePropertyChanged()
      50. End Set
      51. End Property
      52. Private _contactData As ObservableCollection(Of ContactDataViewModel)
      53. Public Property ContactData() As ObservableCollection(Of ContactDataViewModel)
      54. Get
      55. Return _contactData
      56. End Get
      57. Set(ByVal value As ObservableCollection(Of ContactDataViewModel))
      58. _contactData = value
      59. RaisePropertyChanged()
      60. End Set
      61. End Property
      62. End Class
      63. End Namespace


      OK, das sieht erstmal verwirrend aus. Gebe ich zu. Fangen wir oben an. Wir haben eine Private Schreibgeschützte Variable vom Typ Model.Contact.
      Diese Variable dient dazu unser „Modelobjekt“ zu halten. Wie an den beiden Konstuktoren zu sehen, wird entweder eine neue Instanz eines Contact erstellt falls beim Instaziieren unseres ViewModels keine Instanz hereingereicht wird, oder es wird die Instanz an die Variable übergeben.

      Wir haben somit immer ein Model-Object in dieser Variable. In späterer folge haben wir dann eine Eigenschaft FullName. Hier ist schon mal ein Fall für den eine ViewModel Klasse eben praktisch ist.

      In unserem Model ist definiert das wir FirstName und LastName getrennt speichern werden. Wie Ihr es vom Handy evtl. kennt kann man einen neuen Kontakt aber meißt vereinfacht eingeben indem man einfach den Vor und den Zunamen in einer Zeile eingibt. Tja, unser Telefonbuch soll dies auch können, gespeichert soll aber trotzdem getrennt werden. Also wenn der User „Sascha Patschka“ in das Feld eingibt soll unsere App hier „Sascha“ als Vornamen und „Patschka“ als Nachnamen speichern. Das ist bereits der Vorteil der Trennung zwischen einem ViewModel und einem Model.

      Wir können frei bestimmen und sind flexibel wie die View aussehen soll bzw. wie Daten sowohl angezeigt als auch eingegeben werden können.
      Im Getter der Eigenschaft setzen wir die Model-Eigenschaften FirstName und LastName zusammen und geben diese mit einem Leehrzeichen getrennt aus.
      Im Setter lesen wir den String aus und versuchen diesen zu Splitten und anschliessend getrennt an das Modelobjekt zu übergeben . Die Art wie dies geschied ist im Moment nur sehr rudimentär implementiert und hat noch so einige Fehler, reicht uns aber fürs erste um einfach hier nur eine Idee zu bekommen was wir hier vorhaben. Nun sehen wir uns die nächsten Eigenschaften an. Diese reichen im Grunde einfach nur durch.

      VB.NET-Quellcode

      1. Public Property ImagePath As String
      2. Get
      3. Return _modelContact.ImagePath
      4. End Get
      5. Set(value As String)
      6. _modelContact.ImagePath = value
      7. RaisePropertyChanged()
      8. End Set
      9. End Property


      Im Getter wir der ImagePath des ModelObjekts einfach durchgereicht und im Setter wird ImagePath des Modelobjekts gesetzt. Man kann dies als Wrapper bezeichnen.

      Im Grunde geht es einfach darum eine Eigenschaft zu haben welche nach dem setzen des Wertes das Event PropertyChanged wirft und das macht unsere Eigenschaft im Setter mit der Methode RaisePropertyChanged().

      Kommen wir zur letzten Eigenschaft „ContactData“ vom Typ ObservableCollection(Of ContactDataViewModel).
      Diese in Verbindung mit der dritten Zeile des Konstruktors ist etwas speziell und kann vielleicht verwirrend sein. Aber keine Sorge, wir gehen das langsam durch.

      Erstmal benötigen wir den Typ ContactDataViewModel.

      VB.NET-Quellcode

      1. Namespace ViewModel
      2. Public Class ContactDataViewModel
      3. Inherits ViewModelBase
      4. Private ReadOnly _modelContactData As ContactData
      5. Public Sub New()
      6. _modelContactData = New ContactData()
      7. End Sub
      8. Friend Sub New(model As Model.ContactData)
      9. _modelContactData = model
      10. End Sub
      11. Public Property Data As String
      12. Get
      13. Return _modelContactData.Data
      14. End Get
      15. Set(value As String)
      16. _modelContactData.Data = value
      17. RaisePropertyChanged()
      18. End Set
      19. End Property
      20. Public Property DataType As EnumContactDataType
      21. Get
      22. Return _modelContactData.DataType
      23. End Get
      24. Set(value As EnumContactDataType)
      25. _modelContactData.DataType = value
      26. RaisePropertyChanged()
      27. End Set
      28. End Property
      29. End Class
      30. End Namespace


      Diese Klasse sollte soweit verständlich sein, auch diese dient als „Wrapper“ für ein Model-Objekt.

      Zurück zur ContactViewModel-Klasse…
      Wir bekommen ja unser Model-Objekt in die private Variable. Um nun die Auflistung an ContactData des Model-Objekts in die Eigenschaft ContactData des ViewModels zu bekommen können wir diesmal nicht einfach „durchreichen“ da die Typen sich unterschieden. Wir müssen die Typen also konvertieren. Das machen wir am einfachsten indem wir in einer Schleife die Auflistung durchgehen.

      Da wir in der Klasse ContactDataViewModel einen Konstruktor haben welchem wir ein Model-Objekt übergeben können ist dies auch einfach. Wir erstellen schlicht für jedes Objekt in der Auflistung des Model-Objekts eine neue Instanz von ContactDataViewModel und verwenden den Konstruktor mit der Überladung welcher wir ein Modelobjekt mitgeben können was in der dritten Zeile des Konstruktors von ContactViewModel passiert.

      Schon haben wir dafür gesorgt dass die Auflistung mit Daten gefüllt ist.

      Nun muss nur noch schnell ein View her um das mal zu testen.
      Wir öffnen die CodeBehind des MainWindow und erstellen erstmal eine Eigenschaft welche eine Hand voll Testdaten halten soll. Eine ObservableCollection(Of ContactViewModel)

      VB.NET-Quellcode

      1. Class MainWindow
      2. Private _allContacts As ObservableCollection(Of ContactViewModel)
      3. Public Property AllContacts() As ObservableCollection(Of ContactViewModel)
      4. Get
      5. Return _allContacts
      6. End Get
      7. Set(ByVal value As ObservableCollection(Of ContactViewModel))
      8. _allContacts = value
      9. End Set
      10. End Property
      11. End Class


      OK, aber wir müssen die WPF für Binding wieder Benachrichtigen. Die Basisklasse für unsere ViewModels können wir bei einem Window aber nicht erben, wir müssen hier also INotifyPropertyChanged implementieren.

      VB.NET-Quellcode

      1. Class MainWindow
      2. Implements INotifyPropertyChanged
      3. Private _allContacts As ObservableCollection(Of ContactViewModel)
      4. Public Property AllContacts() As ObservableCollection(Of ContactViewModel)
      5. Get
      6. Return _allContacts
      7. End Get
      8. Set(ByVal value As ObservableCollection(Of ContactViewModel))
      9. _allContacts = value
      10. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(AllContacts)))
      11. End Set
      12. End Property
      13. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      14. End Class


      Nun Abonnieren wir das Loaded Event des Window um zum einen ein paar Testdaten zu erstellen und zum anderen die View an die CodeBehind zu Binden.

      VB.NET-Quellcode

      1. Class MainWindow
      2. Implements INotifyPropertyChanged
      3. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      4. AllContacts = New ObservableCollection(Of ContactViewModel)
      5. For i As Integer = 0 To 4
      6. AllContacts.Add(New ContactViewModel(New Model.Contact() With {.FirstName = "Sascha" & i, .LastName = "Patschka",
      7. .Birthday = New Date(1983, 9, 12), .Note = "Eine Notiz", .ContactData = New List(Of Model.ContactData) From {
      8. New Model.ContactData() With {.Data = "+43 664 1234567", .DataType = Model.EnumContactDataType.Phonenumber}}}))
      9. Next
      10. Me.DataContext = Me
      11. End Sub
      12. Private _allContacts As ObservableCollection(Of ContactViewModel)
      13. Public Property AllContacts() As ObservableCollection(Of ContactViewModel)
      14. Get
      15. Return _allContacts
      16. End Get
      17. Set(ByVal value As ObservableCollection(Of ContactViewModel))
      18. _allContacts = value
      19. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(AllContacts)))
      20. End Set
      21. End Property
      22. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      23. End Class


      Wir füllen also AllContacts mit Testdaten in einer Schleife. Nun können wir versuchen diese Testdaten mal in einem View anzuzeigen.

      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:_4_2_PhoneBook"
      7. mc:Ignorable="d"
      8. Title="MainWindow" Height="450" Width="800">
      9. <Grid>
      10. <Grid.RowDefinitions>
      11. <RowDefinition Height="47*"/>
      12. <RowDefinition Height="372*"/>
      13. </Grid.RowDefinitions>
      14. <ItemsControl ItemsSource="{Binding AllContacts}" Grid.Row="1">
      15. <ItemsControl.ItemTemplate>
      16. <DataTemplate>
      17. <TextBlock Text="{Binding FullName}"/>
      18. </DataTemplate>
      19. </ItemsControl.ItemTemplate>
      20. </ItemsControl>
      21. </Grid>
      22. </Window>


      Na das sieht doch gut aus. Nicht von der Optik her, sondern das wir korrekt gebunden haben und unser Code bis hierher erstmal funktioniert.





      Die Solution und das PDF sind im Anhang. Kleine Anmerkung zum PDF: Um dieses kleiner zu halten habe ich nun mit Teil 2 angefangen. Das Inhaltsverzeichnis ist gleich aber der Inhalt fängt bei Teil 2 nun erst mit Kapitel 4 an.




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

      4.3 - Die erste View besser Strukturieren

      4.3

      Das erste View ein bischen besser Strukturieren



      Nun werden wir ein wenig Ordnung in den XAML reinbringen. Ich habe abseites des Tutorials ein wenig die Optik der kleinen App verbessert, nichts besonderes aber ein wenig XAML ist entstanden. Gewisse Dinge wurden in Resourcen ausgelagert und eingebunden aber auch DataTemplates wurden erstellt.

      Diesen XAML Code werden wir besser Strukturieren indem wir UserControls erstellen um den Code auszulagern. Ziel ist es in der MailWindow etwas Odnung zu schaffen.

      Da dies nun nicht wirklich Codearbeit ist oder mit Binding sehr viel zu tun hat ist dies hier in Textform etwas schwer zu zeigen weshalb ich mich für dieses Kapitel auf das Video beschränken werde. Ich hoffe ich könnt mir das verzeihen.





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

      4.4 - Filtermöglichkeit einbinden (oder doch mehr?)

      4.4

      Filtermöglichkeit einbinden



      Wir werden nun eine Filtermöglichkeit für die Kontakte einbinden. Der Benutzer soll ja schließlich nach einem Kontakt suchen können. Ideal wäre hier wenn der Benutzer nach dem Nachnamen, dem Vornamen oder den Informationen in den Notizen suchen könnte.

      Genau das implementieren wir nun. Wir haben ja bereits in Kapitel 4.1.4.6 gelernt das uns hier die ICollectionView zur verfügung steht um zu Filtern und zu Sortieren.
      Wir werden also unsere MainWindow-CodeBehind anpassen und so umbauen das wir eine ICollectionView haben auf welche wir Binden können.

      Als erstes erstellen wir uns diese und geben unsere bereits vorhandene ObservableCollection als Datenquelle an.

      VB.NET-Quellcode

      1. AllContactsView = CollectionViewSource.GetDefaultView(AllContacts)
      2. AllContactsView.Filter = AddressOf Contacts_Filter


      Wir haben nun auch gleich eine Filtermethode angegeben wie in Kapitel 4.1.4.6 ja bereits gelernt.

      In dieser Methode welche ein Boolean als Rückgabewert besitzt, soll nun für jedes Objekt True zurückgeben in welchem der Name oder die Notiz den eingegebenen Filtertext enthält. Aber hierfür benötigt es noch ein Property für den Filterstring. An diese Eigenschaft binden wir in unserem UserControl uclMainHeader die dafür vorgesehene Textbox. Wir dürfen aber nicht vergessen im Setter dann die CollectionView zu aktualisieren.

      VB.NET-Quellcode

      1. Private _filterString As String = ""
      2. Public Property FilterString() As String
      3. Get
      4. Return _filterString
      5. End Get
      6. Set(ByVal value As String)
      7. _filterString = value
      8. AllContactsView.Refresh()
      9. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(FilterString)))
      10. End Set
      11. End Property


      XML-Quellcode

      1. TextBox x:Name="txtSearch" Text="{Binding FilterString,UpdateSourceTrigger=PropertyChanged}"
      2. FontSize="{StaticResource DefaultFontSize}" VerticalContentAlignment="Center" BorderThickness="0"/>


      Wir ihr sehen könnt habe ich zusätzlich beim Binding den UpdateSourceTrigger auf PropertyChanged gesetzt damit im Code bei jedem Tastenanschlag der Setter durchlaufen wird und wir darauf reagieren können. So ist die Suche für den User weitaus angenehmer.

      Nun fehlt uns nur noch die Methode für den eigentlichen Filter zu erstellen.

      VB.NET-Quellcode

      1. Private Function Contacts_Filter(obj As Object) As Boolean
      2. Dim c As ContactViewModel = CType(obj, ContactViewModel)
      3. Return c.FullName.ToLower.Contains(FilterString.ToLower()) Or c.Note.ToLower.Contains(FilterString.ToLower())
      4. End Function


      Nichts wildes, aber effektiv. Jetzt noch das Binding des ItemsControl anpassen da wir nun nicht mehr an die ObservableCollection sondern an unsere neue CollectionView binden möchten.

      XML-Quellcode

      1. <ItemsControl ItemsSource="{Binding AllContactsView}" Grid.Row="1">


      Starten wir nun die App können wir bereits Filtern wie es uns gefällt.



      Was wir auch noch möchten ist, mit dem X das Suchfeld zu leeren. OK, wir müssen also im Grunde nur die Eigenschaft FilterString auf einen Leerstring setzen. Gut. Reagieren wir mal auf einen Klick auf das X. In unserem UserControl haben wir ein ContentControl. Wenn wir dieses Klicken soll der Text in der Textbox geleert werden. Wer schon ein paar Dinge mit der WPF gemacht hat wird bereits wissen das wenn wir nun auf das ContentControl welches als Content einen Path besitzt einen Event verarbeiten wollen ist das unschön. Warum? Der User müsste wirklich genau auf das X klicken. Weil nur wenn die Maus genau über einem Path ist – also genau nur im schwarzen Teil – wird das Event verarbeitet.

      Das ist von der WPF aber so gewollt und ist bei einige Szenarien auch recht wichtig. In unserem Fall möchten wir das in diesem Beispiel aber nicht weshalb wir uns einen Border um das ContentContol machen und dessen Background explizit auf Transparent setzen. Tun wir das nicht ist der Border erst gar nicht klickbar. In dem Border können wir nun das MouserLeftButtonDown Event abonnieren und mit F12 in die CodeBehind des UserControls springen.

      Unser DockPanel sieht nun wie folgt aus:

      XML-Quellcode

      1. <DockPanel LastChildFill="True">
      2. <ContentControl Margin="5" DockPanel.Dock="Left"
      3. Content="{StaticResource IconMagnify}"/>
      4. <Border DockPanel.Dock="Right" MouseLeftButtonDown="Cross_MouseLeftButtonDown" Background="Transparent">
      5. <ContentControl Margin="5" Content="{StaticResource IconCross}"/>
      6. </Border>
      7. <TextBox x:Name="txtSearch"
      8. Text="{Binding FilterString,UpdateSourceTrigger=PropertyChanged}"
      9. FontSize="{StaticResource DefaultFontSize}" VerticalContentAlignment="Center" BorderThickness="0"/>
      10. </DockPanel>


      In unserer CodeBehind haben wir nun folgende Prozedur:

      VB.NET-Quellcode

      1. Private Sub Cross_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs)
      2. End Sub


      So. Und was jetzt? Wir sind ja in einem UserControl. Nicht in dem MainWindow. Haben wir uns nun ein Bein gestellt indem wir den XAML ausgelagert haben? Wir können nun die Eigenschaft FilterString in der CodeBehind der MainWindow ja gar nicht setzen?
      OK, dann geben wir der TextBox einen Namen und setzen die Text-Eigenschaft dieser TextBox einfach auf einen Nullstring.
      Halt, Stopp.
      Nein, wir wollen mit Binding arbeiten. Also fangen wir uns das erst gar nicht an.
      Wir wissen dass dieses UserControl auf jeden Fall als Datenkontext eine MainWindow-Instanz besitzt. Also werden wir uns diese Instanz holen. Und schon haben wir Zugriff.

      VB.NET-Quellcode

      1. Dim mw As MainWindow = CType(Me.DataContext, MainWindow)
      2. mw.FilterString = ""


      Nun funktioniert bereits das Filtern und das leeren des Filters. Super. Aber wäre es nicht toll nun noch eine Gruppierung zu haben. Bei Handys ist es ja so dass man immer beim Scrollen den Anfangsbuchstaben sieht bei welchem man gerade ist. Also wenn man gerade bei allen Namen welche mit B beginnen vorbei ist das dann eine Art Überschrift mit einem C kommt. Moment, dafür bietet sich ja eine Gruppierung in Verbindung mit einem Group-Style für das ItemsControl an.

      Gut, legen wir uns einen Style an:

      XML-Quellcode

      1. <ItemsControl ItemsSource="{Binding AllContacts}" Grid.Row="1">
      2. <ItemsControl.ItemTemplate>
      3. <DataTemplate>
      4. <local:uclContactListItemTemplate ContactClicked="ContactItem_Click"/>
      5. </DataTemplate>
      6. </ItemsControl.ItemTemplate>
      7. <ItemsControl.GroupStyle>
      8. <GroupStyle>
      9. <GroupStyle.ContainerStyle>
      10. <Style TargetType="GroupItem">
      11. <Setter Property="Template">
      12. <Setter.Value>
      13. <ControlTemplate TargetType="GroupItem">
      14. <GroupBox Header="{Binding Name}" >
      15. <ItemsPresenter />
      16. </GroupBox>
      17. </ControlTemplate>
      18. </Setter.Value>
      19. </Setter>
      20. </Style>
      21. </GroupStyle.ContainerStyle>
      22. </GroupStyle>
      23. </ItemsControl.GroupStyle>
      24. </ItemsControl>


      Jetzt nur noch wie wir es gelernt haben eine GroupDescription mit einer PropertyGroupDescription für unsere CollectionView erstellen und schon haben wir Gruppiert.
      Aber für welche Eigenschaft? Wir haben keine Eigenschaft zum Gruppieren nach Anfangsbuchstaben. Wir erstellen uns also eine schreibgeschützte Eigenschaft in unserem ContactViewModel.

      VB.NET-Quellcode

      1. Public ReadOnly Property FirstLastNameLetter As Char
      2. Get
      3. Return FullName.First()
      4. End Get
      5. End Property


      Da die WPF aber auch für schreibgeschützte Eigenschaften über Änderungen benachrichtigt werden will tun wir das in der Eigenschaft FullName.

      VB.NET-Quellcode

      1. Public Property FullName() As String
      2. Get
      3. Return $"{_modelContact.FirstName} {_modelContact.LastName}"
      4. End Get
      5. Set(ByVal value As String)
      6. Dim fullname = value.Split(CChar(" "))
      7. _modelContact.FirstName = fullname.First
      8. If fullname.Length > 1 Then _modelContact.LastName = fullname.Last
      9. RaisePropertyChanged() : RaisePropertyChanged(NameOf(FirstLastNameLetter))
      10. End Set
      11. End Property


      Gut, jetzt können wir unsere PropertyGroupDescription in der MainWindow.vb einfügen.
      Die komplette MainWindow_Loaded sieht nun wie folgt aus:

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AllContacts = New ObservableCollection(Of ContactViewModel)
      3. Dim dp As New ContactsDataProvider()
      4. dp.GetAllContacts(True).ToList.ForEach((Sub(x) AllContacts.Add(New ContactViewModel(x))))
      5. AllContactsView = CollectionViewSource.GetDefaultView(AllContacts)
      6. AllContactsView.Filter = AddressOf Contacts_Filter
      7. AllContactsView.GroupDescriptions.Add(New PropertyGroupDescription("FirstLastNameLetter"))
      8. SelectedContact = AllContacts.FirstOrDefault()
      9. Me.DataContext = Me
      10. End Sub


      Das Ergebnis kann sich durchaus sehen lassen.



      Natürlich zeige ich all das auch wieder im Video wobei ich hier noch auf ein paar kleine Details mehr eingehe. Viel Spaß damit.





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

      4.5 Einträge bearbeiten und speichern

      4.5

      Einträge bearbeiten und speichern



      In diesem Kapitel möchten wir Einträge bearbeiten und speichern. Dies ist ein übliches Scenario in einer jeden CRUD Anwendung.
      Ich entscheide mich in diesem Fall Daten in einem Dialog zu bearbeiten.

      Info: Im Video spreche ich davon das wir die Funktion Einträge bearbeiten zu können im nächsten Video noch ausbauen, ich habe mich aber dazu entschieden bereits etwas früher zum Thema MVVM zu springen. Dies wurde von einigen auch so gewünscht.

      Erst benötigen wir in unserem MainWindow einen Button welcher dann Dialog dann öffnet.
      Diesen definiere ich innerhalb der Grids und vergebe auch einen Handler „btnEditContact“ für das Click-Event:

      XML-Quellcode

      1. <Button Content="Eintrag bearbeiten" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5" Padding="5" Click="btnEditContact"/>


      VB.NET-Quellcode

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


      Wir erinnern uns dass wir in der MainWindow_Loaded Methode sowohl alle Variablen und Eigenschaften initialisieren als auch die Daten laden. Den Code zum Laden der Daten müssen wir in eine neue Methode auslagern, denn wir möchten ja später sicher nach dem speichern der Daten auch diese wieder neu in die Liste laden, das geht zwar natürlich auch nur mit diesem einen Eintrag, aber der Einfachheit halber laden wir in diesem Beispiel einfach alles neu in die Liste.

      Tipp: Wie man in Visual Studio sehr einfach über die Refactoring-Funktion Code auslagert seht ihr in dem Video zu diesem Kapitel.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. AllContacts = New ObservableCollection(Of ContactViewModel)
      3. LoadData()
      4. Me.DataContext = Me
      5. End Sub
      6. Private Sub LoadData()
      7. _allContactsModels = dp.GetAllContacts(True).ToList()
      8. _allContactsModels.ForEach((Sub(x) AllContacts.Add(New ContactViewModel(x))))
      9. AllContactsView = CollectionViewSource.GetDefaultView(AllContacts)
      10. AllContactsView.Filter = AddressOf Contacts_Filter
      11. AllContactsView.GroupDescriptions.Add(New PropertyGroupDescription("FirstLastNameLetter"))
      12. SelectedContact = AllContacts.FirstOrDefault()
      13. End Sub


      Gut, in unserem Handler welcher den „Bearbeiten-Dialog“ öffnen soll müssen wir nun einen Dialog öffnen. Den bauen wir uns schnell mal.
      Ich baue gerne UserControls anstatt Fenster da ich damit flexibler bin wenn ich eine Komponente mal austauschen möchte.

      XML-Quellcode

      1. <UserControl x:Class="uclEditContact"
      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:_4_PhoneBook"
      7. mc:Ignorable="d" Background="WhiteSmoke"
      8. d:DesignHeight="450" d:DesignWidth="800" Width="400">
      9. <Grid Margin="10">
      10. <Grid.ColumnDefinitions>
      11. <ColumnDefinition Width="Auto"/>
      12. <ColumnDefinition Width="*"/>
      13. </Grid.ColumnDefinitions>
      14. <Grid.RowDefinitions>
      15. <RowDefinition Height="50"/>
      16. <RowDefinition Height="Auto"/>
      17. <RowDefinition Height="Auto"/>
      18. <RowDefinition Height="Auto"/>
      19. <RowDefinition Height="Auto"/>
      20. <RowDefinition Height="Auto"/>
      21. <RowDefinition Height="20"/>
      22. <RowDefinition Height="30"/>
      23. </Grid.RowDefinitions>
      24. <Label FontSize="20" Grid.ColumnSpan="2">Eintrag bearbeiten</Label>
      25. <Label Grid.Row="1">Name</Label>
      26. <Label Grid.Row="2">Notiz</Label>
      27. <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FullName}"/>
      28. <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Note}"/>
      29. <Button Grid.Row="10" Grid.Column="1" Content="Speichern" Click="btnSaveClick"></Button>
      30. </Grid>
      31. </UserControl>


      Wie Ihr seht, ist in diesem Dialog nicht viel Spezielles enthalten. Zwei TextBoxen und zwei dazugehörige Labels sowie ein Speicher-Button. Die TextBoxen sind mittels Binding gebunden.
      Aber an was sich diese gebunden? Woher weis die WPF das dieser Dialog einen Telefonbucheintrag bearbeitet?

      Dazu kommen wir gleich, erstmal hauchen wir dem Speichern-Button leben ein indem wir für diesem einen Handler erstellen und folgendes in die Methode packen:

      VB.NET-Quellcode

      1. Private Sub btnSaveClick(sender As Object, e As RoutedEventArgs)
      2. Dim win = Window.GetWindow(Me)
      3. win.DialogResult = True
      4. win.Close()
      5. End Sub



      OK, ein wenig habe ich gelogen, es ist ja im Grunde gar kein Button welcher Speichert. Er schließt nur den Dialog, denn
      es ist einfacher wenn wir von „draußen“ speichern, also im MainWindow, dort haben wir all unsere Daten und können hier leichter speichern. Der Dialog soll und am besten ein DialogResult zurückgeben damit wir außen wissen ob der User auf Speichern geklickt hat oder ob er das Fenster (z.b. mit dem X) geschlossen hat.

      Das machen wir mittels DialogResult.

      Da es sich hier um ein UserControl handelt und nicht um ein Fenster können wir das Fenster in welchem es sich später befinden wird nicht einfach so aufrufen und dieses schließen.
      Mit Window.GetWindow(Me) bekommen wir eine Methode des Frameworks zur Hand welches uns die Instanz des Fensters zurückgibt in welchem sich das Objekt befindet welcher wir unser UserControl als Parameter übergeben.

      Wir setzen dann nur den DialogResult und schließen das Fenster.


      Aber wie kommt nun die Bindung zustande?
      Das machen wir im selben Schritt in welchem wir auch das Fenster öffnen lassen.
      Und zwar in unserer Methode btnEditContact in unserem MainWindow-CodeBehind.

      VB.NET-Quellcode

      1. Private Sub btnEditContact(sender As Object, e As RoutedEventArgs)
      2. Dim dlgWin As New Window()
      3. dlgWin.SizeToContent = SizeToContent.WidthAndHeight
      4. dlgWin.Content = New uclEditContact()
      5. dlgWin.DataContext = SelectedContact
      6. If dlgWin.ShowDialog() Then
      7. dp.SaveContacts(_allContactsModels)
      8. Else
      9. AllContacts.Clear()
      10. LoadData()
      11. End If
      12. End Sub


      OK, was machen wir hier. Wir Instanziieren hier ein neues Window und setzen den Content. In diesem Fall unser soeben erstelltes UserControl. Den DatenContext des Window (welchen das UserControl wie wir ja wissen erbt) setzen wir auf den aktuell selektierten Eintrag unserer Auflistung. Durch die Bindung werden die Eingaben ja direkt Syncronisiert und wir müssen einfach nur die ganze Liste speichern falls der DialogResult = True ist. Falls dem nicht so ist leeren wir unsere Liste und laden diese neu von der Platte. Wie schon erwähnt ist das nicht der schönste Weg, aber für dieses Tutorial und um die Funktionsweise zu verstehen der einfachste, da wir uns mit komplizierten Lade und Speichermechanismen im Moment nicht aufhalten wollen. Das hält die Beispiele einfach und übersichtlich.

      Wenn wir die Anwendung nun starten haben wir die neuen Funktionalitäten zur Verfügung und können Telefonbucheinträge bearbeiten und speichern.

      Ich hoffe das war verständlich und wir haben nun genug Vorkenntnisse um zu einem viel spannenderen Thema kommen. Dem MVVM Pattern.

      Natürlich zeige ich all das auch wieder im Video wobei ich hier noch auf ein paar kleine Details mehr eingehe. Viel Spaß damit.





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

      5.1 - Was ist das MVVM Pattern

      5.1

      Was ist das MVVM Pattern!?




      Von vielen schon lange gewünscht kommen wir nun zum richtig spannenden Teil, dem MVVM Muster. Dieses Muster ist ein Pattern welches die Trennung zwischen Logik und UI ermöglicht wodurch viele Vorteile erlangt werden können, aber wie immer wenn etwas Vorteile hat, hat es auch Nachteile. Auch diese werde ich aufzeigen.

      Aber was ist das MVVM Pattern genau? Im Video erkläre ich es genauer und erläutere einige Textpassagen des dazugehörigen Wikipedia Artikeln zum MVVM Pattern.

      Kurz und knapp die Vorteile und Nachteile von welchen ich denke das sie für die meisten relevant sein dürften.

      Vorteile:
      • Die Geschäftslogik kann unabhängig von der Darstellung bearbeitet werden
      • Die Testbarkeit verbessert sich, da die ViewModel die UI-Logiken enthalten und unabhängig von der View instanziiert werden können Views können von reinen UI-Designern gestaltet werden, während Entwickler unabhängig davon die Models und ViewModels implementieren
      Nachteile:
      • Für kleinere Projekte eher „overkill“
      • Für Anfänger eher nicht geeignet da es schnell komplex wirken kann


      Wie Datenbindung an sich und wie diese im MVVM Pattern funktioniert ist auch im verlinkten Wiki Artikel enthalten, ich gehe aber auch auf diesen Part im Video genauer ein, sorry das ich das hier nicht nochmals aufschreibe, ich denke was die Theorie angeht gibt es ja genug Artikel im Netz. Dennoch – im Video gibt’s ein paar Zusatzinfos.





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

      5.2 - Wann MVVM und wann nicht

      5.2

      Wann MVVM und wann nicht?



      Das ist eine Frage die nicht so leicht zu beantworten ist. Zum einen sollte der eigene Wissensstand auf einem gewissen Level sein. Doch auf welchem.

      Für MVVM ist einiges notwendig, hierfür sollten Dinge wie Vererbung, Interfaces, generische Klassen und XAML Binding schon recht gut sitzen. Ist dies nicht der Fall hält man sich gut und gerne Wochenlang auf wenn man auf ein Problem stößt. Denn die sind Techniken welche immer wieder zum Einsatz kommen und sollten schon sitzen.

      Aber nicht nur das. Immer wieder lese ich in Foren dass man unter WPF unbedingt mit dem MVVM Pattern arbeiten muss. Muss? Why?

      Das kann ich so nicht unterschreiben. Es kommt immer auf das Projekt an. Habe ich nur vor ein kleines „Helferlein“ zu proggen und ich weis im Vorfeld das wird nur eine kleine App mit ein bis zwei Funktionen und evtl. zwei bis drei Fenstern, warum soll ich mir dann diesen Overhead antun?


      Solche Anwendungen sind sicher nicht so komplex das ich von den Vorteilen des Patterns profitieren würde, und die Testbarkeit ist bei solchen Apps auch irrelevant.

      Habe ich allerdings vor eine App zu schreiben welche evtl. in Zukunft wächst, wo sich Abhängigkeiten in Zukunft ändern, wo ich im Vorfeld weis das die Anwendung größer wird und die Komplexität immer weiter steigen wird, dann macht MVVM durchaus Sinn da uns das Pattern einfach in gewisser Weise auch zwingt eine gewisse Struktur einzuhalten und somit die Komplexität mit wachsender Anwendung immer weiter fällt.

      Das hört sich komisch an aber je größer die Anwendung, desto weniger komplex wird diese durch das Pattern.

      Anfangs wirkt eine Anwendung mit MVVM sehr wirr, sehr viele Projekte, sehr viele Klassen, Unmengen an Namespaces und alles befindet sich wo anders. Brainfuck?
      Zum Teil ja – Anfangs!

      Mit zunehmender Anzahl an Logikklassen und immer wachsender Anwendung freut man sich immer mehr über die Trennung und findet alles sofort, denn ohne diese „Ordnung“ hat man irgendwann keinen Überblick mehr. Zugegeben, hält man Ordnung ist alles gut, aber mal ehrlich, wer hält wirklich immer Ordnung, wer weis schon immer genau welcher Code und welche Logik wo genau ist? Bei MVVM stellt sich die Frage oft erst gar nicht.

      Wann und wo Ihr MVVM einsetzt entscheidet Ihr. Unsicher? Frag mich einfach, evtl. kann ich dir weiterhelfen.




      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“ ()

      5.3 Welchen Mehrwert kann ich aus dem Pattern gewinnen?

      5.3

      Welchen Mehrwert kann ich aus dem Pattern gewinnen?



      Ja, für viele stellt sich die Frage was man davon hat die durchaus vorhandenen Hürden auf sich zu nehmen und jede Einzelne versuchen zu meistern.
      Wenn man von den bereits angesprochenen Vorteilen im letzten Kapitel mal absieht stellt sich die Frage was man davon hat. Die wenigsten welche mit MVVM anfangen werden sicher gleich Unittests schreiben, was ja eines der großen Vorteile sind wenn man das MVVM-Pattern korrekt anwendet.

      Was habe ich noch davon?
      OK, Designtime-Support ist ja ganz nett und lustig, aber auch das wäre den Aufwand nicht Wert.
      Bessere und saubere Struktur bei größeren Projekten und den damit verbundenen geringerem Wartungsaufwand ist ein schon recht guter Grund, aber jetzt auch nicht der überhammer.
      Wie wäre es dann mit der Austauschbarkeit von Modulen? Also von Teilen wie z.b. der View welche man komplett tauschen könnte ohne etwas am Code ändern zu müssen? Wenn man dies nicht vor hat auch kein sonderlich guter Grund.
      Jaaa, aber ein Designer könnte die View ja auch unabhängig vom Entwickler erstellen, warten und testen. Gut, das betrifft nur Teams.
      OK, ich habe einen Grund. Weil alles davon reden und überall steht das man MVVM machen soll? Haha, sicher nicht.

      Aber was ist nun ein guter Grund? Die Antwort ist so schlicht. All diese kleinen Gründe zusammen ergeben einfach ein „Rundum Paket“ das es uns ermöglicht eine saubere, gut gewartete und getestete Anwendung sowohl als alleiniger Entwickler, als auch im Team zu erstellen. Und das ist es im Endeffekt was wir wollen.




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

      5.4 - MVVM und CodeBehind - verboten?

      Neu

      5.4

      MVVM und CodeBehind - Verboten?



      Das ist eine sehr stark diskutierte Frage und in diversen Foren ist immer die rede davon das bei MVVM CodeBehind verboten ist. Meine Meinung: Nein, das stimmt so nicht!

      Erstmal muss klar definiert werden um welchen Code es sich handelt. Es ist ganz klar, dass Code, welcher die Logik oder Daten betrifft nicht in die UI Schicht gehört. Aber Code welche rein die UI betrifft kann sehr wohl in die Code-Behind Datei. Denn Code, welcher die Logik betrifft ist somit nicht mehr Testbar und auch eine unabhängige Entwicklung wäre damit nur noch sehr schwer machbar. Betrifft der Code wirklich nur die UI oder deren Manipulation gilt dennoch zu beachten das, wenn eine View „ausgetauscht“ wird, dieser Code auch migriert werden muss. Demnach sollte man immer versuchen alternative Wege zu gehen und ggf. ein AttachedProperty für solch einen Fall zu schreiben, denn so kann die View einfachst ausgetauscht und wiederverwendet werden. In den meisten Fällen kann ein AttachedProperty oder Trigger schon helfen die Code-Behind Datei zu umgehen es gibt aber immer wieder Fälle, in denen das nicht möglich oder nur sehr umständlich möglich wäre.

      Exemplarisch sehen wir hier folgende Ausgangssituation:



      XML-Quellcode

      1. <Grid Margin="10">
      2. <Grid.ColumnDefinitions>
      3. <ColumnDefinition Width="Auto"/>
      4. <ColumnDefinition Width="*"/>
      5. </Grid.ColumnDefinitions>
      6. <Grid.RowDefinitions>
      7. <RowDefinition Height="10"/>
      8. <RowDefinition Height="Auto"/>
      9. <RowDefinition Height="Auto"/>
      10. <RowDefinition Height="Auto"/>
      11. <RowDefinition Height="*"/>
      12. </Grid.RowDefinitions>
      13. <GroupBox Header="Geschäftspartner-Typ" Grid.ColumnSpan="2" Grid.Row="1">
      14. <StackPanel Orientation="Horizontal">
      15. <RadioButton Name="radioPerson" Content="Person" Margin="5,2" Checked="radioChecked" IsChecked="True"/>
      16. <RadioButton Name="radioCompany" Content="Firma" Margin="5,2" Checked="radioChecked"/>
      17. <RadioButton Name="radioClub" Content="Verein" Margin="5,2" Checked="radioChecked"/>
      18. </StackPanel>
      19. </GroupBox>
      20. <Label Name="lblFirstName" Grid.Row="2">Vorname</Label>
      21. <Label Name="lblLastName" Grid.Row="3">Nachname</Label>
      22. <TextBox Name="txtFirstName" Grid.Row="2" Grid.Column="1" Text="{Binding FirstName}"/>
      23. <TextBox Name="txtLastName" Grid.Row="3" Grid.Column="1" Text="{Binding LastName}"/>
      24. </Grid>



      Angenommen wir möchten das ausschließlich bei einer Person beide Felder (Vor und Nachname) sichtbar sind, im Falle einer Firma jedoch nur ein Feld und die Beschriftung soll in diesem Fall auf „Name“ anstatt auf „Vorname“ geändert werden.
      Zudem soll die aktuelle Auswahl gespeichert werden, wählt der Benutzer also eine Firma soll in der Datenbank gespeichert werden das es sich um eine Firma handelt.

      Hier mal ein Beispiel was NICHT in die Code-Behind soll:

      VB.NET-Quellcode

      1. ​Private Sub radioChecked(sender As Object, e As RoutedEventArgs)
      2. If DirectCast(sender, RadioButton).Name = "radioPerson" Then
      3. Dim currentViewModel As PersonViewModel = Me.DataContext
      4. currentViewModel.BusinessPartnerType = BpType.Person
      5. End If
      6. End Sub



      Hier ein Beispiel was gerne in die Code-Behind darf:

      VB.NET-Quellcode

      1. ​Private Sub radioChecked(sender As Object, e As RoutedEventArgs)
      2. If txtFirstName IsNot Nothing AndAlso lblFirstName IsNot Nothing Then
      3. Dim personSelected = DirectCast(sender, RadioButton).Name = "radioPerson"
      4. txtFirstName.Visibility = If(personSelected, Visibility.Visible, Visibility.Collapsed)
      5. lblFirstName.Visibility = txtFirstName.Visibility
      6. lblFirstName.Content = If(personSelected, "Vorname", "Name")
      7. End If
      8. End Sub


      Beim ersten Beispiel haben wir die Daten verändert.
      Das ist Aufgabe des ViewModels, ob dieses weiter delegiert zu einer Businesslogik, einem Repository oder ob die Logik im Model-Layer ist, ist egal, wichtig ist das es nicht über die View passiert.

      Beim zweiten Beispiel betrifft der Code rein die View, es werden nur Controls Manipuliert, hätten die Textboxen oder die Radiobuttons nun ein Binding auf eine ViewModel Klasse hätte dies keinerlei Auswirkungen auf die Daten. Wir setzen lediglich die Visibility von Controls bzw. die Beschriftung eines Labels.

      Ich hoffe es wir so etwas klarer was quasi mehr oder weniger „erlaubt“ ist. Natürlich gibt es auch genug Ansätze gewisse Daten an von der View aus zu manipulieren, damit nimmt man sich allerdings gewisse Vorteile und eben die lose Kopplung, mal ganz abgesehen davon das diese Logik nicht mehr testbar ist.





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

      5.5 - Model - View - ViewModel | War`s das?

      Neu

      5.5

      Model - View - ViewModel | War`s das?



      Jetzt haben wir ja verinnerlicht das wir hier drei Layer haben welche entkoppelt sind und mehr oder weniger unabhängig sind. Wir wissen dass unsere Logik im ViewModel abläuft. Aber ist das gut?

      In kleineren Projekten ist dies noch recht übersichtlich, das MVVM Pattern gibt und aber durch diese Layertrennung auch die Möglichkeit das wir hier einfach noch einen Layer vor das ViewModel schieben wie z.b. eine Businesslogic. Also ein Layer welcher sich um die Logik kümmert, so müssen wir im ViewModel alles nur noch so aufbereiten wie wir es für die Anzeige benötigen. Das macht das ganze bei größeren Projekten nochmals um einiges übersichtlicher.

      Lasst uns das Anhand eines Beispiels durchgehen.

      Mal angenommen wir möchten dem User die Möglichkeit geben das dieser das Passwort ändert. Natürlich geben wir gewisse Richtlinien vor wie z.b. das ein Kennwort eine Mindestlänge besitzen muss, das gleiche Kennwort nicht nochmals verwendet werden darf, evtl. dürfen die letzten X Kennwörter nicht verwendet werden, es muss x Sonderzeichnen enthalten, darf Vor- und Zunahme nicht enthalten usw.

      Hier gibt es sehr viele Möglichkeiten. Solche eine Logik kann schnell mal komplex werden.

      Hier mal ein kleines Beispiel mit einem Pseudocode:

      VB.NET-Quellcode

      1. Public Function ChangePassword(currentUser As User, oldPassword As String, newPassword As String)
      2. Dim errorMessage As String = Nothing
      3. 'Altes Passwort korrekt?
      4. If CryptPassword(oldPassword) <> db.Users.Where(Function(x) x.Username = currentUser.Username).Password Then
      5. errorMessage = "Das alte Passwort war nicht korrekt!"
      6. Return errorMessage
      7. End If
      8. 'Neues Passwort gleich wie altes
      9. If CryptPassword(newPassword) <> CryptPassword(oldPassword) Then
      10. errorMessage = "Das alte Passwort darf nicht gleich wie das neue sein"
      11. Return errorMessage
      12. End If
      13. 'Wieviele der letzten Passwörter dürfen laut Richtlinie nicht verwendet werden
      14. Dim numberOfVorbittenPasswordsFromHistory As Integer = CInt(settings.GetSettingValue("NumberVorbittenPasswords").Value)
      15. Dim vorbittenPasswords As List(Of String) = db.Users.Where(Function(x) x.Username = currentUser.Username).Passwordhistory.OrderByDescending(Function(x) x.CreationTimestamp).Take(10).Select(Function(x) x.Password).Tolist()
      16. If vorbittenPasswords.Any(Function(x) CryptPassword(newPassword)) Then
      17. errorMessage = $"Das neue Passwort darf nicht gleich mit einem der letzten {numberOfVorbittenPasswordsFromHistory} Passwörtern sein."
      18. Return errorMessage
      19. End If
      20. 'Muss passwort Sonderzeichen enthalten laut Richtlinie
      21. Dim passwordMustHaveSpecialCharacter As Boolean = CBool(settings.GetSettingValue("NumberVorbittenPasswords").Value)
      22. If passwordMustHaveSpecialCharacter AndAlso Not ContainsSpecialChars(newPassword) Then
      23. errorMessage = "Das Passwort entspricht nicht den Richtlinien das ein Passwort Sonderzeichen enthalten muss"
      24. Return errorMessage
      25. End If
      26. 'Mindestpasswortlänge
      27. Dim minimumPasswortLength As Integer = CInt(settings.GetSettingValue("MinimumPasswortLength").Value)
      28. If newPassword.Length < minimumPasswortLength Then
      29. errorMessage = $"Das neue Passwort muss mindestens {minimumPasswortLength} Zeichen lang sein"
      30. Return errorMessage
      31. End If
      32. 'Muss Mindestens x Buchstaben enthalten
      33. 'Muss Mindestens x Ziffern enthalten
      34. 'Darf nicht gleich dem Usernamen sein
      35. 'Darf keine Wörter der Blacklist enthalten (Firmennamen, Vornamen, Nachname, Wörterbuchvergleich usw.)
      36. '....
      37. '....
      38. Return errorMessage
      39. End Function


      Packen wir dies alles in unsere ViewModel-Klasse wird schnell ungemütlich. OK, jetzt würden wir Anfangen das wir eine Helper-Klasse erstellen usw.

      Was aber wenn wir einen eigenen Layer hätten in dem wir das alles haben? Wäre doch klasse.Kein Problem, geht ganz Easy und werden wir in späteren Kapiteln auch machen, ich möchte nur das ich jetzt schon wisst das ihr nicht alle in die ViewModel-Klassen packen müsst.

      Wer Lust hat kann sich das WPFNote2 Projekt von mir ansehen. In diesem habe ich unter anderem eine Businesslogik implementiert.


      Im Video gehe ich auch noch etwas tiefer.




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