C# - 在 WPF 应用程序中保存用户设置的方法?

Posted

技术标签:

【中文标题】C# - 在 WPF 应用程序中保存用户设置的方法?【英文标题】:C# - approach for saving user settings in a WPF application? 【发布时间】:2011-04-16 14:47:21 【问题描述】:

对于在 WPF 窗口(桌面)应用程序中持久化用户设置,您推荐什么方法?请注意,这个想法是用户可以在运行时更改他们的设置,然后可以关闭应用程序,然后在以后启动应用程序时,应用程序将使用当前设置。实际上,它看起来好像应用程序设置没有改变。

Q1 - 数据库或其他方法?我确实有一个我将使用的 sqlite 数据库,因此在数据库中使用表与任何方法一样好?

Q2 - If Database:什么数据库表设计?一个表,其中包含一个可能具有的不同数据类型的列(例如stringlongDateTime 等),或者只是一个表,其中包含一个值的字符串,您必须在该表上对值进行序列化和反序列化?我认为第一个会更容易,如果没有很多设置,开销就不会太多?

Q3 - 可以使用应用程序设置吗?如果是这样,是否需要任何特殊任务才能在此处启用持久性?在这种情况下,在应用程序设置设计器中使用“默认”值会发生什么情况?默认设置会覆盖在运行应用程序之间保存的任何设置吗? (或者您是否需要不使用默认值)

【问题讨论】:

@All new users 如果您可以发布单独的问题而不是将您的问题合并为一个问题,则最好。这样,它可以帮助人们回答您的问题,也可以帮助其他人至少寻找您的一个问题。谢谢! 【参考方案1】:

您可以为此使用Application Settings,考虑到读取和写入设置所消耗的时间(特别是如果您使用网络服务),使用数据库并不是最佳选择。

这里有几个链接解释了如何实现这一点并在 WPF 中使用它们 -

User Settings in WPF

Quick WPF Tip: How to bind to WPF application resources and settings?

A Configurable Window for WPF

【讨论】:

【参考方案2】:

更新:现在我会使用 JSON。

我也更喜欢对文件进行序列化。 XML 文件几乎可以满足所有要求。您可以使用 ApplicationSettings 内置,但它们有一些限制和定义但(对我而言)它们存储的非常奇怪的行为。我经常使用它们并且它们有效。但是,如果您想完全控制它们的存储方式和存储位置,我会使用另一种方法。

    使用您的所有设置在某处创建课程。我把它命名为MySettings 实现保存和读取以实现持久性 在您的应用程序代码中使用它们

优点:

非常简单的方法。 设置一个类。加载。保存。 您的所有设置都是类型安全的。 您可以根据需要简化或扩展逻辑(版本控制、每个用户的多个配置文件等) 它在任何情况下都能很好地工作(数据库、WinForms、WPF、服务等...) 您可以定义存储 XML 文件的位置。 您可以通过代码或手动找到并操作它们 它适用于我能想象到的任何部署方法。

缺点: - 您必须考虑存储设置文件的位置。 (但你可以只使用你的安装文件夹)

这是一个简单的例子(未测试)-

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;
        
    

这里是如何使用它。只需检查用户设置是否存在,就可以加载默认值或用用户设置覆盖它们:

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);
    

也许有人会受到这种方法的启发。这就是我多年来一直这样做的方式,我对此感到非常满意。

【讨论】:

不利的是,您不再拥有设置设计器,因此当两者都可以使用时,用户友好性会降低。 完全同意“存储它们的非常奇怪的行为”,正因为如此,我正在使用您的方法。 +1。 如果您有新问题,请点击 按钮提出。【参考方案3】:

您可以将您的设置信息作为Strings 的XML 存储在Settings.Default 中。创建一些类来存储您的配置数据并确保它们是[Serializable]。然后,使用以下帮助程序,您可以将这些对象的实例——或它们的List&lt;T&gt;(或数组T[] 等)序列化为String。将这些不同字符串中的每一个存储在 WPF 应用程序的 Settings 中各自的 Settings.Default 插槽中。

要在应用程序下次启动时恢复对象,请将感兴趣的Settings 字符串和Deserialize 读取为预期的类型T(这一次必须明确指定为Deserialize&lt;T&gt; 的类型参数) .

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);

【讨论】:

【参考方案4】:

解决这个问题的最典型的方法是:隔离存储。

将您的控件状态序列化为 XML 或其他格式(如果您使用 WPF 保存依赖属性,则尤其容易),然后将文件保存到用户的隔离存储中。

如果您确实想采用应用设置路线,我自己曾尝试过类似的方法...尽管可以轻松调整以下方法以使用独立存储:

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();
    

