WPF Fehler Binding, Textbox - ModeTwoWay

  • WPF

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

    WPF Fehler Binding, Textbox - ModeTwoWay

    Hallo,

    ich habe einen Fehler in einem Bindung und kann mir das nicht so recht Erklären, was ich hier falsch mache.
    Der Fehler ist:

    Quellcode

    1. System.Windows.Data Error: 39 : BindingExpression path error: 'Name' property not found on 'object' ''String' (HashCode=-324971015)'. BindingExpression:Path=Name; DataItem='String' (HashCode=-324971015); target element is 'TextBox' (Name='TB_Name'); target property is 'Text' (type 'String')


    Hier der Code dazu:

    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:BindigTest"
    7. mc:Ignorable="d"
    8. DataContext="Person"
    9. Title="MainWindow" Height="450" Width="800">
    10. <Grid>
    11. <Label x:Name="LB_Name" Content="Name" HorizontalAlignment="Left" Margin="85,30,0,0" VerticalAlignment="Top" />
    12. <TextBox x:Name="TB_Name" HorizontalAlignment="Left" Text="{Binding Name, Mode=TwoWay}" Height="23" Margin="132,34,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    13. <Label x:Name="LB_Age" Content="{Binding Age, Mode=OneWay}" HorizontalAlignment="Left" Margin="257,30,0,0" VerticalAlignment="Top" />
    14. <Label x:Name="LB_NewName" Content="NewName" HorizontalAlignment="Left" Margin="61,59,0,0" VerticalAlignment="Top" />
    15. <TextBox x:Name="TB_NewName" HorizontalAlignment="Left" Text="{Binding Name, Mode=TwoWay}" Height="23" Margin="132,62,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    16. <Button x:Name="BT_Change" Content="Change" HorizontalAlignment="Left" Margin="257,62,0,0" VerticalAlignment="Top" Width="75" />
    17. </Grid>
    18. </Window>


    VB.NET-Quellcode

    1. Class MainWindow
    2. Public PersonData As New Person With {
    3. .Age = 25,
    4. .Name = "John"
    5. }
    6. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    7. Me.DataContext = PersonData
    8. End Sub
    9. Public Class Person
    10. Public Property Name As String
    11. Public Property Age As Integer
    12. End Class
    13. Private Sub BT_Change_Click(sender As Object, e As RoutedEventArgs) Handles BT_Change.Click
    14. PersonData.Name = TB_NewName.Text
    15. End Sub
    16. End Class


    Ansich ist es funktionsfähig - aber ich fürchte die Fehler wirken sich später aus, weil halt doch etwas falsch ist. Auch im Editor wird mir der Teil "Mode=TwoWay" angezeigt mit dem Hinweis "für die Bindung wurde kein DataContext gefunden". Evtl kann man es auch besser machen?
    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:local="clr-namespace:ForumBindingTest"
    7. mc:Ignorable="d"
    8. DataContext="{Binding RelativeSource={RelativeSource Self}}"
    9. Title="MainWindow" Height="450" Width="800">
    10. <Grid>
    11. <Label x:Name="LB_Name" Content="Name" HorizontalAlignment="Left" Margin="85,30,0,0" VerticalAlignment="Top" />
    12. <TextBox x:Name="TB_Name" HorizontalAlignment="Left" Text="{Binding PersonData.Name, Mode=TwoWay}" Height="23" Margin="132,34,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    13. <Label x:Name="LB_Age" Content="{Binding PersonData.Age, Mode=OneWay}" HorizontalAlignment="Left" Margin="257,30,0,0" VerticalAlignment="Top" />
    14. <Label x:Name="LB_NewName" Content="NewName" HorizontalAlignment="Left" Margin="61,59,0,0" VerticalAlignment="Top" />
    15. <TextBox x:Name="TB_NewName" HorizontalAlignment="Left" Text="{Binding PersonData.Name, Mode=TwoWay}" Height="23" Margin="132,62,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    16. <Button x:Name="BT_Change" Content="Change" HorizontalAlignment="Left" Margin="257,62,0,0" VerticalAlignment="Top" Width="75" />
    17. </Grid>
    18. </Window>


    Mainwindow:

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Imports System.Runtime.CompilerServices
    3. Class MainWindow
    4. Implements INotifyPropertyChanged
    5. Private _personData As Person
    6. Public Property PersonData() As Person
    7. Get
    8. Return _personData
    9. End Get
    10. Set(ByVal value As Person)
    11. _personData = value
    12. RaisePropertyChanged()
    13. End Set
    14. End Property
    15. Public Class Person
    16. Public Property Name As String
    17. Public Property Age As Integer
    18. End Class
    19. Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    20. PersonData = New Person With {
    21. .Age = 25,
    22. .Name = "John"
    23. }
    24. End Sub
    25. Private Sub BT_Change_Click(sender As Object, e As RoutedEventArgs) Handles BT_Change.Click
    26. PersonData.Name = TB_NewName.Text
    27. End Sub
    28. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    29. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
    30. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    31. End Sub
    32. End Class


    Nachtrag:
    Ich sehe das du den selben Ansatz versuchst der mich Jahrelang in den Wahnsinn getrieben hat als ich Ihn noch genutzt habe, du versuchst Binding zu realisieren, aber Zeitgleich im Codebehind zu arbeiten, ich kann dir nur wärmstens die Tutorialreihe von @Nofear23m ans Herz legen, die WPF unterscheidet sich grundlegend von Forms, und wenn du alle vorzüge genießen möchtest, solltest du versuchen über MVVM zu arbeiten (Gut ich selber arbeite nicht 100%ig danach ich verwende VVM, da ich nicht extra ein Model sondern direkt nur mit Viewmodels arbeite)

    LG
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hallo,

    danke für die Antwort. Ich habe mir auch schon einige der Tuts angesehen und die Beiträge durchgelesen. Das war in der Tat hilfreich. Allerdings ist es für den Moment so, das ich mein Problem nur vereinfacht dargestellt habe - der eigentliche Code ist sehr groß und lässt sich in Kürze nicht so fundamental und komplett umstellen.
    Hm, wenn du magst kannst du dein Projekt ja mal anhängen, dann kan man sich das ansehen, für denn Fall das es dich interessiert habe ich dein Beispiel, mal auf VVM umgebaut und angehangen.

    LG
    Dateien
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hallo asusdk,

    also - vielen Dank für das Beispiel und die Zeit die Du Dir für mich genommen hast!
    Da werde ich noch etwas brauchen bis ich da durchgestiegen bin. Aber mir gefällt der Ansatz recht gut - vor allem bei großen Projekten scheint mir das extrem hilfreich zu sein.
    Den Code des Originalprogramm kann ich leider nicht posten - das gehört meiner Firma. Deshalb immer nur Beispiele...
    ja, hat bei mir auch ewig gedauert bis ich durchgestiegen bin und ich denke so wirklich durchgestiegen bin ich noch lange nicht ^^
    und ja vor allem bei größeren Projekten ist es hilfreich, wenn man eigentlich nur noch die Daten verarbeiten und/oder manipulieren muss, und dank Binding der rest automatisch abläuft. Das du den Code nicht posten kannst ist verständlich, da hat deine Firma sicher etwas dagegen ^^ War auch nur als Vorschlag gedacht, denn dank @Nofear23m und @ErfinderDesRades hab ich in letzter Zeit einige Fortschritte gemacht, die ich auch gerne weitergeben möchte =)

    LG
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hi,

    ja - ich freue mich auch jeden Tag etwas zu lernen und ich merke auch wie es richtig vorwärts geht - auch dank des Forums.
    Aber nochmal wegen der Ausgangsfrage - gibt es da vielleicht noch einen Tipp - wie ich zumindest das aktuelle Problem einfacher lösen kann ohne die Ganze Struktur auf ein neues Prinzip umzustellen oder werde ich doch alle Felder quasi manuell füllen müssen?

    LG
    Martin
    ein simples Ersetzen deines oben gewählten DataContextes durch

    XML-Quellcode

    1. DataContext="{Binding RelativeSource={RelativeSource Self}}"

    Reduziert die angezeigten Fehlermeldungen schon mal auf eine einzige, dass Alter betreffende


    Ok, das ist das einzige was mir für die nutzung hier einfallen würde:


    Window:

    VB.NET-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:ForumBindingTest"
    7. mc:Ignorable="d"
    8. DataContext="{Binding RelativeSource={RelativeSource Self}}"
    9. Title="MainWindow" Height="450" Width="800">
    10. <Grid>
    11. <Label x:Name="LB_Name" Content="Name" HorizontalAlignment="Left" Margin="85,30,0,0" VerticalAlignment="Top" />
    12. <TextBox x:Name="TB_Name" HorizontalAlignment="Left" Text="{Binding PersonData.Name, Mode=TwoWay}" Height="23" Margin="132,34,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    13. <Label x:Name="LB_Age" Content="{Binding PersonData.Age, Mode=OneWay}" HorizontalAlignment="Left" Margin="257,30,0,0" VerticalAlignment="Top" />
    14. <Label x:Name="LB_NewName" Content="NewName" HorizontalAlignment="Left" Margin="61,59,0,0" VerticalAlignment="Top" />
    15. <TextBox x:Name="TB_NewName" HorizontalAlignment="Left" Text="{Binding PersonData.Name, Mode=TwoWay}" Height="23" Margin="132,62,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" />
    16. <Button x:Name="BT_Change" Content="Change" HorizontalAlignment="Left" Margin="257,62,0,0" VerticalAlignment="Top" Width="75" />
    17. </Grid>
    18. </Window>


    Window-VB:

    VB.NET-Quellcode

    1. ​Class MainWindow
    2. Public Property PersonData As New Person With {
    3. .Age = 25,
    4. .Name = "John"
    5. }
    6. Public Class Person
    7. Public Property Name As String
    8. Public Property Age As Integer
    9. End Class
    10. Private Sub BT_Change_Click(sender As Object, e As RoutedEventArgs) Handles BT_Change.Click
    11. PersonData.Name = TB_NewName.Text
    12. End Sub
    13. End Class
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If

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

    Hallo

    Du bis echt gut, danke! Aber ich muss mich grade furchbar über mich selbst ärgern. Ich habe festgestllt, das mein eigentliches Problem nicht an der Bindung liegt, sondern mehr daran, das die Werte aus der Klasse nicht zurückgesynct werden. In meinem Beispiel mit zwei Feldern hat es - Zufällig funktioniert - mit den Testfeldern - also das die Eingabe im Feld NewName auch den Inhalt im Feld Name geändert hat. Wenn ich aber eine Änderung im Objekt selbst mache, dann wird das Form (in meinem Beispiel SamName) nicht aktualisiert.
    NoFear hatte in seinem Tutorial so etwas erwähnt und einen Implements IfNotifyPropertyChanged vorgenommen.
    Aber dann müsste ich ja quasi das Feld auch wieder manuell zurückschreiben...
    Aber das wäre ja totaler Quark.
    Dateien
    • BindigTest.zip

      (71,88 kB, 49 mal heruntergeladen, zuletzt: )
    jap, das war es was ich damit meinte das die WPF eben gänzlich anders funktioniert, es ist zwar möglich, aber völlig Sinnfrei, ein (ich vermute mal Forms ?) Projekt auf WPF umzubauen, und sich dann nicht an den gängigen Konventionen der WPF zu orientieren, das macht das ganze am Ende deutlich weniger angenehm, als sogar noch bei Forms zu bleiben, ich schau mir dein Anhängsel mal an um es möglichst ohne gröbere Anpassungen Binding-Ready zu machen
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hallo @mpmichael und ein herzliches Willkommen bei der WPF

    Wie @asusdk schon erwähnt passiert es vielen die ein WinForms Projekt zur WPF portieren wollen das Sie anfangen falsch mit der WPF zu Arbeiten.
    Ein Projekt zu portieren verführt eben dazu so viel Code zu verwenden wie nur möglich. Is ja klar. Niemand will unnätog viel Arbeit haben.

    Aber... durch das Bindingsystem fällt aben wie Code weg. Eben der Code der die Werte in die Steuerelement packt und wieder zurück.
    Unter WinForm haben viele für gewisse Dinge keine Klassen und rufen von irgendwo Daten ab, packen die Werte in die Controls und fertig. Solche Dinge sollte man komplett streichen und von vorne Anfangen.

    Mit Binding wenn man sich mal eingearbeitet hat kann man echt sehr schnell arbeiten und kommt super schnell an sein Ziel.
    Ich habe mir dein Projekt hier nicht angesehen da dir Asus ja bereits hilft, wollte das aber nur schnell loswerden. ;)

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

    So, im abgehangenen Projekt das zu 90% so ist wie zuvor habe ich lediglich, ohne es umzubenennen, ein ViewModel aus deiner PersonKlasse gemacht. Jede Klasse, welche direkt an die View gebunden wird, ist ein ViewModel. Ein Viewmodel benötigt die IPropertyChanged, und die Propertys müssen voll ausgeschrieben sein (kleiner Tipp, schreib einfach das Wort "prop" und drück 2 x tab, das erstellt dir das Grundgerüst automatisch)

    In deiner PersonKlasse lasse ich nun zusätzlich jedes mal wenn der Name oder das Alter verändert werden, mit: SamName = $"{Name}.{Age}" in den beiden HauptPropertys, das SamName automatisch neu setzen. @Nofear23m gibt es hierfür einen eleganteren Weg oder geht das nur mit einem zugrundeliegenden Model *schuldbewusst dreinblick* ?

    Dein ChangeButton setzt nun lediglich wieder den NewName anstatt des Namens. Den Rest habe ich erstmal so belassen wie er war, ich könnte dir lediglich anbieten mir das OriginalProjekt einmal anzusehen, wenn du meinst das, dass deiner Firma Recht ist (Natürlich dann über eine Konversation, statt öffentlich)

    Wenn du Fragen hast, einfach stellen :P


    LG
    Dateien
    • BindigTest.zip

      (517,11 kB, 40 mal heruntergeladen, zuletzt: )
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hallo Leute

    Da ich markiert wurde gebe ich auch mal meinen Senf dazu. 8o

    asusdk schrieb:

    SamName = $"{Name}.{Age}" in den beiden HauptPropertys, das SamName automatisch neu setzen. @Nofear23m gibt es hierfür einen eleganteren Weg oder geht das nur mit einem zugrundeliegenden Model *schuldbewusst dreinblick* ?

    Wenn ich das richtig verstehe soll die Eigenschaft SamName ja nicht geändert werden. Also sollte hier auch ein ReadOnly Property hin. Wirkt für mich schlüssiger.
    Dann muss man der WPF im Setter der Eigenschaften von welchen das SamName abhängig ist nur nur Bescheid geben das sich dieses auch geändert hat.

    VB.NET-Quellcode

    1. Public Property Name() As String
    2. Get
    3. Return modelObject.Name
    4. End Get
    5. Set(ByVal value As String)
    6. modelObject.Name = value
    7. RaisePropertyChanged()
    8. RaisePropertyChanged(NameOf(SamName))
    9. End Set
    10. End Property
    11. Public Property Age() As Integer
    12. Get
    13. Return modelObject.Age
    14. End Get
    15. Set(ByVal value As Integer)
    16. modelObject.Age = value
    17. RaisePropertyChanged()
    18. RaisePropertyChanged(NameOf(SamName))
    19. End Set
    20. End Property
    21. Public ReadOnly Property SamName() As String
    22. Get
    23. Return $"{Name}.{Age}"
    24. End Get
    25. End Property



    Ich habe das angehängte Beispiel mal etwas erweitert um einfach zu zeigen wie einfach mit ViewModels zu arbeiten ist. Wobei ich es auch ausgeschmückt habe und sogar ein Model UND ein ViewModel habe.
    Also gibt es Person als Model-Objekt und dann gibt es PersonsViewModel und PersonViewModel da ich gleich eine Liste mit Navigation (Manuell und nicht über den Komfort einer CollectionView da ich die Funktionsweise von Binding zeigen wollte) inkl. ViewModel Basisklasse, Commands und DesigntimeSupport ausgesetzt habe.

    Desingtimesupport ist eines der Dinge die einem das ganze VIEL leichter machen, was aber nicht möglich ist wenn man in der CodeBehind des MainWindow arbeitet. Deshalb - IMMER eine ViewModel-Klasse erstellen. Wie man die Benennt ist gleichgültig, aber die CodeBehind lassen wir liegen was direktes Binding betrifft.



    Ich habe dir einen DataManager eingefügt. Einfach als kleines Beispiel wenn man mit Daten arbeitet. Ich weis nicht wie du Arbeitest. XML. Datenbank oder was auch immer. Im Grunde kann man das Beispiel auf alles mögliche umbauen.

    VB.NET-Quellcode

    1. Public Class PersonDataManager
    2. Implements IPersonDataManager
    3. Private CachedPersonList As List(Of Person)
    4. Public Function GetAviablePersons(includeDeleted As Boolean, Optional reload As Boolean = False) As ICollection(Of Person) Implements IPersonDataManager.GetAviablePersons
    5. 'In diesem Beispiel geben wir mal irgendwelche Datan zurück
    6. If CachedPersonList Is Nothing Then CachedPersonList = New List(Of Person) : reload = True
    7. If reload Then
    8. CachedPersonList.Clear()
    9. For i As Integer = 0 To 9
    10. Dim newPerson = New Person() With {.Name = $"Name {i.ToString()}", .Age = GenerateRandomNumber(18, 80)}
    11. If i Mod 2 = 0 Then newPerson.IsDeleted = True 'zum probieren jeden zweiten Eintrag als gelöscht markieren
    12. CachedPersonList.Add(newPerson)
    13. Next
    14. End If
    15. If includeDeleted Then
    16. Return CachedPersonList
    17. Else
    18. Return CachedPersonList.Where(Function(x) Not x.IsDeleted).ToList()
    19. End If
    20. End Function
    21. Public Function SaveChanges(person As Person) As Boolean Implements IPersonDataManager.SaveChanges
    22. 'Wir speichern ja nicht wirklich aber wir ersetzen das Objekt
    23. Dim pEntry = CachedPersonList.Where(Function(x) x.Name = person.Name).Single()
    24. pEntry.Name = person.Name
    25. pEntry.Age = person.Age
    26. pEntry.IsDeleted = person.IsDeleted
    27. Return True
    28. End Function
    29. Function GenerateRandomNumber(min As Integer, max As Integer) As Integer
    30. Static Random_Number As New Random()
    31. Return Random_Number.Next(min, max)
    32. End Function
    33. End Class


    Das PersonsViewModel läd die Daten und Navigiert über Command innerhalb der verfügbaren Datensätzen.
    Durch die Commands musst du dich auch nicht darum kümmern das die Buttons Enabled oder Disabled werden, das passiert bei Commands automatisch über die Methode CanExecute. Gibst du innerhalb dieser False zurück wird der Button Disabled.
    Ich habe dir eine RelayCommand Klasse reingepackt welche einem einiges nochmals abnimmt. Musst du Anfangs nicht unbedingt verstehen. Wichtig ist wie du diese Anwenden kannst, hierfür hast du ja einige Beispiele in diesem Projekt.

    Du siehst, wie @asusdk schon schrieb, die WPF setzt zu 100% auf Binding. Das muss man einfadch wissen.
    Hoffe das war nicht zu viel Input ?(



    Liebe Grüße
    Sascha
    Dateien
    • BindigTest.zip

      (493,69 kB, 50 mal heruntergeladen, zuletzt: )
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo und guten Morgen.

    @asusdk - vielen Dank nochmals für die Hilfe. Ich verstehe schon etwas. Das ist zunächst eine Basis mit der ich weiter denken/arbeiten kann.
    @Nofear23m - auch hier vielen Dank - ich werde das mal auseinandernehmen und verstehen (versuchen)...

    Aber was ich schon sehe ist:
    - zunächst muss ich noch deutlich mehr "Infrastruktur" vorbereiten bevor ich mit den Daten arbeite
    - die direkte Bindung zwischen dem Datenmodell und der GUI sollte über ein ViewModell laufen

    Ok, ich werde mir das heute noch tiefer reinziehen!
    Auf jeden Fall - jetzt schon mal vielen Dank!
    ​auch hier vielen Dank - ich werde das mal auseinandernehmen und verstehen (versuchen)...

    Wenn ich hier ein wenig Senf dazugeben darf:
    RelayCommand und ViewModelBase, sind sehr wichtig, ABER mach bitte nicht denselben Fehler, den ich Anfangs gemacht habe, und versuch gar nicht erst, jedes bisschen darin enthaltenen Code zu verstehen, das hatte ich damals versucht und es hat mich schlicht und ergreifend erschlagen. Wie @Nofear23m vorhin schon schrieb, reicht es für den Anfang, wenn du diese Klassen benutzen kannst, bei einem WPF-Projekt (also eigentlich bei all meinen Projekten, da ich nur noch WPF verwende) erstelle ich immer zuerst die grundlegenden Ordner (einfach, weil es aufgeräumter und sauberer wirkt)und kopiere diese Standard-Klassen hinein.
    Dann erstelle ich mir das grundlegende Design und fange mit der Arbeit an =)

    Auch wie man mit diesen Klassen und den Ordnerstrukturen umgeht, ist jedem selbst überlassen, versteif dich da nicht zu sehr auf eine Methodik, Nofear z.B. hat die beiden Klassen hier in einem Unterordner "Infrastructure" im Ordner Viewmodel, Ich selbst packe mir solche Klassen immer lieber in einen Ordner, den ich Utilitys nenne, es gibt hier meines Erachtens nach, keine ultimative Lösung, erstelle dir die Strukturen so, wie DU damit klarkommst, und wie DU dich zurechtfindest =)

    Evtl. hilft es dir noch nicht ganz, für dein aktuelles Projekt, aber auch das @Nofear23m dir hier schon mal ein Beispiel für einen DataService mit in sein Sample gepackt hat, ist eine Wahnsinnig coole Sache, damit kann man richtig viel anstellen. Wenn du z.B. deine Daten früher aus einer Datei geladen hast, aber dann irgendwann entscheidest dir die Daten künftig von einem Server zu laden, musst du nicht mehr an 100 Stellen in deinem Projekt, Änderungen machen, sondern nur im PersonDataManager, du hast damit die Möglichkeit, dein Programm quasi unverändert zu lassen, und musst nur an einer einzigen Stelle verändern von wo die Daten kommen, der Rest funktioniert weiterhin als wäre nichts gewesen, DAS finde ich ist der Wahnsinnige Vorteil der WPF, wenn man ihr volles Potenzial ausnutzt, stell dir den selben Fall mit einem normalen Forms-Projekt vor du müsstest, an zig stellen, ändern welches Control nun die Daten von wo auch immer herbekommt usw. das wäre ein mehr als enormer Aufwand. Ja hier in dem Beispiel wird nichts wirklich abgespeichert, aber das könnte man ganz einfach machen wenn man möchte, ein Beispiel dafür pack ich hier mit rein, das war mal ein Testprojekt, in welchem ich Notizen anlegen kann, da bei uns in der Firma allerdings kein Server zur Verfügung steht, sondern lediglich ein Netzlaufwerk, musste ich die Daten in einer Datei speichern, dazu habe ich (ich glaube die ursprüngliche Klasse kam auch mal von Nofear, danke nochmal ^^) die XMLSerializer-Klasse verwendet, die hab ich zwar noch angepasst, sodass die Daten vorm abspeichern noch komprimiert werden (Ist ein seeeeehr langsames Netzlaufwerk ^^) aber an sich ist es so verdammt einfach die Daten, mit denen dein Programm arbeitet schlicht zu laden, und wieder abzuspeichern, ohne das du wirklich viel anpassen musst.

    XmlSerializer mit 2facher Komprimierung:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Imports System.IO
    2. Imports System.IO.Compression
    3. Namespace Utilitys
    4. ''' <summary>
    5. ''' Der XMLSerializer serialisiert eine beliebige Klasse welche auch Primitiven Datentypen oder solchen welche als Serialisiert gekennzeichnet sind
    6. ''' Klasse ist generisch aufgebaut und gibt die selbe Type von Klasse zurück welche ihm übergeben wird.
    7. '''
    8. ''' Um 2 fache Compression/Decompression erweitert, erst gzip, dann Deflatestream, um die größe des Savefile klein zu halten. Notiz: Xml ist schneller als Json.
    9. ''' </summary>
    10. Public Class XmlSerializer
    11. Dim Comp As Boolean = True
    12. ''' <typeparam name="T">Den Klassentyp angeben</typeparam>
    13. ''' <param name="path">Der Dateipfad inkl. Dateiendung angeben</param>
    14. ''' <param name="instance">Die Instanz der Klasse welche serialisiert werden soll</param>
    15. Public Sub Serialize(Of T)(path As String, instance As T)
    16. Try
    17. Dim dirName As String = IO.Path.GetDirectoryName(path)
    18. If Not Directory.Exists(dirName) Then Directory.CreateDirectory(dirName)
    19. File.Delete(path)
    20. SaveToStream(path, instance, Comp)
    21. Catch ex As Exception
    22. Throw New Exception(ex.Message, ex.InnerException)
    23. End Try
    24. End Sub
    25. ''' <typeparam name="T">Der Typ der Klasse welche erwartet wird.</typeparam>
    26. ''' <param name="path">Der Pfad zur XML-Datei inkl. Dateiendung</param>
    27. ''' <param name="defaultInstance">Die Instanz der Klasse falls die Datei noch nicht Existiert oder nicht gefunden werden kann</param>
    28. ''' <returns>Gibt die Deserialissierte Klasse zurück</returns>
    29. Public Function DeSerialize(Of T)(path As String, defaultInstance As T) As T
    30. Try
    31. If Not File.Exists(path) Then Return defaultInstance
    32. Return LoadFromStream(Of T)(path, Comp)
    33. Catch ex As Exception
    34. Throw New Exception(ex.Message, ex.InnerException)
    35. End Try
    36. End Function
    37. ''' <typeparam name="T">Den Klassentyp angeben</typeparam>
    38. ''' <param name="spath">Der Stream welcher die Daten enthält (Filestream, MemoryStream,...)</param>
    39. ''' <param name="o">Die Instanz der Klasse welche serialisiert werden soll</param>
    40. Public Sub SaveToStream(Of T)(spath As String, o As T, Compress As Boolean)
    41. Try
    42. If Compress Then
    43. Using fs As New FileStream(spath, FileMode.OpenOrCreate)
    44. Using cfs As New GZipStream(fs, CompressionMode.Compress)
    45. Using dcfs As New DeflateStream(cfs, CompressionMode.Compress)
    46. Dim x As New Xml.Serialization.XmlSerializer(GetType(T))
    47. x.Serialize(dcfs, o)
    48. End Using
    49. End Using
    50. End Using
    51. Else
    52. Using fs As New FileStream(spath, FileMode.OpenOrCreate)
    53. Dim x As New Xml.Serialization.XmlSerializer(GetType(T))
    54. x.Serialize(fs, o)
    55. End Using
    56. End If
    57. Catch ex As Exception
    58. Throw New Exception(ex.Message, ex.InnerException)
    59. End Try
    60. End Sub
    61. ''' <typeparam name="T">Der Typ der Klasse welche erwartet wird</typeparam>
    62. ''' <param name="spath">Der Stream welcher die Daten enthält (Filestream, MemoryStream,...)</param>
    63. ''' <returns>Gibt die Deserialisierte Klasse zurück</returns>
    64. Public Function LoadFromStream(Of T)(spath As String, Decompress As Boolean) As T
    65. Try
    66. If Decompress Then
    67. Using fs As New FileStream(spath, FileMode.OpenOrCreate)
    68. Using cfs As New GZipStream(fs, CompressionMode.Decompress)
    69. Using dcfs As New DeflateStream(cfs, CompressionMode.Decompress)
    70. Dim x As New Xml.Serialization.XmlSerializer(GetType(T))
    71. Return CType(x.Deserialize(dcfs), T)
    72. End Using
    73. End Using
    74. End Using
    75. Else
    76. Using fs As New FileStream(spath, FileMode.OpenOrCreate)
    77. Dim x As New Xml.Serialization.XmlSerializer(GetType(T))
    78. Return CType(x.Deserialize(fs), T)
    79. End Using
    80. End If
    81. Catch ex As Exception
    82. Throw New Exception(ex.Message, ex.InnerException)
    83. End Try
    84. End Function
    85. End Class
    86. End Namespace



    Das hier war meine DataService Schnittstelle:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Imports System.Collections.ObjectModel
    2. Imports NextNoticeTest.Models
    3. Public Interface INoticeDataService
    4. ReadOnly Property GetNotices As ObservableCollection(Of NoticeModel)
    5. Sub Save(Notices As ObservableCollection(Of NoticeModel))
    6. Property LastSaved As DateTime
    7. End Interface



    Und die eigentliche Funktionalität des DataService:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. ​Imports System.Collections.ObjectModel
    2. Imports System.IO
    3. Imports NextNoticeTest.Models
    4. Imports NextNoticeTest.Utilitys
    5. Public Class JsonNoticeDataService
    6. Implements INoticeDataService
    7. Dim _dataPath = "C:\Users\marco\Desktop\tests.xml"
    8. Dim _lastSaveFile = "C:\Users\marco\Desktop\lastsave.xml"
    9. Public ReadOnly Property GetNotices As ObservableCollection(Of NoticeModel) Implements INoticeDataService.GetNotices
    10. Get
    11. If Not File.Exists(_dataPath) Then
    12. Return New ObservableCollection(Of NoticeModel)
    13. Else
    14. Dim ser As New XmlSerializer()
    15. Return ser.DeSerialize(Of ObservableCollection(Of NoticeModel))(_dataPath, New ObservableCollection(Of NoticeModel))
    16. End If
    17. End Get
    18. End Property
    19. Public Property LastSaved As Date Implements INoticeDataService.LastSaved
    20. Get
    21. If Not File.Exists(_lastSaveFile) Then
    22. File.Create(_lastSaveFile).Close()
    23. File.WriteAllText(_lastSaveFile, DateTime.Now.ToString)
    24. End If
    25. Return CType(File.ReadAllText(_lastSaveFile), DateTime)
    26. End Get
    27. Set(value As Date)
    28. If Not File.Exists(_lastSaveFile) Then
    29. File.Create(_lastSaveFile).Close()
    30. End If
    31. File.WriteAllText(_lastSaveFile, value.ToString)
    32. End Set
    33. End Property
    34. Public Sub Save(Notices As ObservableCollection(Of NoticeModel)) Implements INoticeDataService.Save
    35. Dim ser As New XmlSerializer()
    36. ser.Serialize(_dataPath, Notices)
    37. LastSaved = DateTime.Now
    38. End Sub
    39. End Class




    Da hier ein DataService Verwendung findet, müsste ich um z.B. von der XML-Datei, zu serverbasiertem Laden und speichern zu wechseln, nur im DataService, statt den Xml-Serializer zu verwenden, halt eben z.B. die Daten von einem SQL-Server (oder welchem auch immer ^^) laden lassen und wieder dort abspeichern, ich müsste also nur an einer einzigen Klasse etwas simples verändern, und der Rest vom Programm könnte bleiben wie zuvor es würde keine weiteren Anpassungen benötigen
    If Energy = Low Then
    Drink(aHugeCoffee)
    Else
    Drink(aHugeCoffeeToo)
    End If
    Hallo nochmals.

    Sorry, ,ich hatte vergessen die Modelklasse "Person" zu bearbeiten. Diese muss in solch einem Aufbau nicht so kompliziert sein. In dem Fall wo man eigene ViewModels hat können es reine POCO Klassen sein.
    Sorry

    VB.NET-Quellcode

    1. Public Class Person
    2. Public Property Name As String
    3. Public Property Age As Integer
    4. Public Property IsDeleted As Boolean = False
    5. End Class


    Ja, es wirkt Anfangs als wäre es viel mehr aufwand weil so viele Klassen benötigt werden. Aber bedenke hierbei das du diesen Mehrauswand ja aber dem zweiten View ja nicht mehr hast.
    Und jede größere Anwendung besteht ja nicht aus einer Hand voll Views sondern eher oft aus Hunderten. Insofern rechnet sich das wie du dir vorstellen kannst relativ schnell.

    Klar, will ich nur schnell ne kleine App - bestehend aus drei Views - aufbauen, welche mir iregendwas ausrechnet oder so dann ist das sicher viel Aufwand.
    Wir können dir nur sagen: Es lohnt sich. Alleine das alles so schön Strukturiert ist durch die Trennung ist ein segen und gerade bei größeren Projekten unglaublich hilfreich.

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