如何在Windows窗体应用程序中保存应用程序设置?


581

我想要实现的非常简单:我有一个Windows Forms(.NET 3.5)应用程序,该应用程序使用读取信息的路径。用户可以使用我提供的选项表单来修改此路径。

现在,我想将路径值保存到文件中以备后用。这将是保存到该文件的众多设置之一。该文件将直接位于应用程序文件夹中。

我了解三个选项可供选择:

  • ConfigurationSettings文件(appname.exe.config)
  • 登记处
  • 自定义XML文件

我读到没有预见到.NET配置文件来将值保存回它。至于注册表,我想尽可能地远离它。

这是否意味着我应该使用自定义XML文件保存配置设置?

如果是这样,我想查看该代码示例(C#)。

我已经看到了有关该主题的其他讨论,但是对我来说仍然不清楚。


这是.NET WinForms应用程序吗?如果是这样,您正在开发什么版本的.NET?
波特曼2009年

1
是的,它是.NET Framework 3.5版WinForms应用程序。
在2009年

1
您是否需要保存密码或机密值?可能需要任何加密
Kiquenet

Answers:


593

如果使用Visual Studio,则很容易获得持久性设置。在解决方案资源管理器中右键单击该项目,然后选择“属性”。选择设置选项卡,如果设置不存在,请单击超链接。

使用设置选项卡创建应用程序设置。Visual Studio创建文件Settings.settingsSettings.Designer.settings并且包含SettingsApplicationSettingsBase继承 的单例类。您可以从代码中访问此类,以读取/写入应用程序设置:

Properties.Settings.Default["SomeProperty"] = "Some Value";
Properties.Settings.Default.Save(); // Saves settings in application configuration file

此技术适用于控制台,Windows窗体和其他项目类型。

请注意,您需要设置设置的scope属性。如果选择“应用程序范围”,则Settings.Default。<您的属性>将为只读。

参考:如何:使用C#在运行时编写用户设置 -Microsoft Docs


2
如果我有解决方案,这将适用于整个解决方案还是每个项目?
franko_camron 2011年

8
@四:我这里有一个.NET 4.0 WinApp项目,我的SomeProperty不是只读的。Settings.Default.SomeProperty = 'value'; Settings.Default.Save();奇迹般有效。还是那是因为我有用户设置?
doekman 2012年

4
@四:当我将设置从用户更改为应用程序范围并保存文件时,我在生成的代码中看到设置器消失了。客户配置文件4.0也会发生这种情况
doekman 2012年

3
@Four:很棒的链接,尽管您声明Settings.Default.Save()什么都不做是不正确的。正如@aku在答案中指出的那样,应用程序作用域设置是只读的:保存对它们无效。使用该自定义PortableSettingsProvider将用户范围的设置保存到exe所在的app.config中,而不是用户的AppData文件夹中的exe。不,通常不好,但是我在开发过程中使用它来使用相同的设置(从编译到编译)(没有,每次编译它们都会使用新的唯一用户文件夹)。
minnow 2012年

7
到目前为止,使用.NET 3.5,您似乎可以只使用Settings.Default.SomeProperty来分配值并获得强大的类型转换。另外,为了节省其他人的时间(花点时间来弄清楚这一点),您需要键入Properties.Settings.Default,或使用YourProjectNameSpace.Settings添加到文件顶部。没有单独定义/设置“设置”。
eselk

94

如果您打算保存到与可执行文件位于同一目录中的文件,那么以下是一个使用JSON格式的不错的解决方案:

using System;
using System.IO;
using System.Web.Script.Serialization;

namespace MiscConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            MySettings settings = MySettings.Load();
            Console.WriteLine("Current value of 'myInteger': " + settings.myInteger);
            Console.WriteLine("Incrementing 'myInteger'...");
            settings.myInteger++;
            Console.WriteLine("Saving settings...");
            settings.Save();
            Console.WriteLine("Done.");
            Console.ReadKey();
        }

        class MySettings : AppSettings<MySettings>
        {
            public string myString = "Hello World";
            public int myInteger = 1;
        }
    }

    public class AppSettings<T> where T : new()
    {
        private const string DEFAULT_FILENAME = "settings.json";

        public void Save(string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(this));
        }

        public static void Save(T pSettings, string fileName = DEFAULT_FILENAME)
        {
            File.WriteAllText(fileName, (new JavaScriptSerializer()).Serialize(pSettings));
        }

        public static T Load(string fileName = DEFAULT_FILENAME)
        {
            T t = new T();
            if(File.Exists(fileName))
                t = (new JavaScriptSerializer()).Deserialize<T>(File.ReadAllText(fileName));
            return t;
        }
    }
}

