c # - podejście do zapisywania ustawień użytkownika w aplikacji WPF?

85

Jakie podejście jest zalecane do utrwalania ustawień użytkownika w aplikacji WPF Windows (komputerowej)? Zwróć uwagę, że chodzi o to, aby użytkownik mógł zmienić swoje ustawienia w czasie wykonywania, a następnie zamknąć aplikację, a następnie podczas późniejszego uruchamiania aplikacji aplikacja użyje bieżących ustawień. Skutecznie wtedy będzie wyglądać tak, jakby ustawienia aplikacji się nie zmieniły.

Q1 - Baza danych czy inne podejście? Mam bazę danych sqlite, z której i tak będę korzystać, więc użycie tabeli w bazie danych byłoby tak dobre, jak każde inne podejście?

P2 - Jeśli baza danych: Jaki projekt tabeli bazy danych? Jedna tabela z kolumnami dla różnych typów danych, które mogą mieć jeden (np string, long, DateTimeetc) lub po prostu tabela z ciągiem dla wartości, na których trzeba Wartości serialize i de-serialize? Myślę, że pierwsze byłoby łatwiejsze, a jeśli nie ma wielu ustawień, narzut nie jest duży?

P3 - Czy można w tym celu użyć ustawień aplikacji? Jeśli tak, czy są jakieś specjalne zadania wymagane, aby umożliwić wytrwałość w tym miejscu? Co by się stało z użyciem „domyślnej” wartości w projektancie ustawień aplikacji w tym przypadku? Czy ustawienia domyślne zastąpią ustawienia zapisane między uruchomieniem aplikacji? (czy nie musisz używać wartości domyślnej)

Greg
źródło
@ Wszyscy nowi użytkownicy Najlepiej, jeśli możesz zadawać oddzielne pytania zamiast łączyć je w jedno. W ten sposób pomaga ludziom odpowiadającym na Twoje pytanie, a także innym szukającym przynajmniej jednego z Twoich pytań. Dzięki!
Hille

Odpowiedzi:

23

Aktualizacja : Obecnie używałbym JSON.

Wolę też przejść z serializacją do pliku. Pliki XML spełniają większość wymagań. Możesz użyć ApplicationSettingswbudowanej wersji, ale mają one pewne ograniczenia i określone, ale (dla mnie) bardzo dziwne zachowanie, gdy są przechowywane. Używałem ich dużo i działają. Ale jeśli chcesz mieć pełną kontrolę nad tym, jak i gdzie są przechowywane, używam innego podejścia.

  1. Utwórz zajęcia Gdzieś ze wszystkimi swoimi ustawieniami. Nazwałam toMySettings
  2. Zaimplementuj zapisywanie i odczytywanie, aby zachować trwałość
  3. Użyj ich w kodzie aplikacji

Zalety:

  • Bardzo proste podejście.
  • Jedna klasa dla ustawień. Załaduj. Zapisać.
  • Wszystkie Twoje ustawienia są bezpieczne.
  • Możesz uprościć lub rozszerzyć logikę do swoich potrzeb (przechowywanie wersji, wiele profili na użytkownika itp.)
  • Działa bardzo dobrze w każdym przypadku (baza danych, WinForms, WPF, usługa itp.)
  • Możesz zdefiniować miejsce przechowywania plików XML.
  • Możesz je znaleźć i manipulować nimi za pomocą kodu lub ręcznie
  • Działa dla każdej metody wdrażania, jaką mogę sobie wyobrazić.

Wady: - Musisz pomyśleć o tym, gdzie przechowywać pliki ustawień. (Ale możesz po prostu użyć folderu instalacyjnego)

Oto prosty przykład (nie testowany) -

public class MySettings
{
    public string Setting1 { get; set; }
    public List<string> Setting2 { get; set; }

    public void Save(string filename)
    {
        using (StreamWriter sw = new StreamWriter(filename))
        {
            XmlSerializer xmls = new XmlSerializer(typeof(MySettings));
            xmls.Serialize(sw, this);
        }
    }
    public MySettings Read(string filename)
    {
        using (StreamReader sw = new StreamReader(filename))
        {
            XmlSerializer xmls = new XmlSerializer(typeof(MySettings));
            return xmls.Deserialize(sw) as MySettings;
        }
    }
}

