Kurze Orientierung bezüglich Singleton und Designdaten

  • WPF MVVM
  • .NET 7–8

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Kurze Orientierung bezüglich Singleton und Designdaten

    Hallo,

    ich hoffe ich habe nun das Nötige herausdestillieren können, ich habe hier erstmal ein laufendes DataGrid, welches zur Designzeit leichte Kost und zur Laufzeit echte Daten hält.
    Außerdem habe ich mich am Singleton Datenmodell versucht. Hoffe ihr habt nochmal Feedback für mich, denn ich habe das jetzt soweit verkürzt wie mein Verständnis reicht. Was also zu kurz ist, wäre für mich relevant an dieser Stelle

    Window

    XML-Quellcode

    1. xmlns:vm="clr-namespace:WPFTest"
    2. mc:Ignorable="d"
    3. Title="MainWindow" Height="450" Width="800"
    4. DataContext="{Binding Source={x:Static vm:Data.Instance}}">
    5. <Grid>
    6. <DataGrid Name="DG1" ItemsSource="{Binding Produkte}"/>
    7. </Grid>

    Data

    VB.NET-Quellcode

    1. Public NotInheritable Class Data
    2. Private Shared _Instance As Data = Nothing
    3. Public Shared ReadOnly Property Instance As Data
    4. Get
    5. If _Instance Is Nothing Then _Instance = New Data
    6. Return _Instance
    7. End Get
    8. End Property
    9. Private _adpt As Adapter(Of Produkt)
    10. Private ReadOnly __Produkte As ObservableCollection(Of Produkt)
    11. Private ReadOnly _DesignerProdukte As ObservableCollection(Of Testprodukt)
    12. Public ReadOnly Property Produkte As ListCollectionView
    13. Private Sub New()
    14. Using p = Process.GetCurrentProcess()
    15. If p.ProcessName = "WpfSurface" Then
    16. _DesignerProdukte = New ObservableCollection(Of Testprodukt)
    17. LoadTest()
    18. Else
    19. _adpt = New Adapter(Of Produkt)(Cmds.base, DataBase.Eins)
    20. __Produkte = New ObservableCollection(Of Produkt)
    21. Load()
    22. End If
    23. End Using
    24. End Sub
    25. Private Sub LoadTest()
    26. _DesignerProdukte.Clear()
    27. _DesignerProdukte.Add(New Testprodukt(1, "Flasche Pommes", #1/1/2000#, 1))
    28. _DesignerProdukte.Add(New Testprodukt(2, "Kiste Mayo", #1/2/2000#, 1))
    29. _DesignerProdukte.Add(New Testprodukt(3, "Büchse Bohnen", #1/1/2000#, 3))
    30. _DesignerProdukte.Add(New Testprodukt(4, "Büchse Bohnen", #1/2/2000#, 3))
    31. _Produkte = New ListCollectionView(_DesignerProdukte)
    32. End Sub
    33. Friend Sub Load()
    34. __Produkte.Clear()
    35. _adpt.Fill(__Produkte)
    36. _Produkte = New ListCollectionView(__Produkte)
    37. End Sub
    38. End Class

    Was ich auch noch nicht weiß, wie es sein sollte ist, ich möchte gerne Load mit nem Buttonklick aufrufen können zum Aktualisieren.
    Was brauche ich da? <Button Content="Aktualisieren" Margin="5" Width="80" HorizontalAlignment="Left"/> mit Click mag er nix binden.

    Viele Grüße
    Für Design- wie auch Lauf-Zeit würde ich die selbe Liste nehmen. Zur DesignTime aber mit "MockDaten" füllen, zur Laufzeit mit den richtigen. Es fehlt INotifyPropertyChanged, bzw. eine Klasse die das implementiert, von der du dann erbst, so das bei änderung das View benachrichtigt werden kann, für den Button, schau dir dessen Command Property an, daran bindest du ICommands, CommandParameter ist auch interessant dafür ;) . Sieh dir im Tutorial von Nofear23fm die RelayCommand-Klasse an, da sieht du eine Implementation davon.

    ICommand , tut -> 2.1.8.7 - Die RelayCommand-Klasse, bzw. alles in 2.1.8 Inputs und Commands
    INotifyPropertyChanged, Tut -> 6.2 Der Core, wobei ich den Namen den Nofear verwendet schlecht finde. Was wie Notifyable oder ObservableObject ist besser, kommt vor das man auch Modelklassen davon erben läst, da finde ich "ViewModelBase" unpassend, weil auch im Model ganz nützlich.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    DTF schrieb:

    Für Design- wie auch Lauf-Zeit würde ich die selbe Liste nehmen.
    Ich bin da noch radikaler: Vermeide das Wörtchen New. Wenn du mit Databinding unterwegs bist, und erstellst eine Liste mit New, dann sind alle Bindings an die Liste futsch.
    Bzw um genau zu sein: sie sind noch da, binden aber nachwievor an die alte Liste, die nicht mehr gebraucht wird.

    Also die ListCollectionView einmal erstellen für beide: DesignZeit und Runtime.

    Guck dir nochmal meine Architektur an.
    Ansonsten brauchst du im gezeigten Code an keiner Stelle INotifyPropertyChanged, weil es wird einmal gebunden und bleibt dann dabei.
    Mag sein, dass du in deinen Produkte-Klassen INotifyPropertyChanged benötigst - weissinet.

    Also so:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public NotInheritable Class Data
    2. Private Shared _Instance As Data = Nothing
    3. Public Shared ReadOnly Property Instance As Data
    4. Get
    5. If _Instance Is Nothing Then _Instance = New Data
    6. Return _Instance
    7. End Get
    8. End Property
    9. Private _adpt As Adapter(Of Produkt)
    10. Private ReadOnly __Produkte As New ObservableCollection(Of Produkt)
    11. Public ReadOnly Property Produkte As New ListCollectionView(__Produkte)
    12. Private Sub New()
    13. Using p = Process.GetCurrentProcess()
    14. If p.ProcessName = "WpfSurface" Then
    15. LoadTest()
    16. Else
    17. _adpt = New Adapter(Of Produkt)(Cmds.base, DataBase.Eins)
    18. Load()
    19. End If
    20. End Using
    21. End Sub
    22. Private Sub LoadTest()
    23. __Produkte.Clear()
    24. __Produkte.Add(New Testprodukt(1, "Flasche Pommes", #1/1/2000#, 1))
    25. __Produkte.Add(New Testprodukt(2, "Kiste Mayo", #1/2/2000#, 1))
    26. __Produkte.Add(New Testprodukt(3, "Büchse Bohnen", #1/1/2000#, 3))
    27. __Produkte.Add(New Testprodukt(4, "Büchse Bohnen", #1/2/2000#, 3))
    28. End Sub
    29. Friend Sub Load()
    30. __Produkte.Clear()
    31. _adpt.Fill(__Produkte)
    32. End Sub
    33. End Class

    tja, zum Laden auf Buttonclick schliesse ich mich meim Vorredner an.
    Guck dir nochmal meine Architektur an. Ich bin fast ärgerlich, weil da ist genau vorgeturnt - etwa in snippet #2 und Besprechnung - wonach du hier jetzt nochmal fragst.

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

    Hm ja ich habe zwei Listen, weil die unterschiedliche Klassen beinhalten.
    Also Testprodukt hat 4 Properties, Produkt hat 25 Properties, da ist das initialisieren echt unschön, aber naja man machts wohl nur einmal.
    Aber ich hab mir extra n schönen Adapter gebastelt, vielleicht muss ich den Designtime Unterschied auf den erweitern, damit der in diesem Fall nicht an die Datenbank geht, sondern an eine xml oder so.
    Könnte ich Produkt von einer anderen Klasse ableiten und dann diese Basisklasse in der ObservableCollection als Typ angeben, um so zwischen beiden Klassen wechseln zu können? Oder macht mir das auch Binding kaputt?

    Das mit INotifyPropertyChanged ist mir auch aufgefallen, deswegen weiß ich auch noch gar nicht wofür das ist.
    Ich hab den Vorteil, dass ich keine Produkte ändern muss (sind auch alle Properties ReadOnly), deswegen wird das wohl so sein.

    Haudruferzappeltnoch schrieb:

    Ich hab den Vorteil, dass ich keine Produkte ändern muss (sind auch alle Properties ReadOnly), deswegen wird das wohl so sein.
    INotifyPropertyChanged brauchst du streng genommen nur, wenn du einen Datensatz änderst, der woanders auch angezeigt wird. Damit die zweite Anzeige sich aktualisiert.
    Aber da das bei Datensätzen relativ oft vorkommt, gibt man einfach jeder Datensatz-Klasse ein INotifyPropertyChanged (vorzugsweise geerbt von ViewmodelBase).
    Aber wenn du das grad nicht brauchst, brauchstes halt nicht.

    Jedenfalls eine Liste, an die gebunden wird, ist kein Datensatz.
    Wie gesagt: Listen brauchen und sollten sich nie ändern. Man kann sie leeren und befüllen, einzelne Datensätze daraus austauschen und wasweissich. Aber die Liste - und auch die ListCollectionView - bleibt dieselbe. Mach eine Readonly Property davon und ist in trockenen Tüchern.
    Nun ich bin noch immer nicht bei den RelayCommands angekommen, da die recht weit hinten stehen bei NoFear. (@ErfinderDesRades Was deine Architektur angeht: Ich will das nicht einfach kopieren, nur damits läuft, ich will es ja auch verstehen.)
    Aber auf dem Weg dorthin ist mir etwas aufgefallen womit ich quasi meine Aktualisierung hintricksen konnte.

    Und zwar führt die Checkbox im Gegensatz zum Button bereits bei einem Klick Code aus. Nämlich ändert die ihre IsChecked Eigenschaft. Und darauf kann man binden. Und wenn ich da auf eine Property binde, kann ich im Setter der Property einfach Aktualisieren.
    Da man die Checkbox auch einfach wie einen Button aussehen lassen kann, muss das doch mindestens genauso einfach direkt für einen Button gehen?

    XML-Quellcode

    1. <CheckBox Content="Aktualisieren" IsChecked="{Binding Aktualisieren}"/>

    VB.NET-Quellcode

    1. Public NotInheritable Class Data
    2. ...
    3. Private _Aktualisieren As Boolean
    4. Public Property Aktualisieren As Boolean
    5. Get
    6. Return _Aktualisieren
    7. End Get
    8. Set
    9. Load()
    10. _Aktualisieren = Value
    11. End Set
    12. End Property
    13. Private Sub Load()
    14. __Produkte.Clear()
    15. _adpt.Fill(__Produkte)
    16. End Sub
    17. End Class

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

    kann man machen.
    Quasi alle Buttons verbannen, und nur noch Checkboxen verwenden, die aussehen wie Buttons.
    Vielleicht geht das auch mit MenüItem.
    Aber einfacher ginge es, wenn man für Buttons Buttons verwendet und für MenüItem MenüItem.
    Und zwei wichtige Features des Command-Patterns gehen dabei flöten: Binding an übergeordnete Viewmodels und die CanExecute-Funktionalität

    Also offsichtlich gelingt dir, die Checkbox.Checked-Eigenschaft an eine Boolean-Property zu binden.

    Wenn das bei dir funzt, wärs doch nur noch ein kleiner Schritt, die Button.Command-Eigenschaft an eine RelayCommand-Property zu binden.



    Haudruferzappeltnoch schrieb:

    Was deine Architektur angeht: Ich will das nicht einfach kopieren, nur damits läuft, ich will es ja auch verstehen.
    Und du denkst, du kannst meine Architektur nicht mehr verstehen, wenn du sie erst einmal kopiert hast?
    Da kann ich dich beruhigen: Man kann das eine tun, und muss das andere deswegen nicht lassen.
    Und statt das Rad neu zu erfinden, weil man ein Detail nicht verstanden hat, ists vielleicht langweilig, aber doch schneller zielführend, nachzufragen.
    Vielleicht langtst sogar schon, nicht nur den Code zu kopieren, sondern auch den Text des Tuts durchzuarbeiten, der enthält ja auch Erklärungsversuche.

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

    ErfinderDesRades schrieb:

    CanExecute-Funktionalität

    Nu, da geht es ja schon los, ich weiß nicht mal, was es damit auf sich hat. Ich sehe in deinem Beispiel wohl, das soll verhindern, dass das Rating >10 oder < 1 wird. Aber was mache ich damit, wenn ich keine solche Barrieren habe?
    Und dann widerum kann ich das Rating doch auch statt um += 1 erhöhen, einfach += If(Rating <10,1,0) machen. Also ich will damit nicht sagen, dass das besser schlechter oder sonst was ist, sondern ich will sagen es fehlt jedwede Motivation, die Dinge so zu tun wie du sie tust und das steht auch nicht im Text dazu.
    Und die RelayCommand Klasse erklärst du doch gar nicht, sondern nur die Dinge, die sie benutzen. Ich denke das ist eher Material für fortgeschrittenere User.
    Also für mich ist das schon ein großer Unterschied. Weil ein Relaycommand viel mehr Funktionalität abdeckt als eine Dummy-Property in meiner Data-Klasse (die eben ja genau nichts tut, außer eine Bindung zu sein)

    Ich denke sogar gerade weil man nicht jede Motivation für jeden User abdecken kann, ist es wichtig eigene Einstiegpunkte zu finden ("das Rad neu erfinden") und damit dann zu verstehen warum die Lösungen, die existieren, existieren.

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

    Haudruferzappeltnoch schrieb:

    Aber was mache ich damit, wenn ich keine solche Barrieren habe?
    dann lass es weg, meingottwasdennsonst.



    += If(Rating <10,1,0)
    kann man machen. Ist nur komisch für den User, wenns da einen enableten UpRating-Button gibt, der ab 10 nicht mehr uprated - da sehe ich durchaus schon eine ganz deutliche Motivation.
    Oder wenn es einen Item-Delete-Button gibt, und die Liste ist leer.
    In meiner Welt muss so ein Button dann disabled werden, weil ist ja einfach Tatsache, dass CanExecute=False.



    Womöglich tust du dir da keinen Gefallen, wenn du immer auch den Infrastruktur-Code bis ins letzte Detail verstehen willst - ansonsten du lieber etwas eigenes erfindest.
    Von anderer Infrastruktur hast du ja erst garkeinen Code, und trotzdem verwendest du TabControls, ObservableCollections und wasweissichnichalles.

    Haudruferzappeltnoch schrieb:

    Und die RelayCommand Klasse erklärst du doch gar nicht, sondern nur die Dinge, die sie benutzen. Ich denke das ist eher Material für fortgeschrittenere User.
    Ja natürlich.
    Da würde doch keiner mehr durchblicken im Tut, wenn ich mehr daran herumlaberte, als wie das Ding zu benutzen ist.

    Soll ich etwa die ganzen Codezeilen meiner RelayCommand-Klasse durchgehen? In einem tutorial zur Wpf-Anwendungs-Struktur?
    Das würde das Tut aufs doppelte oder dreifache anschwellen lassen, und nur die wenigsten würden alles verstehen: Einer schnallt bei Shared Fields ab, ein anderer bei TypParamtern, der nächste bei der Event-Implementierung, und wieder einer scheitert an anonymen Methoden.
    Oder soll ich das alles auch noch auseinandersetzen? Na, dann hab ich aber ein Buch über .Net geschrieben, wenn ich fertig bin.
    Und für jemanden, der wissen will, wie man eine Wpf-Anwendung auf die Spur bringt, täts genau garnix bringen, weil all die Eingeweide-Schau ändert keinen Deut daran, wie ein RelayCommand zu benutzen ist.

    Du kannst dir auch bei Nuget eine Wpf-Helper-Bibliothek downloaden, da ist auch immer ein RelayCommand drinne. Das sind dann Binaries, und du kommst garnet auf die Idee, dich damit aufzuhalten, wie deren Code wohl funktioniert.

    Aber guck!: Zu Commands gibts auch ein Tutorial. Schau mal rein, ob dir das was bringt.

    .Net-Programmierung ist numal wesentlich, dass man vorhandene Komponenten richtig benutzen lernt.
    Deren Implementierung einzusehen und zu verstehen ist ein nice-to-have aber erstmal irrelevant.
    Erstmal Auto fahren lernen. Unter die Haube gucken kannste später, wenns dich besonders interessiert.

    Aber frag ruhig. Ich bin durchaus bereit, dir jedes Detail zu erklären. Also nachdem du gelernt hast, es zu benutzen.

    Dieser Beitrag wurde bereits 12 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Nein sollst du natürlich nicht, das habe ich ja gesagt.
    Aber ich weiß ja nicht wie ichs anders erklären soll, für mich funktioniert das halt so. Jeder lernt anders.
    Für mich ist das auch nicht unter die Motorhaube gucken, sondern eher der Ablauf von Kupplung Schaltung und Gaspedal. Zum Autofahren gehören viele Details nun mal dazu das ist nicht einfach nur Autofahren.

    Eine ObservableCollection finde ich habe ich genügend verstanden, und die habe ich keineswegs von Anfang an genutzt, zuerst hatte ich da eine typisierte DataTable stattdessen.
    Die RelayCommand-Klasse fällt da vor allem besonders auf, weil sie auf derselben Ebene liegt wie das MainModelBase, und das habe ich ja auch auf eine Version abgespeckt die mir wenig ablenkend erscheint.

    Und "lass es weg" sagst du "ist doch ganz klar" sagst du, aber habe ich oben nicht genau da einige Sachen weggelassen, wodurch nun die Diskussion steht?
    Also so einfach scheints ja dann wohl wieder nicht zu sein, dann muss Weglassen ja scheinbar auch erstmal gekonnt sein. (Mal ganz davon zu schweigen, dass ich keine solche Diskussion beabsichtigen wollte; ich versuche nur mich nach vorne zu hangeln.)
    ich meine mit Weglassen: lass es weg, das Feature zu benutzen.
    Ich meine nicht: programmiere das Feature weg.
    Grade beim RelayCommand sind im Tut eigentlich sehr schön alle Standard-Features verwendet - das wird nicht einfach, da was weg-zu-vereinfachen.
    Schon weil der Kram vom ICommand-Interface ja gefordert ist.
    ZB hast du dich schon mit dem CommandParameter beschäftigt?
    Wird im Tut benutzt, aber nur ganz knapp erwähnt.
    Dabei berührt das eine hochinteressante Eigenschaft des Wpf-DataContexts, wie der nämlich in ItemsControls an die Items vererbt wird.
    ItemsControl selbst ist eine hochinteressante (Basis-)Klasse - alles was man gemeinhin unter ItemControl verstehen mag (als ein Control was mehrere Items zeigt) erbt tatsächlich von ItemsControl:
    TabControl, Treeview, ListBox, DataGrid, ListView
    Mit den Parametern habe ich mich noch nicht beschäftigt, gesehen habe ich es schon.

    Ich habe mich nun mal auf ICommand beschränkt.
    Damit habe ich den Button nun auch funktional.

    VB.NET-Quellcode

    1. Public Class Relaycommand
    2. Implements ICommand
    3. Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
    4. Private ReadOnly myAction As Action
    5. Public Sub New(_sub As Action)
    6. myAction = _sub
    7. End Sub
    8. Public Sub Execute(parameter As Object) Implements ICommand.Execute
    9. myAction.Invoke
    10. End Sub
    11. Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
    12. Return True
    13. End Function
    14. End Class

    Zum Binding habe ich da eine Frage. Eine xaml Eigenschaft bindet auf eine DataContext-Eigenschaft. Aber scheinbar geht da jede xaml-Eigenschaft anders mit so einer Bindung um.
    Bei IsChecked der CheckBox kann man sehen wie Setter und Getter aufgerufen werden. Beim Command des Buttons muss hingegen ja irgendwo ICommand.Execute aufgerufen werden. Also klar erstmal mit dem Getter dran kommen, aber dann ist das ja noch ein Extraschritt, den man nirgendwo sieht.

    Und ihr macht da ja ein Custom Event aus dem Event, aber was passiert da eigentlich?

    Haudruferzappeltnoch schrieb:

    Bei IsChecked der CheckBox kann man sehen wie Setter und Getter aufgerufen werden. Beim Command des Buttons muss hingegen ja irgendwo ICommand.Execute aufgerufen werden. Also klar erstmal mit dem Getter dran kommen, aber dann ist das ja noch ein Extraschritt, den man nirgendwo sieht.

    Bei Checked ändert das Control den Property-Wert.
    Das ist beim Command nicht nötig. Beim Command wird nur das Command abgeholt - genau einmal. Dann hat der Button das Command.
    Und was ist drin im Command? achja - eine anonyme Methode, die man aufrufen kann. Daran isst das Binding dann garnet mehr beteiligt.

    Also im Grunde ist hier das Binding völlig überkandidelt. DataBinding ist ja vom Konzept her dazu da, dass das Control das Datenobjekt verändert, und das Datenobjekt verändert das Control.
    Brauch man hier aber garnet - man will nur initial dem Button eine Methode übergeben, die er aufrufen kann.
    Aber Xaml kann numal nicht anderes mittm Viewmodel kommunizieren als per Binding.