是的,如果要保存到另一个目录,请将DEFAULT_FILENAME更改为绝对路径。我认为,最常见的是将文件保存到应用程序所在的目录或子目录中(如果您不将它们保存到注册表中)。
Trevor 2014年

哦,也许更好的选择是将设置文件存储在用户的appdata文件夹中。
Trevor

1
无需更改DEFAULT_FILENAME,只需致电settings.Save(theFileToSaveTo); 总而言之,DEFAULT_FILENAME应该是一个常数。如果您需要读写属性,请设置一个属性并将其构造函数设置为DEFAULT_FILENAME。然后将默认参数值设为null,进行测试,然后将您的属性用作默认值。它输入的内容更多一些,但为您提供了更标准的界面。
杰西·奇斯霍尔姆

10
System.Web.Extensions.dll如果尚未参考,则需要参考。
TEK

9
我已经基于此答案进行了很多改进,创建了一个完整的库,并使其在nuget中可用:github.com/Nucs/JsonSettings
NucS

67

注册表是不行的。您不确定使用您的应用程序的用户是否具有足够的权限来写入注册表。

您可以使用 app.config文件保存应用程序级设置(对于使用您的应用程序的每个用户都相同)。

我会将用户特定的设置存储在XML文件中,该文件将保存在独立存储SpecialFolder.ApplicationData中。目录中。

紧接着,从.NET 2.0开始,可以将值存储回app.config文件中。


8
但是,如果要按登录名/用户设置,请使用注册表。
thenonhacker

19
注册表是不portble
KB。

10
@thenonhacker:或使用Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
肯尼·曼

4
可以将用户注册表写入(很多程序都在其中写入信息,并且用户权限永远不会成为问题)。与使用设置相比,使用注册表的优势在于,如果您有多个共享同一文件夹的应用程序(例如,安装程序和应用程序),则它们将不会共享相同的设置。
凯斯蒂(Kesty)

3
注册表的主要缺点是很难将设置导出/复制到其他PC。但是我不同意“您不确定使用您的应用程序的用户是否具有足够的权限来写入注册表”-在HKEY_CURRENT_USER中,您始终具有写入权限。可以拒绝它,但是当前用户(所有可能的TEMP文件夹等)也无法访问文件系统。
i486

20

ApplicationSettings类不支持设置保存到的app.config文件。这很大程度上是设计使然。使用正确保护的用户帐户运行的应用程序(例如Vista UAC)没有对该程序的安装文件夹的写访问权。

您可以与ConfigurationManager同班同学打架。但是最简单的解决方法是进入“设置”设计器,并将设置的范围更改为“用户”。如果这会造成麻烦(例如,该设置与每个用户有关),则应将“选项”功能放在单独的程序中,以便您可以请求特权提升提示。或放弃使用设置。


您能再说一遍吗?要求提升以编写app.config或编写一个单独的应用程序,该应用程序将遍历所有用户的家用计算机,查找user.config并进行编辑?
CannibalSmith,2009年

2
单独的程序需要一个清单来要求海拔。Google的“ asinvoker requireadministrator”可以找到正确的语法。编辑user.config不切实际,也没有必要。
汉斯·帕桑

18

Registry / configurationSettings / XML参数似乎仍然非常活跃。随着技术的进步,我已经全部使用了它们,但是我最喜欢的是基于Threed的系统Isolated Storage相结合。