A oto jak z niego korzystać. Możliwe jest załadowanie wartości domyślnych lub nadpisanie ich ustawieniami użytkownika, po prostu sprawdzając, czy istnieją ustawienia użytkownika:

public class MyApplicationLogic
{
    public const string UserSettingsFilename = "settings.xml";
    public string _DefaultSettingspath = 
        Assembly.GetEntryAssembly().Location + 
        "\\Settings\\" + UserSettingsFilename;

    public string _UserSettingsPath = 
        Assembly.GetEntryAssembly().Location + 
        "\\Settings\\UserSettings\\" + 
        UserSettingsFilename;

    public MyApplicationLogic()
    {
        // if default settings exist
        if (File.Exists(_UserSettingsPath))
            this.Settings = Settings.Read(_UserSettingsPath);
        else
            this.Settings = Settings.Read(_DefaultSettingspath);
    }
    public MySettings Settings { get; private set; }

    public void SaveUserSettings()
    {
        Settings.Save(_UserSettingsPath);
    }
}

może ktoś zainspiruje się tym podejściem. Tak to robię teraz od wielu lat i jestem z tego całkiem zadowolony.

Mata
źródło
1
Wadą byłoby to, że nie masz już projektanta ustawień, więc jest nieco mniej przyjazny dla użytkownika, gdy oba działają.
Phil 1970,
3
Całkowicie zgadzam się z „bardzo dziwnym zachowaniem, w którym są przechowywane”. Właśnie dlatego używam twojego podejścia. +1.
Hannish
Jeśli masz NOWE pytanie, zadaj je, klikając przycisk Zadaj pytanie .
Mat
12

Możesz przechowywać informacje o ustawieniach Stringsw formacie XML w pliku Settings.Default. Utwórz klasy do przechowywania danych konfiguracyjnych i upewnij się, że tak jest [Serializable]. Następnie, korzystając z następujących pomocników, możesz serializować wystąpienia tych obiektów - lub List<T>(lub tablic T[]itp.) Z nich - do String. Przechowuj każdy z tych różnych ciągów we własnym odpowiednim Settings.Defaultgnieździe w aplikacji WPF Settings.

Aby odzyskać obiekty przy następnym uruchomieniu aplikacji, przeczytaj Settingsinteresujący Cię ciąg znaków i Deserializeoczekiwany typ T(który tym razem musi być jawnie określony jako argument typu Deserialize<T>).

public static String Serialize<T>(T t)
{
    using (StringWriter sw = new StringWriter())
    using (XmlWriter xw = XmlWriter.Create(sw))
    {
        new XmlSerializer(typeof(T)).Serialize(xw, t);
        return sw.GetStringBuilder().ToString();
    }
}

public static T Deserialize<T>(String s_xml)
{
    using (XmlReader xw = XmlReader.Create(new StringReader(s_xml)))
        return (T)new XmlSerializer(typeof(T)).Deserialize(xw);
}
Glenn Slayden
źródło
6

Najbardziej typowym podejściem do tego pytania jest: izolowana pamięć masowa.

Serializuj stan kontroli do formatu XML lub innego formatu (szczególnie łatwo, jeśli zapisujesz właściwości zależności za pomocą WPF), a następnie zapisz plik w izolowanym magazynie użytkownika.

Jeśli chcesz przejść do ścieżki ustawiania aplikacji, w pewnym momencie próbowałem czegoś podobnego ... chociaż poniższe podejście można łatwo dostosować do korzystania z izolowanej pamięci masowej:

class SettingsManager
{
    public static void LoadSettings(FrameworkElement sender, Dictionary<FrameworkElement, DependencyProperty> savedElements)
    {
        EnsureProperties(sender, savedElements);
        foreach (FrameworkElement element in savedElements.Keys)
        {
            try
            {
                element.SetValue(savedElements[element], Properties.Settings.Default[sender.Name + "." + element.Name]);
            }
            catch (Exception ex) { }
        }
    }