.....和....

  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);
        

【讨论】:

【参考方案5】:

除了数据库,您还可以有以下选项来保存用户相关设置

    HKEY_CURRENT_USER 下的注册表

    AppData文件夹中的文件中

    在 WPF 中使用 Settings 文件并将其范围设置为用户

【讨论】:

建议 1 是应用程序减慢 Windows 的原因,最好不要用 IMO 文件中更好的方法来填充注册表项。 @Console,在磁盘上写入文件会减慢(磨损)SSD,在数据库中写入数据会减慢数据库的速度。那你的选择是什么? Windows 注册表打算用作保存设置的地方之一。 你说得对,我认为如果每个应用程序都在那里保存数百个用户首选项,注册表有一些缺点是很重要的。 @Sinatr 最初,注册表是为此目的而设计的……但它的设计目的不是处理大量数据,因此在历史上的某个时刻,微软建议停止使用它。据我所知,Windows 在登录时会加载整个注册表,并且还会复制用于漫游或能够在重大崩溃后加载最后一次已知的良好配置。因此,即使应用程序从未使用过,使用注册表也会影响系统。 注册表也有大小限制,如果它仍然被应用程序使用,在大多数计算机上可能会超过这个限制。注册表是在系统内存 (MB) 比计算机内存 (GB) 少的时候设计的。注册表的设计并没有那么大,因此即使限制增加了,它也没有针对我们当前的需求进行优化。【参考方案6】:

根据我的经验,将所有设置存储在数据库表中是最好的解决方案。甚至不用担心性能。今天的数据库速度很快,可以轻松地将数千列存储在一个表中。我很难学会这一点 - 在我序列化/反序列化之前 - 噩梦。将其存储在本地文件或注册表中存在一个大问题 - 如果您必须支持您的应用程序并且计算机已关闭 - 用户不在它前面 - 您无能为力....如果设置在数据库中 - 您可以更改了它们和中提琴,更不用说您可以比较设置....

【讨论】:

当连接不可用时,远程存储它们也有一个大问题......许多为在线工作而编写的应用程序在离线工作时体验不太理想,有时甚至有一些错误导致某些功能不能离线工作,即使它除了“窥探”设备的使用方式之外不会产生任何影响。【参考方案7】:

您可以使用 SQLite,这是一个小型、快速、独立、功能齐全的 SQL 数据库引擎。在尝试设置文件和 XML 文件方法后,我个人推荐它。

安装 NuGet 包 System.Data.SQLite 这是 SQLite 的 ADO.NET 提供程序。 该软件包包括对 LINQ 和实体框架的支持 总体而言,您可以在设置窗口中使用此类支持功能做很多事情。

1.安装SQLite 2.创建你的数据库文件 3.创建表格以保存您的设置 4.访问应用程序中的数据库文件以读取和编辑设置。

我觉得这种方法对应用程序设置非常有帮助,因为我可以调整数据库并利用 ADO.Net 和 LINQ 功能

【讨论】:

【参考方案8】:

我通常通过定义自定义 [Serializable] 设置类并将其简单地序列化到磁盘来执行此类操作。在您的情况下,您可以轻松地将其作为字符串 blob 存储在 SQLite 数据库中。

【讨论】:

【参考方案9】:

    在我工作过的所有地方,由于应用程序支持,数据库一直是强制性的。正如 Adam 所说,用户可能不在办公桌前,或者机器可能已关闭,或者您可能想要快速更改某人的配置或为新加入者分配默认(或团队成员)配置。

    如果设置可能会随着应用程序新版本的发布而增长,您可能希望将数据存储为 blob,然后应用程序可以对其进行反序列化。如果您使用诸如发现模块的 Prism 之类的东西,这将特别有用,因为您不知道模块将返回什么设置。 Blob 可以由用户名/机器组合键作为键控。这样您就可以为每台机器设置不同的设置。

    我没有过多地使用内置的设置类,所以我将放弃评论。 :)

【讨论】:

【参考方案10】:

我想为我的 VB.net 桌面 WPF 应用程序使用基于类的 xml 控制文件。上面的代码可以做到这一点非常好,让我朝着正确的方向前进。如果有人正在寻找 VB.net 解决方案,这里是我构建的类:

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

【讨论】:

以上是关于C# - 在 WPF 应用程序中保存用户设置的方法?的主要内容,如果未能解决你的问题,请参考以下文章

WPF C# 输入框

WPF 在控件上淡出

如何在 C# 中的 WPF 应用程序中保存图像

WPF 保存设置

C#从字节数组启动另一个WPF程序[重复]

请教c# winform,权限设计问题