Programmeinstellungen vor dem Entladen speichern

  • WPF MVVM
  • .NET (FX) 4.5–4.8

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

    Programmeinstellungen vor dem Entladen speichern

    Hallo Forum,

    hab mal eine ganz kurze Frage:

    Und zwar möchte ich vor dem Schliessen des Programms noch etliche Controlwerte und Einstellungen speichern, also quasi nachdem man im Hauptfenster das X rechts obendrückt aber bevor irgendwas ungeloadet wird.

    Und das Ganze natürlich umgedreht, wenn man das Programm lädt, dass wenn alles geladen ist, dann die Einstellungen wieder aus einer Datei ausgelesen werden und dann den Controls zugeordnet werden.

    Ich habe im Internet dazu verschiedene Rangehensweisen gesehen, nun die Frage an euch, was ist die beste für meinen Fall? Ich benutze MVVM mit zahlreichen Verschachtelungen und weiss nicht wirklich, welches ViewModel denn nun zuerst oder zuletzt ge-/entladen wird...

    Soweit, so gut,

    kafffee :)
    Na ja, Du musst schon entscheiden, wann Du die Werte speichern willst?
    • Am Ende alles was ggf. offen ist
    • Beim Schließen eines Fensters die Werte aus dem Fenster?
    • ...

    Ich denke das Speichern jeweils beim Schließen eines Fensters, wird die sinnvollste sein.
    NB. Es ist doch schön, wenn man lesbare Namen vergibt. Siehe auch [VB.NET] Beispiele für guten und schlechten Code (Stil).
    @kafffee In welcher Form liegen diese Settings in Deinem Programm vor?
    Wann erfolgt der ereste, wann der lertre Zugriff auf die Settings?
    Laden bei Programmstart und Speichern bei Programmbeendigung ist m.E. sinnvoll.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hab nur ein Fenster, das MainWindow, aus dem die Werte gespeichert werden müssen, aber das zeigt verschiedene Views an (die dann wiederum an verschiedene ViewModels gekoppelt sind). Die Werte aus diesen ViewModels müssen dann gespeichert werden, ich hatte da vor ein Model zu machen, das ich dann serialisiere...

    @RodFromGermany

    Das sind alles Properties, die an die View von verschiedenen Controls gebunden sind.
    Der Zugriff erfolgt dann, wenn der User jetzt Z. B. eine Progressbar verschiebt oder den Inhalt einer Textbox ändert.
    Speichern bei Programmende und laden beim Start war auch meine Idee...
    OK, diese WPF-Datenkoppelung - da habe ich keine Ahnung von.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    kafffee schrieb:

    Hab nur ein Fenster, das MainWindow, aus dem die Werte gespeichert werden müssen, aber das zeigt verschiedene Views an (die dann wiederum an verschiedene ViewModels gekoppelt sind). Die Werte aus diesen ViewModels müssen dann gespeichert werden, ich hatte da vor ein Model zu machen, das ich dann serialisiere...


    Bedeutet du hast noch nichts wo du überhaupt etwas speicherst? (Datenbank, XML File etc.)
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    @Akanel

    Doch klar. Hab bis jetzt ein paar elementäre Properties, die ich in den meisten Fällen vom jeweiligen ViewModel aus als JSON speichere. Jetzt möchte ich das alles vereinheitlichen, die ganzen Werte aus meinen an die View gebundenen Properties in einer Klasse sammeln, diese dann serialisieren und auch als JSON String speichern.
    Voraussetzung ist also, dass ich von der Prozedur, die das durchführen soll, auf alle meine ViewModels zugreifen kann. Soweit kein Problem, meine Architektur ist so, dass ich von einem ViewModel aus auf die anderen zugreifen kann.
    Die Herausforderung besteht also darin, den Controls die Werte aus dem JSON erst dann zuzuweisen, wenn alle relevanten ViewModels schon initialisiert bzw. geladen sind und umgekehrt, die Werte im JSON zu speichern bevor die ViewModels entladen sind...

    kafffee schrieb:

    Ich benutze MVVM mit zahlreichen Verschachtelungen und weiss nicht wirklich, welches ViewModel denn nun zuerst oder zuletzt ge-/entladen wird...


    Hmm - bei mir ist das einfach: das MainViewmodel wird zuerst und zuletzt ge-/entladen.

    Control-Einstellungen speichern ist mit MVVM natürlich problematisch.
    Oder auch nicht, wenn sie ans ViewModel gebunden sind.

    kafffee schrieb:

    (...) was ist die beste für meinen Fall? Ich benutze MVVM mit zahlreichen Verschachtelungen und weiss nicht wirklich, welches ViewModel denn nun zuerst oder zuletzt ge-/entladen wird (...)

    Das weiß höchstwahrscheinlich niemand, weil niemand sonst außer demjenigen, der fragt, an der entsprechenden Tastatur und vor dem entsprecheden Bildschirm sitzt – ausprobieren bzw. das eine oder andere probieren ist die Devise, um einige Erfahrungswerte für sich zu gewinnen und dadurch ggfs. neue oder entsprechende, reale Probleme – oder eben auch keine – zu entdecken. Auch die Technik, krampfhaft an dem MainWindow festzuhalten, wenn viele verschiedene Inhalte/Bildschirme/Menüs/Optionen dargestellt werden sollen, und das damit verbundene Umfüllen der Werte für Kontrollelemente des Hauptfensters dürfen oder sollten infrage gestellt werden, bzw. eine andere Technik in Erwägung gezogen werden – z.B. mit diversen Fenstern, wo man weniger dynamisch arbeiten, dafür dann aber nur umschalten darf oder muss. Ich persönlich arbeite mittlerweile bei den aktuellen Entwürfen meiner Anwendungen fast immer mit über drei Fenstern, MainWindow ist quasi nur so eine Art StartFenster oder Krücke zum Laden der Anwendung, insofern würde sich die Frage für mich nicht mehr stellen – die Zugriffe auf das MainWindow von weiteren Fenstern sind in WPF auch etwas problematischer als die Zugriffe unter diesen weiteren Fenstern untereinander. Das vernünftige Schließen aller Fenster am Ende der Laufzeit muss auch gut/sicher gelöst sein, damit dann nichts als hässliches Artefakt im Hintergrund verbleibt, das man nur noch mit dem Taskmanager finden, betrachten und manuell wieder schließen kann, denn das kann vor allem dann schnell passieren, wenn man mit Hide und Show arbeitet und gewisse Schließvorgänge mit Interrupts (Events) abfangen und unterbinden möchte. Das gleichzeitige Arbeiten mit vielen Fenstern hat also auch Nachteile, bzw. ist nicht so trivial, wenn man z.B. gerade mit dem ganzen Thema frisch ist oder sich in dieses gerade einarbeitet. Am Ende des Tages oder besser gesagt – der Tage – lohnt es sich dann aber doch, den Umgang mit vielen Fenstern gelernt zu haben, es zu meistern, insbesondere dann, wenn man sich das alles selbst erarbeitet hat und daraus gewisse Standardprozeduren für seine Projekte herausgearbeitet hat, denn diese können – sofern sie gut sind und sich bis dato bewährt haben – dann immer wieder eingesetzt werden. Herausfinden, was für einen dann besser oder möglich ist, muss letztendlich aber jeder für sich selbst – und das geht meistens nur über Praxiswerte/Erfahrungen sammeln, Theorie ist natürlich auch gut und wichtig, das ist oder sollte quasi die Grundlage allen Seins sein, aber vom Theoretisieren selbst wird man am Ende nicht weiterkommen bzw. satt werden, man muss etwas tun, etwas (er)schaffen, auf etwas beißen, kauen, verdauen, sich manchmal auch vergiften und übergeben – solche Szenarien sind aber auch wichtig und gehören dazu.

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

    Hab mir mal paar Gedanken gemacht und hatte tatsächlich eine Idee:

    Ich hab ja sowieso einen Service wo ich alles drin hab, was programmweit verfügbar sein muss. Darin sammle ich einfach meine Daten in einer Klasse und beim Verlassen des Programms schreib ich die Klasse in ein JSON-File... Beim Laden des Service, und das sollte theoretisch vor dem Laden de ViewModels geschehen, lade ich den File wieder in die Klasse und in den Konstruktoren meiner ViewModels hol ich mir dann wieder die Einstellungen aus der Klasse in die Properties, an die meine Controls gebunden sind.

    Bin mal gespannt ob das klappt und halte euch auf dem Laufenden.... :)

    Edit: Okay so wies aussieht hab ichs hinbekommen, zumindest mal das Abspeichern, das war der schwierigere Teil. Näheres poste ich mal wenn ich damit auch fertig bin...

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

    Okay, beim Laden, oder besser gesagt beim Setzen der Controleigenschaften auf die aus dem JSON geladenen Werte, taucht jetzt doch ein Problem auf, wo ich mir die Haare raufe, weils einfach für mich komplett unlogisch aussieht.

    Folgendes Szenario:
    Ich habe einen Service, in dessen Konstruktor das JSON in die Klasse MeineEinstellungen geladen wird (s. Screenshot oberes rotes Rechteck). Dieser Service wird vor dem MainViewModel geladen (s. untere zwei Rechtecke).

    Dann will ich die Werte aus MeineEinstellungen auf die an die Controls gebundenen Properties übertragen:

    VB.NET-Quellcode

    1. If MainModule.SettingsVorhanden = True Then
    2. LayerViewModel.PlattendecksViewModel.MikrofonVolume = MeineEinstellungen.MikrofonVolume
    3. LayerViewModel.PlattendecksViewModel.LineInVolume = MeineEinstellungen.LineInVolume
    4. Else
    5. LayerViewModel.PlattendecksViewModel.MikrofonVolume = 1
    6. LayerViewModel.PlattendecksViewModel.LineInVolume = 1
    7. End If


    Da passiert aber gar nichts, oder besser gesagt werden die Properties auf 0 gesetzt, bzw. das Komischste kommt erst:

    Wie auf dem Screenshot ersichtlich, gibt die Debug-Ausgabe die richtigen Werte für MicVolume und LineInVolume aus. Aber wenn ich mit der Maus dann drüber hovere (s. ebenfalls Screenshot) wird mir für MicVolume der Wert 0 angezeigt. Das heisst ja wohl dann, dass der Wert in dem Zeitraum zwischen der Debug-Ausgabe und dem Haltepunkt an irgendeiner Stelle wieder auf 0 gesetzt wird, was mit absolut schleierhaft ist...
    Hab mal auch einen Haltepunkt gesetzt in dem Setter von MikrofonVolume in MeineEinstellungen und dann in der Aufrufhierarchie nachgeschaut, da wurde ich aber auch nicht schlauer draus...

    Gibt es einen Weg rauszufinden, von wo aus genau der Wert auf 0 gesetzt wird oder hat einer vielleicht eine Idee, wie ich da beim Debuggen weiterkomme?

    Edit: Mir ist noch was eingefallen:
    Im Setter von PlattendecksViewModel.MikrofonVolume wird MeineEinstellungen.MikrofonVolume geupdatet. Das kommt sich wahrscheinlich dann beim Initialisieren in die Quere. Aber wie löse ich das anders?

    Edit 2:
    Eine Alternative ist ich mach im Service nur Readonly-Properties und hol mir dann in den Gettern die Daten aus dem ViewModel. Wäre sowieso eleganter und sinnvoller.... Aber wie hol ich mir die richtige Instanz vom ViewModel in den Service?

    Edit3:

    ErfinderDesRades schrieb:

    Hmm - bei mir ist das einfach: das MainViewmodel wird zuerst und zuletzt ge-/entladen.


    Habs mal geprüft, bei mir wird das MainViewModel auch als Letztes geladen. Hab mal nen Haltepunkt gesetzt und bin dann mit Einzelschritten durchgegangen:
    (Die Sub zum Laden der Einstellungen aus dem JSON File habe ich jetzt extra ganz unten in den Konstruktor von MainViewModel gepackt)
    Es ist als ob MicVolume wie durch Zauberhand auf 0 gesetzt wird... zumindest setze ich es nicht manuell auf 0, ich habe also keine Codezeile oder so übersehen...
    Nach dem Laden des JSON File ist alles in Ordnung, die Werte werden korrekt geladen. und wenn ich dann die Werte setze, sind sie auf einmal auf 0...

    Hast du ne Idee was da los sein könnte? Gerne schicke ich dir auch noch mehr Infos / Codezeilen...
    Bilder
    • screenshot loadsettings.png

      393,17 kB, 2.560×1.080, 15 mal angesehen

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „kafffee“ ()

    Neu

    @kafffee Es scheint mir so, als würdest du irgendwie auf eine Kopie einer Default-Instanz zurückgreifen.

    Leider bietet den Faden bisher wenig Quellcode, sodass man mal etwas genauer hinschauen könnte: Glaskugel ist aktuell etwas beschlagen ;)

    Könntest du hier vielleicht die entsprechenden Ausschnitte aus deiner JSON-Datei, deiner Config-Klasse und dem Ladevorgang reinstellen?
    Quasi ein Minimal-Setup?

    Ein kleiner Hinweis am Rande, Bilder statt Quelltext sind immer doof. Und Anstatt deinen String immer neu zu instanziieren mit (Arbeitsverzeichnis & "/mein/pfad"), kannst du den Pfad in eine Variable auslagern und so immer auf eine Referenz zugreifen.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Neu

    siycah schrieb:

    Es scheint mir so, als würdest du irgendwie auf eine Kopie einer Default-Instanz zurückgreifen.


    Ja ich glaube das war der springende Punkt :) Verwende nun statt des Services eine Instanz einer ViewModel-Klasse, die in meinem "Haupt-Service" instanziiert wird und es geht...

    Was gleich zum nächsten Problem führt:

    Kann es sein, dass man eine Arrray-Property nicht ohne weiteres nach JSON serialisieren kann? Die anderen Properties funktionieren, bloss die Property, die ein Array ist, scheint "übersprungen" zu werden, wenn ich mir den JSON File dann anschaue, ist sie einfach nicht vorhanden...

    Hier mein Code:

    Meine Property in der ViewModel-Klasse, die dann serialisiert wird:

    VB.NET-Quellcode

    1. Private _EqualizerLeft() As Single = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    2. Public Property EqualizerLeft(index As Integer) As Single
    3. Get
    4. Return _EqualizerLeft(index)
    5. End Get
    6. Set(value As Single)
    7. _EqualizerLeft(index) = value
    8. Debug.WriteLine("EQUALIZER" & index & ": " & value)
    9. RaisePropertyChanged()
    10. End Set
    11. End Property


    Im Hauptservice wird dann folgendermassen serialisiert:

    VB.NET-Quellcode

    1. Public Sub New()
    2. MeineEinstellungen = New SettingsInfoVM
    3. End Sub
    4. Private Sub SettingsSpeichern() Implements IZentraleKlasse.SettingsSpeichern
    5. Dim ZuSpeicherndeEinstellungen As String = JsonConvert.SerializeObject(MeineEinstellungen)
    6. System.IO.File.WriteAllText(Arbeitsverzeichnis & "\database\Einstellungen.dtb", ZuSpeicherndeEinstellungen)
    7. End Sub


    Das JSON-File:
    Spoiler anzeigen
    {"MikrofonVolume":1.0,"LineInVolume":1.0,"Theme":{"ColorName":"AliceBlue","Farbe":"#FFF0F8FF","SampleBrush":"#FFF0F8FF","HexValue":"#FFF0F8FF"},"ThemeIndex":0,"VerzeichnisDJSets":null,"Brennlaufwerk":0,"Brennformat":0,"LautsprecherIndex":0,"KopfHoererIndex":0,"MicIndex":0,"LineInIndex":0,"AutoFade":false,"AutoFadeSchwellenwert":0.0,"AutoFadeReduzierenAuf":0.0,"OffLineMode":false,"StreamingIP":null,"StreamingPort":0,"StreamingMaxListeners":0,"SpektrumAnalyzerOptimal":false,"SpektrumAnalyzerDistanz":0,"SpektrumAnalyzerBreite":0,"CodecRippen":0,"CodecDJSetSpeichern":0,"CodecStreamen":0,"VMisBusy":false,"IsInDesignMode":false}

    Neu

    kafffee schrieb:


    Kann es sein, dass man eine Arrray-Property nicht ohne weiteres nach JSON serialisieren kann? Die anderen Properties funktionieren, bloss die Property, die ein Array ist, scheint "übersprungen" zu werden, wenn ich mir den JSON File dann anschaue, ist sie einfach nicht vorhanden...


    Doch doch. Aber du hast deine Property als private markiert, weshalb die Eigenschaft für JSON.Net unsichtbar wird.
    Alle Properties, die du (de-)sterilisieren willst, müssen public markiert werden. Solche Bibliotheken und Frameworks verwenden ganz simple Reflections um die Performance oben zu halten.
    D.h. die schauen wirklich nur nach bestimmten Annotationen oder nach public-Eigenschaften.

    Ansonsten sieht das JSON erstmal korrekt aus.

    Als kleine Randbemerkung würde ich den Pfad zu deiner Datei immer noch als Variable in deiner Klasse speichern.
    Zudem finde ich es persönlich immer sinnvoll, in der "Speichern"-Funktion den Pfad überschreiben zu können. So kannst du nämlich immer Kopien davon anlegen, wenn du deinem Anwender ein einfaches Zurückspringen ermöglichen möchtest. (Hat mir zumindest schon öfter den Hintern gerettet, wenn Kollegen im Namen von Kunden anrufen und das plötzlich ganz anders wiedergeben als vom Kunden ursprünglich gemeint).

    VB.NET-Quellcode

    1. Class Foo
    2. const PathToFile as string = "/mein/pfad.conf"
    3. private m_settings as MySettings
    4. public function SerialiseObject() as string
    5. ' prüfen, ob alle Einstellungen so auch sinnig sind
    6. return JsonConvert.SerializeObject(m_settings) ' ggf noch Einstellungen übergeben?
    7. end function
    8. public sub SaveFile(optional byval savePath as string = PathToFile)
    9. File.WriteAllText(savePath, SerialiseObject())
    10. end sub
    11. End Class
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Neu

    @siycah

    Verzeih mir wenn ich mich täusche aber die Property ist doch Public?
    Nur die Variable zum Zwischenspeichern ist Private...

    Die Idee mit dem überschreibbaren Settings pfad ist gut, aber macht bei diesen Einstellungen nicht viel Sinn (man kann nicht viel kaputt machen) . Höchstens vielleicht für die Equalizer Einstellungen, dass man sich da Presets anlegen kann...

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

    Neu

    kafffee schrieb:

    Ah guck mal was ich grad gefunden hab. Aber das ist glaube ich ein anderer Serializer oder?


    Steht ja da: Namespace: System.Text.Json (also nicht Newton). Allerdings gehört es nicht zum Umfang des von dir verwendeten Frameworks, du müsstest es auch als NuGet Paket installieren. Der Entwickler ist übrigens identisch, der arbeitet nämlich mittlerweile bei Microsoft :)