    public static void SaveSettings(FrameworkElement sender, Dictionary<FrameworkElement, DependencyProperty> savedElements)
    {
        EnsureProperties(sender, savedElements);
        foreach (FrameworkElement element in savedElements.Keys)
        {
            Properties.Settings.Default[sender.Name + "." + element.Name] = element.GetValue(savedElements[element]);
        }
        Properties.Settings.Default.Save();
    }

    public static void EnsureProperties(FrameworkElement sender, Dictionary<FrameworkElement, DependencyProperty> savedElements)
    {
        foreach (FrameworkElement element in savedElements.Keys)
        {
            bool hasProperty =
                Properties.Settings.Default.Properties[sender.Name + "." + element.Name] != null;

            if (!hasProperty)
            {
                SettingsAttributeDictionary attributes = new SettingsAttributeDictionary();
                UserScopedSettingAttribute attribute = new UserScopedSettingAttribute();
                attributes.Add(attribute.GetType(), attribute);

                SettingsProperty property = new SettingsProperty(sender.Name + "." + element.Name,
                    savedElements[element].DefaultMetadata.DefaultValue.GetType(), Properties.Settings.Default.Providers["LocalFileSettingsProvider"], false, null, SettingsSerializeAs.String, attributes, true, true);
                Properties.Settings.Default.Properties.Add(property);
            }
        }
        Properties.Settings.Default.Reload();
    }
}

.....i....

  Dictionary<FrameworkElement, DependencyProperty> savedElements = new Dictionary<FrameworkElement, DependencyProperty>();

public Window_Load(object sender, EventArgs e) {
           savedElements.Add(firstNameText, TextBox.TextProperty);
                savedElements.Add(lastNameText, TextBox.TextProperty);

            SettingsManager.LoadSettings(this, savedElements);
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            SettingsManager.SaveSettings(this, savedElements);
        }
Jeff
źródło
5

Oprócz bazy danych możesz również mieć następujące opcje zapisywania ustawień związanych z użytkownikiem

  1. rejestr pod HKEY_CURRENT_USER

  2. w pliku w AppDatafolderze

  3. używając Settingspliku w WPF i ustawiając jego zakres jako User

ajay_whiz
źródło
2
Sugestia 1 to powód, dla którego aplikacje spowalniają system Windows, lepiej nie zapełniać kluczy rejestru czymś lepiej wykonanym w pliku IMO.
Konsola
1
@Console, zapisywanie pliku na dysku spowalnia (zużywa się) SSD, zapisywanie danych w bazie danych spowalnia bazę danych. Jaka jest twoja opcja? Rejestr systemu Windows ma służyć jako jedno z miejsc zapisywania ustawień .
Sinatr
1
Masz rację, myślę, że ważne jest, aby wspomnieć, że rejestr ma pewne wady, jeśli każda aplikacja zapisuje tam setki preferencji użytkownika.
Konsola
@Sinatr Pierwotnie rejestr był przeznaczony do tego celu ... ale nie był przeznaczony do obsługi dużej ilości danych, więc w pewnym momencie historii Microsoft zalecił zaprzestanie jego używania. O ile wiem, system Windows ładuje cały rejestr podczas logowania, a także kopiuje go do roamingu lub w celu załadowania ostatniej znanej dobrej konfiguracji po poważnej awarii. W ten sposób korzystanie z rejestru wpływa na system, nawet jeśli aplikacja nigdy nie jest używana.
Phil1970,
Rejestr ma również limit rozmiaru i jeśli nadal byłby używany przez aplikację, prawdopodobnie ten limit zostałby przekroczony na większości komputerów. Rejestr został zaprojektowany w czasie, gdy system ma mniej pamięci w MB niż komputer ma obecnie w GB. Rejestr nie został zaprojektowany tak, aby był tak duży, a zatem mimo że limity wzrosły, nie jest zoptymalizowany pod kątem naszych bieżących potrzeb.
Phil1970,
3