以下示例允许将名为属性的对象存储到隔离存储中的文件中。如:

AppSettings.Save(myobject, "Prop1,Prop2", "myFile.jsn");

可以使用以下方法恢复属性:

AppSettings.Load(myobject, "myFile.jsn");

这只是一个示例,并不代表最佳实践。

internal static class AppSettings
{
    internal static void Save(object src, string targ, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = src.GetType();

        string[] paramList = targ.Split(new char[] { ',' });
        foreach (string paramName in paramList)
            items.Add(paramName, type.GetProperty(paramName.Trim()).GetValue(src, null));

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify.
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Create, storage))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                writer.Write((new JavaScriptSerializer()).Serialize(items));
            }

        }
        catch (Exception) { }   // If fails - just don't use preferences
    }

    internal static void Load(object tar, string fileName)
    {
        Dictionary<string, object> items = new Dictionary<string, object>();
        Type type = tar.GetType();

        try
        {
            // GetUserStoreForApplication doesn't work - can't identify
            // application unless published by ClickOnce or Silverlight
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForAssembly();
            using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(fileName, FileMode.Open, storage))
            using (StreamReader reader = new StreamReader(stream))
            {
                items = (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(reader.ReadToEnd());
            }
        }
        catch (Exception) { return; }   // If fails - just don't use preferences.

        foreach (KeyValuePair<string, object> obj in items)
        {
            try
            {
                tar.GetType().GetProperty(obj.Key).SetValue(tar, obj.Value, null);
            }
            catch (Exception) { }
        }
    }
}

1
还是更好;使用DataContractJsonSerializer
Boczek

16

我想分享我为此建立的图书馆。这是一个很小的库,但是相对于.settings文件有很大的改进(IMHO)。

该库称为Jot(GitHub)。这是我撰写的有关代码项目的旧文章

这是您使用它来跟踪窗口的大小和位置的方式:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

与.settings文件相比,它的好处是:代码少得多,而且出错率也少得多,因为您只需提及每个属性一次

对于设置文件,您需要五次提及每个属性:一次是在您明确创建属性时提及,另一次是在代码中四次来回复制值。

存储,序列化等都是完全可配置的。当目标对象由IoC创建时容器,您可以[将其连接] [],以便它自动将跟踪应用于它解析的所有对象,因此使属性持久化所需要做的就是拍一个[Trackable]属性就可以了。

它是高度可配置的,您可以配置:-当数据持久保存并应用到全局或每个跟踪对象时-序列化的方式-存储位置(例如文件,数据库,在线,隔离存储,注册表)-可以取消应用/保留属性的数据

相信我,图书馆是一流的!


14

一种简单的方法是使用配置数据对象,将其与应用程序的名称一起另存为XML文件在本地文件夹中,并在启动时将其读回。

这是存储表单的位置和大小的示例。

配置数据对象是强类型的,并且易于使用:

[Serializable()]
public class CConfigDO
{
    private System.Drawing.Point m_oStartPos;
    private System.Drawing.Size m_oStartSize;

    public System.Drawing.Point StartPos
    {
        get { return m_oStartPos; }
        set { m_oStartPos = value; }
    }

    public System.Drawing.Size StartSize
    {
        get { return m_oStartSize; }
        set { m_oStartSize = value; }
    }
}

用于保存和加载的经理类:

public class CConfigMng
{
    private string m_sConfigFileName = System.IO.Path.GetFileNameWithoutExtension(System.Windows.Forms.Application.ExecutablePath) + ".xml";
    private CConfigDO m_oConfig = new CConfigDO();

    public CConfigDO Config
    {
        get { return m_oConfig; }
        set { m_oConfig = value; }
    }

    // Load configuration file
    public void LoadConfig()
    {
        if (System.IO.File.Exists(m_sConfigFileName))
        {
            System.IO.StreamReader srReader = System.IO.File.OpenText(m_sConfigFileName);
            Type tType = m_oConfig.GetType();
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            object oData = xsSerializer.Deserialize(srReader);
            m_oConfig = (CConfigDO)oData;
            srReader.Close();
        }
    }

