关于c#:如何在Windows窗体应用程序中保存应用程序设置?

How to save application settings in a Windows Forms Application?

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

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

我知道有三种选择:

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

我读到.NET配置文件不可预知,无法将值保存回它。至于登记处,我想尽量远离它。

这是否意味着我应该使用自定义XML文件来保存配置设置?如果是这样,我想看看代码示例(C)。

我看过关于这个问题的其他讨论,但我还是不清楚。


如果您使用的是Visual Studio,那么很容易获得持久的设置。右键单击解决方案资源管理器中的项目,选择"属性"。选择"设置"选项卡,如果设置不存在,请单击超链接。使用"设置"选项卡创建应用程序设置。Visual Studio创建文件Settings.settingsSettings.Designer.settings,其中包含从applicationsettingsbase继承的单例类Settings。您可以从代码访问该类以读/写应用程序设置:

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

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

请注意,您需要设置设置的scope属性。如果选择应用程序范围,则settings.default.将是只读的。


如果计划保存到与可执行文件位于同一目录下的文件中,下面是一个使用JSON格式的好解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
        }
    }
}


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

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

我将把用户特定的设置存储在一个XML文件中,该文件将保存在独立的存储中,或者保存在specialfolder.applicationdata目录中。

其次,从.NET 2.0开始,可以将值存储回app.config文件。


ApplicationSettings类不支持将设置保存到app.config文件中。这在设计上非常重要,使用适当安全的用户帐户运行的应用程序(想想VistaUAC)对程序的安装文件夹没有写访问权。

你可以和ConfigurationManager类的系统作斗争。但最简单的解决方法是进入设置设计器,将设置的范围更改为用户。如果这会造成困难(例如,设置与每个用户都相关),您应该将选项功能放在单独的程序中,这样您就可以请求特权提升提示。或者放弃使用设置。


我想和大家分享我为这个建的图书馆。它是一个很小的库,但是比.settings文件有很大的改进。

这个库叫做jot(github),这里有一篇关于它的老的代码项目文章。

以下是如何使用它跟踪窗口的大小和位置:

1
2
3
4
5
6
7
8
9
10
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容器创建时,您可以[将其连接起来]]以便它自动将跟踪应用于它解析的所有对象,以便使属性持久化所需的全部操作都是在其上加上[可跟踪]属性。

它是高度可配置的,您可以配置:-当数据在全局或每个跟踪对象中被持久化和应用时-如何序列化-存储位置(如文件、数据库、联机、独立存储、注册表)-可以取消应用/保持属性数据的规则

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


registry/configurationsettings/xml参数似乎仍然非常活跃。随着技术的进步,我已经全部使用了它们,但我最喜欢的是基于Threed的系统和独立存储。

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

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

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

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

它只是一个样本,不暗示最佳实践。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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) { }
        }
    }
}


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

下面是一个存储表单位置和大小的示例。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[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; }
    }
}

用于保存和加载的管理器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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();
        }
    }
}

现在,您可以创建一个实例并在窗体的加载和关闭事件中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    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文件也是可读的:

1
2
3
4
5
6
7
8
9
10
11
<?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>


我不喜欢使用web.configapp.config的建议解决方案。尝试读取自己的XML。查看XML设置文件–不再查看web.config。


其他选项,而不是使用自定义XML文件,我们可以使用更用户友好的文件格式:json或yaml文件。

  • 如果您使用.NET 4.0动态库,那么这个库非常容易使用(序列化、反序列化、嵌套对象支持和排序输出如您所愿+将多个设置合并为一个)jsonconfig(用法等同于applicationsettingsbase)
  • 对于.NET Yaml配置库…我还没有找到一个易于用作jsonconfig

您可以将设置文件存储在多个特殊文件夹中(针对所有用户和每个用户),如这里列出的environment.specialfolder枚举和多个文件(默认为只读、每个角色、每个用户等)。

  • 获取特殊文件夹路径示例:c获取%AppDATA %

如果选择使用多个设置,则可以合并这些设置:例如,合并default+basicuser+adminuser的设置。您可以使用自己的规则:最后一个规则覆盖值等。


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

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

1
2
3
4
[General]
# a comment
SomeString = Hello World!
SomeInteger = 10 # an inline comment

我们可以像这样检索值

1
2
3
4
5
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

希望有帮助。


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

The Application Settings feature of Windows Forms makes it easy to create, store, and maintain custom application and user preferences on the client computer. With Windows Forms application settings, you can store not only application data such as database connection strings, but also user-specific data, such as user application preferences. Using Visual Studio or custom managed code, you can create new settings, read them from and write them to disk, bind them to properties on your forms, and validate settings data prior to loading and saving.
- http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx


有时,您希望摆脱那些保存在传统web.config或app.config文件中的设置。您需要对设置项的部署和分离的数据设计进行更细粒度的控制。或者要求允许在运行时添加新条目。

我可以想象两个好的选择:

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

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

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

您可以在这里找到这两个完全功能实现的代码。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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();
    }
}