Z mojego doświadczenia wynika, że ​​najlepszym rozwiązaniem jest przechowywanie wszystkich ustawień w tabeli bazy danych. Nie martw się nawet o wydajność. Dzisiejsze bazy danych są szybkie i mogą z łatwością przechowywać tysiące kolumn w tabeli. Nauczyłem się tego na własnej skórze - zanim zacząłem serilizować / deserializować - koszmar. Przechowywanie go w lokalnym pliku lub rejestrze ma jeden duży problem - jeśli musisz obsługiwać aplikację, a komputer jest wyłączony - użytkownik nie jest przed nią - nic nie możesz zrobić ... jeśli ustawienia są w DB - możesz zmieniłem je i altówkę nie wspominając już o tym, że można porównać ustawienia ....

Adam
źródło
Przechowywanie ich zdalnie wiąże się również z jednym dużym problemem, gdy połączenie nie jest dostępne ... Wiele aplikacji napisanych do pracy online ma mniej niż idealne wrażenia podczas pracy w trybie offline lub czasami ma nawet błędy, które powodują, że niektóre funkcje nie działają w trybie offline, nawet jeśli powinny nie mają żadnego innego wpływu niż „szpiegowanie”, w jaki sposób urządzenie jest używane.
Phil 1970,
1

Zazwyczaj robię tego typu rzeczy, definiując Serializableklasę ustawień niestandardowych [ ] i po prostu serializując ją na dysk. W twoim przypadku możesz równie łatwo przechowywać go jako obiekt typu blob w bazie danych SQLite.

Doobi
źródło
0
  1. We wszystkich miejscach, w których pracowałem, baza danych była obowiązkowa ze względu na obsługę aplikacji. Jak powiedział Adam, użytkownik może nie być przy swoim biurku lub maszyna może być wyłączona, albo możesz chcieć szybko zmienić czyjąś konfigurację lub przypisać nowemu stolarzowi domyślną konfigurację (lub członka zespołu).

  2. Jeśli ustawienia prawdopodobnie wzrosną wraz z wydaniem nowych wersji aplikacji, możesz chcieć przechowywać dane jako obiekty blob, które mogą być następnie deserializowane przez aplikację. Jest to szczególnie przydatne, jeśli używasz czegoś takiego jak Prism, który wykrywa moduły, ponieważ nie możesz wiedzieć, jakie ustawienia moduł zwróci. Obiekty blob mogą być wpisywane za pomocą klucza złożonego nazwy użytkownika / komputera. W ten sposób możesz mieć różne ustawienia dla każdego komputera.

  3. Nie korzystałem zbytnio z wbudowanej klasy ustawień, więc powstrzymam się od komentowania. :)

Will_Piuvans
źródło
0

Chciałem użyć pliku kontrolnego XML opartego na klasie dla mojej aplikacji WPF na pulpicie VB.net. Powyższy kod, aby zrobić to wszystko w jednym, jest doskonały i skierował mnie we właściwym kierunku. Na wypadek, gdyby ktoś szukał rozwiązania VB.net, oto klasa, którą zbudowałem:

Imports System.IO
Imports System.Xml.Serialization

Public Class XControl

Private _person_ID As Integer
Private _person_UID As Guid

'load from file
Public Function XCRead(filename As String) As XControl
    Using sr As StreamReader = New StreamReader(filename)
        Dim xmls As New XmlSerializer(GetType(XControl))
        Return CType(xmls.Deserialize(sr), XControl)
    End Using
End Function

'save to file
Public Sub XCSave(filename As String)
    Using sw As StreamWriter = New StreamWriter(filename)
        Dim xmls As New XmlSerializer(GetType(XControl))
        xmls.Serialize(sw, Me)
    End Using
End Sub

'all the get/set is below here

Public Property Person_ID() As Integer
    Get
        Return _person_ID
    End Get
    Set(value As Integer)
        _person_ID = value
    End Set
End Property

Public Property Person_UID As Guid
    Get
        Return _person_UID
    End Get
    Set(value As Guid)
        _person_UID = value
    End Set
End Property

End Class
Scott
źródło