    // Save configuration file
    public void SaveConfig()
    {
        System.IO.StreamWriter swWriter = System.IO.File.CreateText(m_sConfigFileName);
        Type tType = m_oConfig.GetType();
        if (tType.IsSerializable)
        {
            System.Xml.Serialization.XmlSerializer xsSerializer = new System.Xml.Serialization.XmlSerializer(tType);
            xsSerializer.Serialize(swWriter, m_oConfig);
            swWriter.Close();
        }
    }
}

现在,您可以创建一个实例,并在表单的load和close事件中使用它:

    private CConfigMng oConfigMng = new CConfigMng();

    private void Form1_Load(object sender, EventArgs e)
    {
        // Load configuration
        oConfigMng.LoadConfig();
        if (oConfigMng.Config.StartPos.X != 0 || oConfigMng.Config.StartPos.Y != 0)
        {
            Location = oConfigMng.Config.StartPos;
            Size = oConfigMng.Config.StartSize;
        }
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        // Save configuration
        oConfigMng.Config.StartPos = Location;
        oConfigMng.Config.StartSize = Size;
        oConfigMng.SaveConfig();
    }

生成的XML文件也是可读的:

<?xml version="1.0" encoding="utf-8"?>
<CConfigDO xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <StartPos>
    <X>70</X>
    <Y>278</Y>
  </StartPos>
  <StartSize>
    <Width>253</Width>
    <Height>229</Height>
  </StartSize>
</CConfigDO>

1
我的开发工作非常顺利,但是在部署应用程序时,普通用户无法访问该c:\program files\my application文件夹,因此保存设置会引发错误。我正在考虑将xml文件保存在AppData中,但是我只是想知道是否存在解决此问题的明显方法,因为这种方法似乎对您有用。
菲利普·斯特拉特福德

@PhilipStratford由于它只是一个普通文件,因此您可以将其保存在任何地方。只是找到一个具有写访问权限的地方。
Dieter Meemken'3

@PhilipStratford可能是AppData文件夹是您的一个选择,请参见C#获取kite提到的%AppData%的路径
Dieter Meemken'3

谢谢,我已经实现了这一点,将xml文件保存在AppDate文件夹中。我只是想知道是否有一种简单的方法可以按照您的示例将其保存在应用程序的文件夹中,因为我认为您已经使它可以工作了。不用担心,无论如何,AppData文件夹可能是一个更好的位置!
菲利普·斯特拉特福德,


5

除了使用自定义XML文件以外,我们还可以使用其他更友好的文件格式:JSON或YAML文件。

  • 如果您使用.NET 4.0 dynamic,那么此库确实非常易于使用(序列化,反序列化,嵌套对象支持和按需排序输出顺序+将多个设置合并为一个)JsonConfig(用法等效于ApplicationSettingsBase)
  • 对于.NET YAML配置库...我还没有找到一个像JsonConfig一样易于使用的库

您可以将设置文件存储在此处列出的多个特殊文件夹(针对所有用户和每个用户)Environment.SpecialFolder枚举和多个文件(默认只读,针对每个角色,针对每个用户等)

如果选择使用多个设置,则可以合并这些设置:例如,合并默认+ BasicUser + AdminUser的设置。您可以使用自己的规则:最后一个覆盖值,依此类推。


4

“这是否意味着我应该使用自定义XML文件来保存配置设置?” 不,不一定。我们使用SharpConfig进行此类操作。

例如,如果配置文件是这样的

