Settings in MVVM - Umsetzung?

  • WPF

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von florian03.

    Settings in MVVM - Umsetzung?

    Hallo,

    vorweg - ich weiß nicht ob das Thema WPF spezifisch ist - wenn nicht bitte einfach verschieben :thumbsup:

    Also nun zu meinem "Problem":
    Ich stelle gerade mein eines Projekt (SnippetLibrary) auf eine korrekte MVVM Struktur um, was komplizierter als gedacht ist.
    Nun stehe ich vor einem Punkt wo mir überhaupt nicht klar ist, wie ich diesen grundlegend in MVVM umsetzt.

    Ich will in dem Programm Settings speichern.
    Ganz simple, also nicht so "komplex" wie es @Nofear23m in [WpfNote2] Mehrschichtanwendungen mit MVVM und Generischem Repository mittels EF Core - ich hab einfach eine Modelklasse Settings:

    C#-Quellcode

    1. public class Settings
    2. {
    3. public bool TestSetting { get; set; }
    4. public int MaxSnippets { get; set; } //Nur beispiele!
    5. }


    Diese Klasse serialisiere ich mit XML und halte sie während der Laufzeit in einer Singleton Klasse.

    Wie greife ich jetzt aber im XAML auf diese Settings zu - sowohl um ein Einstellungsfenster zu erstellen, wo ich ja an die Werte binden muss, als auch in anderen Fenstern, wo ich z.B. je nach Wert ein Control anzeigen oder ausblenden will.

    Ein ViewModel brauche ich irgendwie, aber wo speicher ich dieses - einfach als Property in meinem WorkspaceViewModel...

    Ich sehe glaube ich den Wals vor Bäumen nicht mehr... :D

    Ich hoffe ich hab mich verständlich ausgedrückt und freue mich über hilfreiche Antworten

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Wenn Du doch eine Singleton Klasse hast, hast du doch vom ViewModel aus Zugriff auf die Properties oder nicht?

    ansonsten erstellst du doch eh ein ViewModel für die Settings, da kannst du die dann direkt laden und an dessen Properties binden.
    "Hier könnte Ihre Werbung stehen..."

    florian03 schrieb:

    Wie greife ich jetzt aber im XAML auf diese Settings zu

    Auf die Settings kannst du wie folgt binden...

    XML-Quellcode

    1. xmlns:local="clr-namespace:DeinProject"
    2. <CheckBox IsChecked="{Binding SettingsProperty, Source={x:Static local:MySettings.Default}}"/>​
    Hallo,

    erst einmal vielen Dank für eure Antworten!

    Zu dem Punkt, warum ich die Settings neu erfinden will:
    Ich habe gedacht es ist besser wenn ich es in meiner eigenen Klasse habe, dann weiß ich wo ich es speichere, kann es in und exportieren, etc.
    Außedem bin ich mir nicht sicher, wie diese mit MVVM und XAML arbeten betüglich ViewAktualisierung.

    MichaHo schrieb:

    ansonsten erstellst du doch eh ein ViewModel für die Settings, da kannst du die dann direkt laden und an dessen Properties binden.

    Genau. Ich habe ein SettingsVM, da habe ich die Properties aus meiner Settings Klasse drinne.
    Die Frage ist nur, wo speichere ich dieses SettingsViewModel - erzeuge ich da einfach in jedem WorkspaceVM ein Property SettingViewModel?

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Ich mache es so das ich eine Singleton Settingsviewmodel habe. Im Viewmodel lade und speichere ich die Settings und hab dann in jedem ViewModel zugriff darauf.
    ich mache das über IoC mit Ninject nuget Paket, @Nofear23m hat daür einen InstanceHolder wenn ich es richtig verstanden habe.
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    @Nofear23m hat daür einen InstanceHolder wenn ich es richtig verstanden habe.

    Ich bin mir jetzt nicht sicher wie ich das in dem WpfNotes2 Projekt gemacht habe und ob ich da überhaupt Settings habe aber in eine Signleton Klasse würde ich das nur packen wenn es nicht mehr als ne Hand voll Settings sind.

    Je nachdem wie komplex Settings und deren Werte sind überlege ich mir da gerne was eigenes. die "My.Settings" sind zwar toll aber mit einem ViewModel bin ich viel flexibler.

    Erstmal muss man unterscheiden. Settings anzeigen in einem View damit der Benutzer diese einsehen/bearbeiten kann oder das abrufen und abspeichern (z.b. die letzt Fenstergröße und Position).

    Bei ersterem bin ich mit einem ViewModel gut bedient. Brauch ich ja wenn ich MVVM mache. Hier muss man eben gucken was man braucht.
    Bei letzterem muss ich die Daten abrufen oder speichern aber nix anzeigen.

    Wenn ich die beiden Dinge nun zusammen betrachte kommt man schnell zu dem entschluss das es sinn macht globale Methoden zum abrufen und Speichern zu haben. Diese Methoden kann ich dann auch gleich im ViewModel nutzen wenn der User diese bearbeiten will. Ob diese globalen Methoden nun in einer Signleton sind oder in Modul oder in einer normalen Klasse von welcher ich immer ne Instanz erstelle bleibt einem selbst überlassen.
    Je nachdem wie viele Settings man hat und wie komplex sie Sache ist kann man sich dann soger nen Cache bauen. Denn diese Methoden zum Abrufen und speichern kümmern sich dann darum das der Cache befüllt wird usw.

    Ich empfehle immer solche Dinge Projektbezogen zu betrachten, viele legen auf sowas wie Settings wenig wert, ich sehe das anders denn auch hier kann man performanceprobleme bekommen wenn man die Settings z.b. in ner DB hätte.

    Da ich gerade Lust zu schreiben habe hier mal ne Geschichte (nur für interessierte):

    Spoiler anzeigen
    Ich habe im Moment ein Projekt in dem es Settings gibt. Aber nicht nur in einer Dimension sondern wenn man so will in 4.
    Es gibt einen Settingswert wie z.b. wo beim Öffnen eines Fensters der Focus liegen soll (also auf welchem Control). Ja, das kann von Kunden unterschiedlich gewünscht sein. (gibt hier viele Scenarios)
    Es soll also einen globalen Wert geben (DB weit), einen Mandentanbezogenen (weil vielleicht ne Multimandantanwendung), einen Arbeitsplatzbezogenen (am Rechner im Lager soll der Focus auf der Textbox mit dem Lagerort gelegt werden), und einen Userbezogenen (der Lagerleiter soll die Übernahme direkt mit Enter bestätigen können).
    Je nach Setting bestimmt der Entwickler für welche Art "der Setting" verwendet werden darf, also in wiefern er überschrieben werden darf (vieleicht nur DB und Mandantweit, oder nur Global, User, und Arbeitsplatzweit).
    Ein Arbeitsplatzweiter Wert überschreibt einen Userwert, ein Userwert überschreibt einen Mandantenwert und ein Mandantenwert überschreibt einen globalen Wert.
    Damit nicht alles immer abgerufen werden muss, soll gecached werden, sobald ein bestimmter Setting abgerufen und die logik herauskristllisiert hat was auf diesem Rechner mit diesem User unter dem aktuellen Mandanten gilt wird der Wert mit dem Key gacached und beim nächsten mal wird direkt aus dem cache abgerufen. Wird der Wert aktualisiert soll die Logik dies auch im Cache aktualisieren. Und selbst dieser Roman ist nur ein kleiner Auszug der Logik zu den Settings in dem Projekt denn die Anzeige im View soll ja auch dynamisch sein.

    Sowas bekommt man mit My.Settings nicht hin. Hier muss Logik dahinter. Die moral von der Geschichte........


    Entweder du speicherst einfach per XML Serialisierung und machst dir ne Klasse welche dir die Wert holt und speichert (sind im Grunde zwei einfache Methoden) oder so benötigst für dein vorhaben eine mehr oder weniger komplexe Klasse. Je nachdem was du machen willst.
    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. ##

    Ah, ok. Wusste nicht mehr genau was ich alles da drin hatte. Wobei der Cache bei dem Projekt im Grunde nie notwendig wäre. Haha
    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. ##

    Da ist wer auf den Geschmack gekommen. :)
    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,

    also in meiner Anwenung will ich sowohl Einstellungen speicher, die der Benutzer verändern kann, als auch solche, die er nicht verändern kann.

    Habe ich das jetzt richtig verstanden?
    Ich habe ein SettingsViewModel.
    Dieses hohlt sich die Werte von dem SettingsModel Objet, was ich in der Singleton Klasse gespeichert habe.

    Doch nochmal meine Frage - wie komme ich im XAML jetzt an das SettingsViewModel, weil ich habe ja nicht jenes als DataContext.

    Hier ist übrigens das Projekt: https://github.com/florian03-1/SnippetLibraryPro

    Viele Grüße und Danke an alle Helfenden!
    Florian
    ----

    WebApps mit C#: Blazor

    florian03 schrieb:

    also in meiner Anwenung will ich sowohl Einstellungen speicher, die der Benutzer verändern kann, als auch solche, die er nicht verändern kann.

    Dann würde ich das gleich so in dein Model mit integrieren.

    Also eine Klasse SettingValue mit mindestens drei Eigenschaften. Key, Value und IsAutomatedValue. Evtl. dann noch DefaultValue damit der User später "zurücksetzen" könnte.
    Dann eine Helper Klasse oder DataAccessLayer der Settings wenn man so will. Kann Singleton sein oder vieleicht sogar ein Modul.

    Diese bekommt eine List(Of KeyValuePair) eine Methode "LoadData", eine Methode "GetSettingValue(key as string) as string" und eine "SetSettingValue(key as string, value as string)" sowie "SaveSettings".
    Fertig. Ich denke der Inhalte der Methoden ist kein Problem für dich.

    Wenn du nun ein ViewModel erstellst kannst du entweder mit fixen Properties arbeiten oder auch mit einer List(Of T) in welche du nur Settings lädst wo IsAutomatedValue auf False gesetzt ist.

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

    Hallo,

    alles klar, ist mir vom Verständnis her klar :thumbsup:

    Allerdings ist ja in dem SettingValue ja nur der Wert als String gespeichert...
    Wie mache ich des dann wenn ich im ViewModel z.B. einen Boolean brauche?

    Oder empfiehlst du mir es so umzusetzen, wie in deinem WPFNotes Projekt. Da ist es ja sehr schön gelöst - natürlich auch sehr kompliziert.
    Meinst du ich soll das mal versuchen @Nofear23m?

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor

    florian03 schrieb:

    Allerdings ist ja in dem SettingValue ja nur der Wert als String gespeichert...

    Was für dein Vorhaben im Moment auch völlig ausreicht. In einer XML ist im Grunde ja alles ein String oder?

    florian03 schrieb:

    Wie mache ich des dann wenn ich im ViewModel z.B. einen Boolean brauche?

    Dann hast du dort ein Boolean-Propertie.

    Bitte denke über den Tellerrand. Nur weil du irgendwo einen String zurückbekommst bedeutet das ja nicht das du in deinem ViewModel auch einen String haben musst.

    Angenommen du hast Die Methoden GetSetting(key as string) und SetSetting(key as string, value as string) um die Settings abzurufen und zu speichern.
    Bei einem Property in einem ViewModel kann das dann wie folgt aussehen:

    VB.NET-Quellcode

    1. Public Property MyBoolProp As Boolean
    2. Get
    3. Return cbool(GetValue("MeinKey1"))
    4. End Get
    5. Set
    6. SetValue("MeinKey1",value.ToString())
    7. End Get
    8. End Property


    Properties müssen nicht immer so aussehen wie die Standardvorlage. so kannst du dich spielen wie du willst. Ich hoffe das ist/war verständlich.

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

    Ja Klar! Jetzt hab ich es verstanden.

    Ich kann ja einfach den Boolean als String ins Property schreiben und ihn dann ganz unbedenklich wieder auslesen!

    Nofear23m schrieb:

    Bitte denke über den Tellerrand.

    Das ist oft ein großes Prolem bei mir - mir fallen so "einfache" Lösungen nicht ein, weil ich zu kompliziert denke!

    Vielen Dan und Grüße
    Florian
    ----

    WebApps mit C#: Blazor

    florian03 schrieb:

    Das ist oft ein großes Prolem bei mir

    Nene, so war das nicht gemeint. Das liegt nicht an dir. Das liegt einfach daran dan man Properties immer als das sieht was sie sind. Normale Properties, aber fast niemand denkt daran das diese ja einen Getter und Seter haben und ich hier alles damit machen kann. Das ist ne überaus geniale Sache.

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

    Hallo nochmal,

    wollte hier nur nochmal meine Umsetzung posten, vielleicht hilft sie ja dem ein oder anderen...

    SettingValue Klasse

    Das ist die SettingValue Klasse. Sie hat die vier oben, von @Nofear23m empfohlenen Eigenschaften.

    C#-Quellcode

    1. public class SettingValue
    2. {
    3. public string Key { get; set; }
    4. public string Value { get; set; }
    5. public string DefaultValue { get; set; }
    6. public bool IsAutomatedValue { get; set; }
    7. public override string ToString()
    8. {
    9. return $"Key: '{Key}', Value: '{Value}', DefaultValue: '{DefaultValue}', IsAutomatedValue: '{IsAutomatedValue.ToString()}'";
    10. }
    11. }



    SettingsContext Klasse

    Das ist die Klasse, die sich um das Speichern, Laden, Abrufen und halen der Settings kümmert. Sie hat eine List(Of SettingValue) und Mehtoden wie Save, Load, GetSetting, GetSettingValue, SetSetting, SetSettingValue.

    Außerdem habe ich mich in der Klasse darum gekümmert, dass die Settings beim ersten Verwenden erstellt werden (​CheckIfAllSettingsAviable).

    C#-Quellcode

    1. public class SettingsContext
    2. {
    3. private SettingsContext() // Damit kein Objekt angelegt werden kann
    4. {
    5. }
    6. public static SettingsContext Instance { get; } = new SettingsContext();
    7. public List<SettingValue> Settings { get; set; }
    8. public void Load(string specialPath = null)
    9. {
    10. try
    11. {
    12. string path = "";
    13. if (specialPath != null && File.Exists(specialPath)) path = specialPath;
    14. else path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\SnippetLibrary\\settings.sls";
    15. if (!File.Exists(path)) { Settings = new List<SettingValue>(); CheckIfAllSettingsAviable(); } //Wenn noch keine Settings da, dann settings mit Standartwerten erstellen
    16. else Settings = Serializer.LoadXML<List<SettingValue>>(path);
    17. }
    18. catch (Exception ex)
    19. {
    20. throw ex;
    21. }
    22. }
    23. public void Save(string specialPath = null)
    24. {
    25. CheckIfAllSettingsAviable();
    26. try
    27. {
    28. string path = "";
    29. FileInfo fInfo = new FileInfo(specialPath);
    30. if (specialPath != null && Directory.Exists(fInfo.DirectoryName)) path = specialPath;
    31. else path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\SnippetLibrary\\settings.sls";
    32. Serializer.SaveXML(path, Settings);
    33. }
    34. catch (Exception ex)
    35. {
    36. throw ex;
    37. }
    38. }
    39. public string GetSettingValue(string key)
    40. {
    41. return Settings.Find(x => x.Key == key).Value;
    42. }
    43. public SettingValue GetSetting(string key)
    44. {
    45. return Settings.Find(x => x.Key == key);
    46. }
    47. public void SetSettingValue(string key, string value)
    48. {
    49. Settings.Find(x => x.Key == key).Value = value;
    50. }
    51. public void SetSetting(SettingValue setting)
    52. {
    53. Settings.Remove( Settings.Find(x => x.Key == setting.Key));
    54. Settings.Add(setting);
    55. }
    56. public int CheckIfAllSettingsAviable()
    57. {
    58. if (Settings == null) Settings = new List<SettingValue>(); //Wenn settins noch gar nicht erstellt wurde...
    59. int unaviable = 0;
    60. List<SettingValue> allSettings = new List<SettingValue>();
    61. SettingValue ui_showIdUnderName = new SettingValue() { Key = "ui_schowIdUnderName", Value = Boolean.TrueString, DefaultValue = Boolean.TrueString, IsAutomatedValue = false }; allSettings.Add(ui_showIdUnderName);
    62. SettingValue startup_loadRecentDatabase = new SettingValue() { Key = "startup_loadRecentDatabase", Value = Boolean.FalseString, DefaultValue = Boolean.FalseString, IsAutomatedValue = false }; allSettings.Add(startup_loadRecentDatabase);
    63. SettingValue startup_recentFile = new SettingValue() { Key = "startup_recentFile", Value = "", DefaultValue = "", IsAutomatedValue = true }; allSettings.Add(startup_recentFile);
    64. foreach (SettingValue settingValue in allSettings)
    65. {
    66. bool found = false;
    67. foreach (SettingValue existingSetting in Settings)
    68. {
    69. if (settingValue.Key == existingSetting.Key) found = true;
    70. }
    71. if (found == false) { unaviable += 1; Settings.Add(settingValue);}
    72. }
    73. return unaviable;
    74. }
    75. }​



    Wenn Fragen sind, könnt ihr gerne fragen...
    ...und wenn es von den Experten noch Verbesserungen und Ideen für Optimierung gibt können diese sie gerne nennen! :thumbsup:

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor