Tutorialreihe <WPF lernen/>

    • WPF

    Es gibt 53 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?

      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?

      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.



      Edit: Im Pseudocode hatte sich ein kleiner Fehler eingeschlichen welchen ich hier korrigiert habe, im PDF aber nicht. Bitte um nachsicht. Ist ja nur Pseudocode.

      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.6 - Wir erstellen eine MVVM-Projektmappe unter Visual Studio

      5.6

      Wir erstellen eine MVVM-Projektmappe unter Visual Studio



      Wir erstellen nun eine Projektmappe unter Visual Studio welche „MVVM Gerecht“ sein soll.
      Das bedeutet das alle Layer (Model – View – ViewModel) in getrennte Assemblys verteilt werden.

      Warum dies wichtig ist habe ich zwar bereits erwähnt, gehe aber gerne nochmals darauf ein.
      Da es wichtig ist das wir uns keine Abhängigkeiten in unser ViewModel holen, da wir dieses später ja evtl. testen möchten, ist es wichtig dass wir dies nicht irgendwann unabsichtlich tun. Ist unser Projekt einfach eine WPF Anwendung und wir erstellen lediglich einfach unterschiedliche Namespaces für die einzelnen „Layer“ ist dies nicht gewährleistet.
      In einer Projektmappe mit mehreren getrennten Projekten kann uns das nicht mehr so leicht passieren da wir hierfür explizit einen Verweis auf ein Projekt setzen müssten. Spätestens an diesem Punkt müsste uns unser Fehler dann auffallen. Aber auch für die widerverwendbarkeit, denn das Model oder gar das ViewModel können wir sicher in einer anderen Anwendung evtl. auch benötigen, insofern ist die Trennung auch für solch einen Fall ganz Praktisch.

      Erstmal erstelle ich immer eine leere Projektmappe welcher ich den Namen des Projekts gebe, in diesem Fall ist dies „WpfPhoneBook“.



      Wir suchen die Projektvorlage „Leere Projektmappe“ im Assistenten für neue Projekte.



      Benennen diese Projektmappe nun „WpfPhoneBook“, wählen einen Speicherort und bestätigen die Angaben.



      Daraufhin erhalten wie eine leere Projektmappe in welche sich keine Dateien befinden. Diese dient nur dafür verschiedene Typen von Projekten zu beherbergen. Und genau das wollen wir ja. Wir erstellen also auch gleich das erste Projekt und fangen mit der App selbst an…



      Dieses Projekt ist vom Typ WPF .NET Core Anwendung.
      Achtung, erstellt ihr die Anwendung unter .Net Core/.NET 5 müssen alle später folgenden Projekte unserer MVVM Anwendung auch .Net Core verwenden und nicht .Net Framework. Das ist wichtig, man „verklickt“ sich hier leicht!

      Im folgenden Bild habe ich euch dies nochmals extra markiert…



      Diese Anwendung nennen wir nun WpfPhoneBook.App und wählen im nächsten Schritt .Net 5 als Zielplattform aus.

      Wurde uns dieses Projekt zur Projektmappe hinzugefügt können wir sogleich das nächste Projekt erstellen.
      Die View.

      Diese ist von Typ „Bibliothek für WPF-Benutzersteuerelementen“. Auch wieder darauf zu achten das die Vorlage für .Net Core ist. Wir nennen diese „WPFPhoneBook.View“.

      Als nächstes erstellen wir ein neues Projekt mit der Vorlage „Klassenbibliothek“. Dieses Projekt wird unser ViewModel.
      Wir nennen dieses also „WpfPhoneBook.ViewModel".

      ACHTUNG: Keine WPF Klassenbibliothek, eine normale Klassenbibliothek. Die WPF Klassenbibliothek bringt uns Abhängigkeiten mit die wir nicht haben möchten.


      Zu guter Letzt benötigen wir noch ein Projekt für unser Model. Auch dies wird eine normale Klassenbibliothek für .Net 5. Ihr habt es erraten, wir nennen es „WpfPhoneBook.Model“. In den Klassenbibliotheken können wir nun die erzeugten Class1.vb Dateien löschen, diese benötigen wir nicht. Im View-Projekt können wir das per Default erzeugte UserControl1.xaml löschen.

      Nun müssen wir nur noch die Projektverweise unter den einzelnen Projekten herstellen…



      Wie man Projektverweise herstellt solle man denke ich wissen wenn man sich an MVVM heranwagt, hier die Auflistung welches Projekt auf welches einen Verweis benötigt:
      • Model -> benötigt keinen Verweis auf ein Projekt
      • ViewModel -> Model
      • View -> ViewModel
      • App -> Model, ViewModel, View
      Das war es dann schon mal fürs erste, im nächsten Video erstellen wir dann die ersten Infrastruktur-Files.

      Im Video könnte ihr alles Schritt für Schritt sehen. Das PDF für den e-Book Reader habe ich euch mit reingepackt und auch die Solution ist wieder mal mit dabei.



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

      6.1 - Das Model erstellen

      6.1

      Das Model erstellen



      Erstellen wir nun unsere Models. Zuerst empfiehlt es sich eine Basisklasse für das Model zu erstellen wovon dann alle Models erben. Somit ist jede Property automatisch in jedem Model verfügbar. Dort können wir dann zum Beispiel die Property ID anlegen welche uns dann eine eindeutige ID des Datensatzes zurückgibt. Noch ein paar weitere Propertys und unsere Basisklasse sieht so aus.

      VB.NET-Quellcode

      1. Public MustInherit Class ModelBase
      2. Public Property ID As Guid = Guid.NewGuid()
      3. Public Property CreatedAt = DateTime.Now()
      4. Public Property Deleted As Boolean
      5. Public Property DeletedAt As DateTime?
      6. End Class


      Nun erstellen wir die Klasse Contact und lassen diese von der eben erstellten Basisklasse erben. Fügen wir nun ein paar Propertys hinzu die ein
      Contact“ haben soll.

      VB.NET-Quellcode

      1. Public Class Contact
      2. Inherits ModelBase
      3. Public Property Firstname As String
      4. Public Property Lastname As String
      5. Public Property ImagePath As String
      6. Public Property Birthday As Date?
      7. Public Property Note As String
      8. Public Property ContactData As List(Of ContactData)
      9. End Class


      ContactData beinhaltet eine Auflistung von „ContactData“. Somit kann ein Contact mehrere Kontaktdaten (ContactData) haben. Also legen wir noch die Klasse „ContactData“ an.

      VB.NET-Quellcode

      1. Public Class ContactData
      2. Inherits ModelBase
      3. Public Property Data As String
      4. Public Property ContactType As PersonContactType = PersonContactType.Phonenumber
      5. End Class
      6. Public Enum PersonContactType
      7. Phonenumber = 0
      8. Mail = 1
      9. SocialMedia = 2
      10. Fax = 3
      11. Webvsite = 4
      12. End Enum


      PersonContactType beschreibt den Typen von ContactData. Das kann eine Telefonnummer, eine Mailadresse oder was auch immer sein. In Data steht dann der entsprechende Inhalt, die Telefonnummer, Mail etc. Somit wäre unser Datenmodel für unsere App fertig.

      Im Video könnte ihr alles Schritt für Schritt sehen.





      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 3 mal editiert, zuletzt von „Nofear23m“ ()

      6.2 - Der Core

      6.2

      Der Core



      Im letzten Kapitel haben wir unsere Models erstellt und damit quasi auch das Datenmodel unserer Anwendung.

      Kommen wir nun zu den Viewmodels:
      Fangen wir wieder mit einer Basisklasse an, von welcher dann jedes ViewModel erben sollte.

      Als erstes benötigen wir INotifyPropertyChanged, welches wichtig für das Binding ist.

      Es sorgt dafür, dass die View benachrichtigt wird, wenn sich im ViewModel eine Eigenschaft geändert hat.

      Hierfür implementieren wir INotifyPropertyChanged und setzen noch den
      benötigten Import für den Namespace.

      Wenn wir nun die Schnittstelle implementieren, wird uns von VisualStudio ein PropertyChangedEventHandler erstellt.

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. Imports System.Runtime.CompilerServices
      3. Public Class ViewModelBase
      4. Implements INotifyPropertyChanged
      5. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      6. End Class


      Da es nicht schön und viel Tipparbeit ist, das Event bei jeder Eigenschaft auszulösen, erstellen wir uns eine Methode, die uns das Ganze etwas vereinfacht.
      OK, erstellen wir nun die Methode RaisePropertyChanged und übergeben den Propertynamen als String.
      Innerhalb der Methode lösen wir nun das Event RaisePropertyChanged aus und übergeben als Sender uns selbst, als EventArg ein neues PropertyChangedEventArgs und übergeben diesem den Propertynamen.

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. Imports System.Runtime.CompilerServices
      3. Public Class ViewModelBase
      4. Implements INotifyPropertyChanged
      5. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      6. Public Overridable Sub RaisePropertyChanged(prop As String = "")
      7. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      8. End Sub
      9. End Class


      Erstellen wir uns testweise ein Viewmodel und nennen es MainViewModel (werden wir später auch benötigen), lassen es von ViewModelBase erben und erstellen darin ein Property.
      Name und Type sind erst mal nicht wichtig. Nun können wir im Setter der Propertys RaisePropertyChanged() aufrufen und müssen den Propertynamen angeben.
      Da es als String deklariert ist haben wir zwei Möglichkeiten: entweder, wir schreiben den Namen direkt als String „Test“ , oder wir nutzen den nameOf() Ausdruck, welcher uns den Namen einer Variable, eines Typs oder eines Members als Zeichenkette zurückgibt.

      VB.NET-Quellcode

      1. Private _test As String
      2. Public Property Test() As String
      3. Get
      4. Return _test
      5. End Get
      6. Set(ByVal value As String)
      7. _test = value
      8. RaisePropertyChanged("Test")
      9. RaisePropertyChanged(NameOf(Test))
      10. End Set
      11. End Property


      Wir könnten das nun so lassen, aber wir wollen es etwas verbessern.
      Als erstes wird unsere ViewModelBase Klasse als MustInherit deklariert.

      Somit ist schon mal sichergestellt, dass diese Klasse als Basisklasse dient und wir keine Instanz davon erstellen können.

      Als nächstes ändern wir den Übergabeparameter.
      Seit .Net Framework 4.5 haben wir die Möglichkeit den <CallerMemberName> anzugeben.

      <CallerMemberName> ermöglicht das Abrufen des Methoden oder Eigenschaftsnamens des Methodenaufrufers.
      Somit können wir z.B. im Setter einer Property einfach RaisePropertyChanged() schreiben.

      Der Compiler übergibt dann in unserem Fall den Eigenschaftsnamen des Aufrufers und das ist der Name unserer Property.

      Nun müssen wir den vorherigen Übergabeparmeter als Optional angeben und ihm einen expliziten Standardwert zuweisen.
      Den Standardwert setzen wir auf Nothing.

      VB.NET-Quellcode

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


      Viele fragen jetzt noch ab, ob der übergebene Parameter auch wirklich was enthält und werfen dann das Event.
      Das kann man sich sparen.

      Wenn nichts (Nothing) oder String.Empty übergeben wird, werden alle Properties der Klasse aktualisiert.
      Das ist manchmal ganz hilfreich, wenn man eine ganze View aktualiseren möchte.

      So braucht man nicht extra eine Methode „RefreshAll“ schreiben, wo dann zehn oder mehr RaisePropertyChanged() mit dem jeweiligen Propertynamen stehen.

      Einfach ein RaisePropertyChanged(nothing) oder RaisePropertyChanged(„“) aufrufen und alle Properties der Klasse werden aktualisiert.

      Gut, das wäre bisher unsere Basisklasse.

      Es sei nochmal gesagt das alles, was die Viewodels benötigen, hier rein gehört. Da liegt es an jedem selbst was er braucht und in die Basisklasse packt.

      Nehmen wir an, wir laden Daten aus einer Datenbank, wo wir nicht sicher sind, wie lange der Vorgang dauert, weil es größere Mengen an Daten sind, die geladen werden sollen. Man könnte da z.B. einen Waitingindicator einbauen, indem man in der Basisklasse eine Property IsBusy() implementiert.

      VB.NET-Quellcode

      1. ​Imports System.ComponentModel
      2. Imports System.Runtime.CompilerServices
      3. Public MustInherit Class ViewModelBase
      4. Implements INotifyPropertyChanged
      5. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
      6. Public Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional prop As String = "")
      7. If prop IsNot Nothing Then RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
      8. End Sub
      9. Private _isBusy As Boolean
      10. Public Property IsBusy() As Boolean
      11. Get
      12. Return _isBusy
      13. End Get
      14. Set(ByVal value As Boolean)
      15. _isBusy = value
      16. RaisePropertyChanged()
      17. End Set
      18. End Property
      19. End Class


      In jedem ViewModel, welches von der Basisklasse erbt, ist dies dann verfügbar und man kann z.b. folgendes schreiben.


      VB.NET-Quellcode

      1. ​Public Class MainViewModel
      2. Inherits ViewModelBase
      3. Private test As String
      4. Public Property Test() As String
      5. Get
      6. Return Test
      7. End Get
      8. Set(ByVal value As String)
      9. test = value
      10. RaisePropertyChanged("")
      11. End Set
      12. End Property
      13. Public Sub New()
      14. IsBusy = True
      15. 'Do Something
      16. IsBusy = False
      17. End Sub
      18. End Class


      Somit wäre die Basisklasse erst mal fertig.

      Nun stellen sich viele die Frage, was denn nun alles zum Kern gehört.
      Im Endeffekt muss jeder selbst wissen, was er benötigt.
      In unserem Beispiel nehmen wir mal einen ContactDataManager.

      Wir müssen die Daten ja irgendwie verwalten. Da wir noch nichts von der Festplatte laden wollen, oder anderweitig Serialisieren wollen, halten wir die Klasse ganz einfach.
      Wir erstellen uns im ViewModel-Projekt einen neuen Ordner und nennen diesen DataManager. Darin erstellen wir und eine Klasse und nennen diese ContactDataManager.

      In unserem Fall reicht es uns wenn wir uns ein Property vom Typ List<T> erstellen und es AllContacts nennen.
      Im Konstruktor initialisieren wir nun diese Liste und fügen ihr ein oder zwei Contacts hinzu.

      Im Grunde ist das schon unsere ganze Infrastruktur die wir benötigen.
      Beim nächsten mal reden wir dann darüber, wie man z.b Messageboxen oder Dialoge öffnet.

      Alles was mit der View zu tun hat oder UI ist und im Viewmodel gesteuert werden muss, muss abstrahiert werden, denn das Viewmodel soll für sich alleine stehen und soll nichts von der View wissen.

      Wenn wir beispielsweise versuchen im Konstruktor eine Messagebox wie gewohnt via Messagebox.Show() aufzurufen, bekommen wir von VisualStudio nichts angeboten, was in die Klammern soll.
      Was uns VisualStudio allerdings anbietet ist ein Import.
      Entweder auf PresentationFramework oder WinForms. Letzteres fällt schon mal ganz raus. =)

      Das PresentationFramework wäre eine Option, sie würde funktionieren und wäre nicht gänzlich falsch. Damit haben wir später aber dennoch ein Problem.
      Das MVVM Pattern sieht vor das Layer strickt getrennt sind, was auch jetzt noch so wäre.
      Das Problem bekommen wir bei den Unittests, dazu kommt noch ein eigenes Kapitel.

      Durch die Messagebox wäre das Viewmodel nicht mehr testbar. Unittests laufen voll automatisiert ab, probiert ob der Code läuft und ob am Ende das rauskommt was auch rauskommen soll. Das würde bedeuten, wir müssten jedes-mal die Messagebox anklicken und wüssten im Zweifel nicht, was wir anklicken sollen, je nach dem welcher Test gerade läuft. Aus diesem Grund müssen Dinge wie Messageboxen, Dialoge etc abstrahiert werden.

      Dazu aber dann wirklich mehr im eigenen Kapitel.





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

      6.3 - Die RelayCommand Klasse

      6.3

      Die RelayCommand-Klasse




      Bevor wir nun zu den Services kommen, womit wir dann letztendlich die Messageboxen, Dialoge etc steuern, möchte ich noch einmal auf die RelayCommand Klasse eingehen.
      Dazu erstellen wir uns eine Klasse im Viewmodelprojekt, benennen diese RelayCommand und fügen folgenden Code ein.


      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.Windows.Input
      2. ''' <summary>
      3. ''' Diese Klasse Implementiert das ICommand Interface, so muss man nicht in jeder Klasse eines ViewModel alles selbst implementieren.
      4. ''' Einfach eine Command wie folgt Instanzieren:
      5. ''' MyCommand = New RelayCommand(AddressOf MyCommand_Execute, AddressOf MyCommand_CanExecute)
      6. ''' </summary>
      7. Public Class RelayCommand : Implements ICommand
      8. #Region " Fields "
      9. ReadOnly _execute As Action(Of Object)
      10. ReadOnly _canExecute As Predicate(Of Object)
      11. #End Region
      12. #Region " Constructors"
      13. ''' <summary>
      14. ''' Erstellt einen neuen Command welcher NUR Executed werden kann.
      15. ''' </summary>
      16. ''' <param name="execute">The execution logic.</param>
      17. ''' <remarks></remarks>
      18. Public Sub New(execute As Action(Of Object))
      19. Me.New(execute, Nothing)
      20. End Sub
      21. ''' <summary>
      22. ''' Erstellt einen neuen Command welcher sowohl die Execute als auch die CanExecute Logik beinhaltet.
      23. ''' </summary>
      24. ''' <param name="execute">Die Logik für Execute.</param>
      25. ''' <param name="canExecute">Die Logik für CanExecute.</param>
      26. ''' <remarks></remarks>
      27. Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
      28. If execute Is Nothing Then
      29. Throw New ArgumentNullException("execute")
      30. End If
      31. _execute = execute
      32. _canExecute = canExecute
      33. End Sub
      34. #End Region
      35. #Region " ICommand Members "
      36. ''' <summary>
      37. ''' Setzt die CanExecute-Methode des ICommand-Interfaces auf True oder False
      38. ''' </summary>
      39. ''' <param name="parameter"></param>
      40. ''' <returns>Gibt zurück ob die Aktion ausgeführt werden kann oder nicht</returns>
      41. ''' <remarks>
      42. ''' Benutzt DebuggerStepThrough from System.Diagnostics
      43. ''' Der Debugger überspringt diese Prozedur also, es sei den es wird explizit ein Haltepunkt gesetzt.
      44. ''' </remarks>
      45. <DebuggerStepThrough>
      46. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
      47. Return _canExecute Is Nothing OrElse _canExecute(parameter)
      48. End Function
      49. ''' <summary>
      50. ''' Event welches geworfen wird wenn die Propertie CanExecuteChanged sich ändert.
      51. ''' </summary>
      52. ''' <remarks></remarks>
      53. Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
      54. AddHandler(value As EventHandler)
      55. If _canExecute IsNot Nothing Then
      56. AddHandler CommandManager.RequerySuggested, value
      57. End If
      58. End AddHandler
      59. RemoveHandler(value As EventHandler)
      60. If _canExecute IsNot Nothing Then
      61. RemoveHandler CommandManager.RequerySuggested, value
      62. End If
      63. End RemoveHandler
      64. RaiseEvent(sender As Object, e As EventArgs)
      65. End RaiseEvent
      66. End Event
      67. ''' <summary>
      68. ''' Führt die Prozedur Execute des ICommand.Execute aus
      69. ''' </summary>
      70. ''' <param name="parameter"></param>
      71. ''' <remarks></remarks>
      72. Public Sub Execute(parameter As Object) Implements ICommand.Execute
      73. _execute(parameter)
      74. End Sub
      75. #End Region
      76. End Class


      Ich werde an dieser stelle nicht weiter auf den Code eingehen, was die Klasse macht und welche Aufgabe sie hat haben wir schon in einem vorherigen Kapitel besprochen.
      Schaut dazu bitte im Kapitel 2.1.8.7 nach.
      Nachdem wir den Code eingefügt haben wird uns der CommandManager rot unterstrichen.
      VisualStudio schlägt uns den Import auf System.Windows.Input vor.
      Hier muss man beachten dass Klassenbibliotheken die in .NET5 .NET6 oder .NET Core Projekten erstellt werden Cross-Platformfähig sind. Da System.Windos.Input aus PresentationCore nur unter Windows zur verfügung steht wäre die .dll nicht mehr Cross-Platformfähig. Sollen die Viewmodels noch in anderen Anwendungen benutzt werden muss man sich um eine andere Implementierung kümmern.
      Der Import wird nun aber noch nicht funktionieren.
      Schauen wir uns dazu die Projektdatei an.

      XML-Quellcode

      1. <Project Sdk="Microsoft.NET.Sdk">
      2. <PropertyGroup>
      3. <RootNamespace>WpfPhoneBook.ViewModel</RootNamespace>
      4. <TargetFramework>net5.0</TargetFramework>
      5. </PropertyGroup>
      6. <ItemGroup>
      7. <ProjectReference Include="..\WphPhoneBook.Model\WpfPhoneBook.Model.vbproj" />
      8. </ItemGroup>
      9. </Project>


      Wie wir sehen ist dass TargetFramework .NET5.0
      Bedeutet es hat nichts mit Windows zu tun und kennt daher auch die Windows spezifischen Namespaces nicht.
      Hier müssen wir nun angeben das es sich um eine Windows App handelt, zusätzlich noch das wir WPF nutzen.

      XML-Quellcode

      1. <Project Sdk="Microsoft.NET.Sdk">
      2. <PropertyGroup>
      3. <RootNamespace>WpfPhoneBook.ViewModel</RootNamespace>
      4. <TargetFramework>net5.0-windows</TargetFramework>
      5. <UseWPF>true</UseWPF>
      6. </PropertyGroup>
      7. <ItemGroup>
      8. <ProjectReference Include="..\WphPhoneBook.Model\WpfPhoneBook.Model.vbproj" />
      9. </ItemGroup>
      10. </Project>


      Gehen wir nun in die RelayCommandKlasse und speichern diese ab erkennt der Compiler das wir System.Windows.Input zur Verfügung haben, damit dann auch den CommandManager.




      Hier das PDF für Leute mit EBook: 6_3_Die RelayCommand Klasse.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:
      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“ ()

      6.4 - Services

      6.4

      Services




      Diesmal geht es darum wie wir Messageboxen, Dialoge etc. aus den Viewmodels heraus steuern können. Also alles was die View betrifft, im MVVM Pattern kennen die Viewmodels die View ja nicht.

      Beispielsweise kann man in einen Viewmodel nicht einfach eine Messagebox aufrufen. Die Intellisense bietet es auch gar nicht an. Wenn wir es ausschreiben würden, wird uns ein Import von System.Windows angeboten. Damit würde es dann funktionieren aber wir wollen die Layer ja trennen, und die Testbarkeit wäre damit auch nicht mehr gegeben.

      Wie gehen wir nun vor?
      Wenn wir eine Messagebox aufrufen wollen muss das von einem Ort aus passieren der die Messagebox kennt. Bedeutet wir müssen Abstrahieren.
      Abstrahierung funktioniert eigentlich immer mit Hilfe von Interfaces.
      Ich nenne alles was ich in die Applikation hineinreiche gerne Service.

      Fangen wir mit etwas einfachem an und nehmen zu beginn eine Inputbox.

      Erstellen wir uns im Viewmodel Projekt einen neuen Ordner und benennen diesen Services.
      Innerhalb dieses Ordners erstellen wir ein Interface und benennen dieses IInputBoxService.
      Hier empfiehlt es sich einen eigenen Namespace zu erstellen, so hat man alles was mit Services zu tun hat beisammen.

      VB.NET-Quellcode

      1. Namespace Services
      2. Public Interface IInputBoxService
      3. End Interface
      4. End Namespace



      Um zu sehen was eine Inputbox überhaupt kann schauen wir uns mal die MSDN Seite dazu an.

      Hier sehen wir nun das die Inputbox einen prompt und einen title als Parameter hat und einen string zurück gibt. Optional kann man auch noch die Position angeben, einen vordefinierten Text anzeigen lassen etc. Dies benötigen wir erst mal nicht.

      Wir wollen die Inputbox aufrufen, ihr 2 Parameter übergeben und sie soll einen string zurück geben.

      VB.NET-Quellcode

      1. Namespace Services
      2. Public Interface IInputBoxService
      3. Function Show(prompt As String, title As String) As String
      4. End Interface
      5. End Namespace



      Somit ist unser Interface bereits fertig. Mehr benötigen wir hier nicht.
      Nun müssen wir das Interface auch mit leben füllen. Dies passiert im .App Projekt denn dort haben wir vollen zugriff auf Messageboxen, Dialoge etc.

      Hierzu legen wir auch im .App Projekt einen neuen Ordner an und benennen diesen Services.
      Darin erstellen wir eine Klasse, benennen diese InputBoxService, Implementieren das Interface und importieren den benötigten Namespace. Jetzt noch die Schnittstelle importieren und wir haben folgendes Gerüst.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel.Services
      2. Public Class InputBoxService
      3. Implements IInputBoxService
      4. Public Function Show(prompt As String, title As String) As String Implements IInputBoxService.Show
      5. Throw New NotImplementedException()
      6. End Function
      7. End Class



      Wir wollen aber den string der InputBox zurückgeben. Also returnen wir die InputBox und übergeben die beiden Parameter.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel.Services
      2. Public Class InputBoxService
      3. Implements IInputBoxService
      4. Public Function Show(prompt As String, title As String) As String Implements IInputBoxService.Show
      5. Return InputBox(prompt, Title:=title)
      6. End Function
      7. End Class



      Das wäre auch schon die ganze Implementierung. Mehr benötigen wir an dieser Stelle nicht.
      Jetzt haben wir die Implementierung unserer Services, müssen diese aber noch „anmelden“.
      Wir können die Services aus den Viewmodels heraus nicht nutzen denn das Viewmodel Projekt hat ja keinen Verweis auf das .App Projekt.

      Wie lösen wir das nun?

      Wir bauen uns einen Container. In diesen werden Instanzen von Services hineingereicht. Diese Instanzen können wir dann im Viewmodel aufrufen ohne das wir etwas davon wissen müssen.
      Wir kennen im Viewmodel ja nur das Interface.
      Erstellen wir im ViewModel Projekt innerhalb des Ordners Services eine Klasse, benennen diese ServiceContainer und fügen folgenden Code ein.

      VB.NET-Quellcode

      1. Namespace Services
      2. Public Class ServiceContainer
      3. Public Shared ReadOnly Instance As New ServiceContainer()
      4. Shared Sub New()
      5. ServiceMap = New Dictionary(Of Type, Object)()
      6. ServiceMapLock = New Object()
      7. End Sub
      8. Public Sub AddService(Of TServiceContract As Class)(implementation As TServiceContract)
      9. SyncLock ServiceMapLock
      10. ServiceMap(GetType(TServiceContract)) = implementation
      11. End SyncLock
      12. End Sub
      13. Public Shared Function GetService(Of TServiceContract As Class)() As TServiceContract
      14. Dim service As Object = Nothing
      15. SyncLock ServiceMapLock
      16. ServiceMap.TryGetValue(GetType(TServiceContract), service)
      17. End SyncLock
      18. Return TryCast(service, TServiceContract)
      19. End Function
      20. Shared ReadOnly ServiceMap As Dictionary(Of Type, Object)
      21. Shared ReadOnly ServiceMapLock As Object
      22. End Class
      23. End Namespace



      Für den Code ist wichtig zu wissen das es Dictionary(of Type, Object)() gibt.
      Man kann hier Objekte hinzufügen, gibt den Typ an und das Object ist quasi die Instanz davon, die man dem Container hinzufügen möchte. Dafür gibt es die Methode AddService(), mit GetService() kann man einen gewissen Typ abrufen.

      Somit wäre auch dieser Teil fertig.

      Gehen wir nun in das .App Projekt, öffnen die Application.vb und generieren uns mal das Startup Ereignis.

      VB.NET-Quellcode

      1. Class Application
      2. Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
      3. End Sub
      4. ' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
      5. ' can be handled in this file.
      6. End Class


      Zum Start der App wollen wir unsere Services anmelden. Dazu benötigen wir den zuvor erstellten ServiceContainer und können dann folgendes schreiben.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel
      2. Imports WpfPhoneBook.ViewModel.Services
      3. Class Application
      4. Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
      5. ServiceContainer.Instance.AddService(Of IInputBoxService)(New InputBoxService())
      6. End Sub
      7. ' Application-level events, such as Startup, Exit, and DispatcherUnhandledException
      8. ' can be handled in this file.
      9. End Class



      Wir haben den ServiceContainer, fügen dessen Instanz mit AddService() einen Service hinzu, geben den Typ an, in unserem Fall IInputboxService, und übergeben mit New InputboxService() die Instanz.
      Somit ist unser Service fertig und wir können ihn ausprobieren.
      Dazu gehen wir in das MainViewModel und schreiben im Konstruktor folgenden Code.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel.Services
      2. Public Class MainViewModel
      3. Inherits ViewModelBase
      4. Public Sub New()
      5. Dimib = ServiceContainer.GetService(Of IInputBoxService)
      6. Debug.WriteLine(ib.Show("Hallo Welt", "Test"))
      7. End Sub
      8. End Class


      Wir holen uns über den ServiceContainer, mit GetService() den Service und geben an welchen Typ der Service haben soll. Wir rufen die Inputbox auf und lassen uns den zurückgegebenen string in der Ausgabe anzeigen.
      Nun müssen wir nur noch das MainViewModel instanziieren. Am einfachsten machen wir das im Applikation_Startup Ereignis.

      VB.NET-Quellcode

      1. Dim test As NewMainViewModel


      Damit ist alles fertig und mit F5 können wir die Applikation starten.
      Die Inputbox erscheint mit unserem Titel und Text. Schreiben wir etwas in die Eingabezeile und bestätigen mit Ok, erscheint der eingegebene Text in der Ausgabe.

      Erstellen wir noch einen weiteren Service, einen den eigentlich fast jeder benötigt, die Messagebox.
      Wir erstellen also im Projekt ViewModel innerhalb des Ordners Services ein weiteres Interface und benennen dies IMessageboxService.
      Schauen wir zunächst wieder auf der MSDN Seite nach wie eine Messagebox aufgebaut ist.
      Wir wissen ja was eine Messagebox an Parameter hat. Title, Message, Icon und Buttons.
      Aber wie sind die Buttons und Icons implementiert?

      Suchen wir uns auf der MSDN Seite mal den Konstruktor raus der alle 4 Parameter hat und klicken dort auf „MessageBoxButtons“ um zur Implementierung zu gelangen. Jetzt müssen wir leider feststellen das die Buttons vom Type „System.Windows.Forms.MessageBoxButtons“ sind. Selbes beim Icon und MessageBoxResult. Das würde bedeuten das wir uns die View ins Viewmodel holen. Aber genau das wollen wir nicht.

      Wie umgehen wie das?
      Wir müssen wieder Abstrahieren.

      Am einfachsten geht das wenn wir uns Testweise in der Codebehind des MainWindows eine Messagebox erstellen.

      VB.NET-Quellcode

      1. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
      2. MessageBox.Show("test", "Test", MessageBoxButton.OK, MessageBoxImage.Warning)
      3. End
      4. Sub


      Wenn wir jetzt den Cursor in „MessageBoxButton“ setzen und F12 drücken bekommen wir die Definition angezeigt und können sehen wie es im .Net Framework implementiert ist.

      VB.NET-Quellcode

      1. Namespace System.Windows
      2. Public Enum MessageBoxButton
      3. OK = 0
      4. OKCancel = 1
      5. YesNoCancel = 3
      6. YesNo = 4
      7. End Enum
      8. End Namespace



      Wenn wir nun eine Kopie des Enumerator erstellen haben wir keinerlei Probleme bei der Übergabe.
      Bedeutet wir Kopieren den Enumerator und fügen ihn in unser Interface. Selbiges machen wir auch mit dem Icon und dem MessageBoxResult. Definition ansehen und ins Interface kopieren.

      VB.NET-Quellcode

      1. Namespace Services
      2. Public Interface IMessageboxService
      3. End
      4. Interface
      5. Public Enum MessageBoxButton
      6. OK= 0
      7. OKCancel= 1
      8. YesNoCancel= 3
      9. YesNo= 4
      10. EndEnum
      11. Public Enum MessageBoxImage
      12. None= 0
      13. [Error]= 16
      14. Question= 32
      15. Warning= 48
      16. Information= 64
      17. End Enum
      18. Public Enum MessageBoxResult
      19. None= 0
      20. OK= 1
      21. Cancel= 2
      22. Yes= 6
      23. No= 7
      24. EndEnum
      25. EndNamespace


      Nun können wir anfangen und den Konstruktor erstellen.

      VB.NET-Quellcode

      1. Namespace Services
      2. Public Interface IMessageboxService
      3. Function Show(title As String, message As String, button As MessageBoxButton, icon As MessageBoxImage) As MessageBoxResult
      4. End Interface
      5. Public Enum MessageBoxButton
      6. OK = 0
      7. OKCancel= 1
      8. YesNoCancel= 3
      9. YesNo= 4
      10. End Enum
      11. Public Enum MessageBoxImage
      12. None= 0
      13. [Error]= 16
      14. Question= 32
      15. Warning= 48
      16. Information= 64
      17. End Enum
      18. Public Enum MessageBoxResult
      19. None = 0
      20. OK = 1
      21. Cancel= 2
      22. Yes= 6
      23. No= 7
      24. End Enum
      25. End Namespace


      Die Messagebox im CodeBehind kann nun gelöscht werden, diese benötigen wir nicht mehr.
      Kommen wir jetzt zur Implementierung des Interfaces.

      Im .App Projekt innerhalb des Ordners Services erstellen wir eine Klasse, benennen diese MessageboxService und implementieren das IMessageboxService Interface und lassen die Schnittstelle implementieren.

      Nun haben wir nichts weiter zu tun als die Messagebox aufzurufen und die Parameter zu übergeben.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel.Services
      2. Public Class MessageBoxService
      3. Implements IMessageboxService
      4. Public Function Show(title As String, message As String, button As MessageBoxButton, icon As MessageBoxImage) As MessageBoxResult Implements IMessageboxService.Show
      5. Return MessageBox.Show(message, title, button, icon)
      6. End Function
      7. End Class


      Damit wäre dann auch die Implementierung soweit fertig.
      Der Service muss nun noch bei der App angemeldet werden.
      Das machen wir wieder in der Application.vb.

      VB.NET-Quellcode

      1. ServiceContainer.Instance.AddService(Of IMessageboxService)(New MessageBoxService())


      Erstellen wir uns noch ein UserControl im View Projekt welches den Button beinhaltet. Den DataContext des UserControls setzen wir auf das MainViewModel und Binden den Button an ein Command.

      XML-Quellcode

      1. <UserControl
      2. x:Class="uclTestMessagebox"
      3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      6. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      7. xmlns:local="clr-namespace:WpfPhoneBook.View"
      8. xmlns:viewmodel="clr-namespace:WpfPhoneBook.ViewModel;assembly=WpfPhoneBook.ViewModel"
      9. mc:Ignorable="d"
      10. d:DesignHeight="450"
      11. d:DesignWidth="800">
      12. <UserControl.DataContext>
      13. <viewmodel:MainViewModel/>
      14. </UserControl.DataContext>
      15. <StackPanel Margin="30">
      16. <Button Content="Show Messagebox over Service" Command="{Binding ShowBoxCommand}"/>
      17. </StackPanel>
      18. </UserControl>


      Jetzt benötigen wir noch das Command im MainViewModel.
      Den Aufruf der Inputbox im MainViewModel können wir auskommentieren.

      VB.NET-Quellcode

      1. Imports System.Windows.Input
      2. Imports WpfPhoneBook.ViewModel.Services
      3. Public Class MainViewModel
      4. Inherits ViewModelBase
      5. Public Sub New()
      6. _showBoxCommand = New RelayCommand(AddressOf ShowBoxCommand_Execute)
      7. 'Dim ib = ServiceContainer.GetService(Of IInputBoxService)
      8. 'Debug.WriteLine(ib.Show("Hallo Welt", "Test"))
      9. End Sub
      10. Private _showBoxCommand As ICommand
      11. Public ReadOnly Property ShowBoxCommand() As ICommand
      12. Get
      13. Return
      14. _showBoxCommand
      15. End Get
      16. End Property
      17. Private Sub ShowBoxCommand_Execute(obj As Object)
      18. ServiceContainer.GetService(Of IMessageboxService).Show("Test", "Das ist ein Test", MessageBoxButton.YesNo, MessageBoxImage.Warning)
      19. End Sub
      20. End Class


      Wir holen uns wie im vorherigen Beispiel den ServiceContainer, den Service und geben den Typ an und können wirket danach .Show aufrufen. Dies ist jetzt die Variante in einer Zeile. Bei der Inputbox hatten wir es in 2 Zeilen. Jeder wie er es mag.

      Da unser UserControl bisher noch nicht zugeordnet ist, setzen wir das nun in unser MainWindow.

      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:view="clr-namespace:WpfPhoneBook.View;assembly=WpfPhoneBook.View"
      7. xmlns:local="clr-namespace:WpfPhoneBook.App"
      8. mc:Ignorable="d"
      9. Title="MainWindow"
      10. Height="450"
      11. Width="800">
      12. <Grid>
      13. <view:uclTestMessagebox/>
      14. </Grid>
      15. </Window>


      Testen wir nun die App mit klick auf F5 und betätigen den Button erscheint unsere Messagebox.
      Alles so wie wir es möchten.
      Allerdings gibt es noch einen kleinen Haken.
      Wir haben in den Projekten OptionStrict noch nicht auf ON gestellt.
      :(

      Das holen wir jetzt nach und schauen mal was passiert.
      Also, alle Projekte auf OptionStrict On stellen.

      Hier bekommen wir jetzt einen Fehler angezeigt der sagt das die Typen der MessageBoxButtons, MessageBoxIcons und des MessageBoxResults nicht übereinstimmen.
      Das ist auch richtig, wir haben uns ja eine Kopie der Enumerator erstellt. Diese sind vom Typ ViewModel.Services.MessageBoxButton und nicht vom Typ Sytem.Windows.MessageBoxButton.
      Da wir eine Kopie der Enumerator erstellt haben sind die Werte identisch und wir haben es einfach zu konvertieren.

      VB.NET-Quellcode

      1. Imports WpfPhoneBook.ViewModel.Services
      2. Public Class MessageBoxService
      3. Implements IMessageboxService
      4. Public Function Show(title As String, message As String, button As MessageBoxButton, icon As MessageBoxImage) As MessageBoxResult Implements IMessageboxService.Show
      5. Return DirectCast(MessageBox.Show(message, title, DirectCast(button, Windows.MessageBoxButton), DirectCast(icon, Windows.MessageBoxImage)), MessageBoxResult)
      6. End Function
      7. End Class


      Nochmal mit F5 starten und schauen ob alles funktioniert.

      Damit ist das Service dann auch erfolgreich implementiert. :)

      Gehen wir noch das Handling der Rückgabewertes durch, wenn wir Ja oder Nein abfragen möchten.

      Dazu gehen wir in die Execute Methode des Commands.

      VB.NET-Quellcode

      1. Private Sub ShowBoxCommand_Execute(obj As Object)
      2. If ServiceContainer.GetService(Of IMessageboxService).Show("Test", "Das ist ein Test", MessageBoxButton.YesNo, MessageBoxImage.Warning) =MessageBoxResult.Yes Then
      3. Debug.WriteLine("ER HAT JA GEKLICKT!!!!")
      4. End If
      5. End Sub


      Das wars schon. Wir können die MessageBox nun wie gewohnt nutzen, müssen es nur vorher aus dem Service herausholen.



      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: 6_4_WpfPhoneBook.zip
      Und wie immer auch das PDF für euer E-Book:
      6_4_Services.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. ##