[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment

我们可以像这样检索值

var config = Configuration.LoadFromFile("sample.cfg");
var section = config["General"];

string someString = section["SomeString"].StringValue;
int someInteger = section["SomeInteger"].IntValue;

它与.NET 2.0及更高版本兼容。我们可以即时创建配置文件,以后可以保存它。

来源:http : //sharpconfig.net/
GitHub:https : //github.com/cemdervis/SharpConfig


3

据我所知,.NET确实使用内置的应用程序设置工具支持持久设置:

Windows窗体的“应用程序设置”功能使在客户端计算机上创建,存储和维护自定义应用程序和用户首选项变得容易。使用Windows Forms应用程序设置,您不仅可以存储数据库连接字符串之类的应用程序数据,还可以存储诸如用户应用程序首选项之类的特定于用户的数据。使用Visual Studio或自定义的托管代码,您可以创建新设置,从中读取它们并将它们写入磁盘,将它们绑定到表单上的属性以及在加载和保存之前验证设置数据。- http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx


2
不正确。.参见上面的aku的回答。可以使用Settings和ApplicationSettingsBase
Gishu,

2

有时您想要摆脱保留在传统web.config或app.config文件中的那些设置。您希望对设置条目和单独的数据设计的部署进行更精细的控制。或要求是允许在运行时添加新条目。

我可以想象两个不错的选择:

  • 强类型版本和
  • 面向对象的版本。

强类型版本的优点是强类型设置名称和值。不会混淆名称或数据类型。缺点是必须对更多设置进行编码,不能在运行时添加。

使用面向对象的版本的优点是可以在运行时添加新设置。但是您没有强类型的名称和值。必须小心使用字符串标识符。获取值时必须知道较早保存的数据类型。

您可以在此处找到这两种功能齐全的实现的代码。


2

是的,可以保存配置-但这在很大程度上取决于您选择的配置方式。让我描述一下技术差异,以便您了解所拥有的选项:

首先,你需要区分,无论你想使用的applicationSettings的AppSettings在你*.exe.config(又名App.config在Visual Studio)文件-有根本的区别, 这里所描述

两者都提供了不同的更改保存方式:

  • AppSettings的让你直接读取和写入配置文件(通过config.Save(ConfigurationSaveMode.Modified);,其中配置被定义为config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);)。

  • 的applicationSettings允许读,但如果你写的变化(通过Properties.Settings.Default.Save();),它会被写入在每个用户的基础上,保存在一个特殊的地方(例如C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0)。正如Hans Passant在回答中提到的那样,这是因为用户通常具有对Program Files的受限权限,并且在不调用UAC提示的情况下无法对其进行写入。缺点是,如果将来要添加配置密钥,则需要将其与每个用户配置文件同步。

注意:如问题中所述,有第三个选项:如果将配置文件视为XML文档,则可以使用System.Xml.Linq.XDocument该类加载,修改和保存它。不需要使用自定义XML文件,您可以读取现有的配置文件。对于元素查询,您甚至可以使用Linq查询。我在这里一个例子,GetApplicationSetting在答案中检查了功能。

如果您需要加密来保护您的值,请查看答案。


感谢您的出色回答。
NoChance

2
@NoChance-不客气,很高兴我能为您提供帮助!
马特

真好!最后想通了这一切😂😂😂
Momoro

1
@Momoro-很好听!;-)
马特

1
public static class SettingsExtensions
{
    public static bool TryGetValue<T>(this Settings settings, string key, out T value)
    {
        if (settings.Properties[key] != null)
        {
            value = (T) settings[key];
            return true;
        }

        value = default(T);
        return false;
    }

    public static bool ContainsKey(this Settings settings, string key)
    {
        return settings.Properties[key] != null;
    }

    public static void SetValue<T>(this Settings settings, string key, T value)
    {
        if (settings.Properties[key] == null)
        {
            var p = new SettingsProperty(key)
            {
                PropertyType = typeof(T),
                Provider = settings.Providers["LocalFileSettingsProvider"],
                SerializeAs = SettingsSerializeAs.Xml
            };
            p.Attributes.Add(typeof(UserScopedSettingAttribute), new UserScopedSettingAttribute());
            var v = new SettingsPropertyValue(p);
            settings.Properties.Add(p);
            settings.Reload();
        }
        settings[key] = value;
        settings.Save();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.