ChatGPT解决这个技术问题 Extra ChatGPT

How can I save application settings in a Windows Forms application?

What I want to achieve is very simple: I have a Windows Forms (.NET 3.5) application that uses a path for reading information. This path can be modified by the user, by using the options form I provide.

Now, I want to save the path value to a file for later use. This would be one of the many settings saved to this file. This file would sit directly in the application folder.

I understand three options are available:

ConfigurationSettings file (appname.exe.config)

Registry

Custom XML file

I read that the .NET configuration file is not foreseen for saving values back to it. As for the registry, I would like to get as far away from it as possible.

Does this mean that I should use a custom XML file to save configuration settings?

If so, I would like to see code example of that (C#).

I have seen other discussions on this subject, but it is still not clear to me.

Is this a .NET WinForms application? If so, what version of .NET are you developing on?
Yes, it is a .NET framework version 3.5 WinForms application.
do you need save passwords or secrets values ? Maybe requires any encryption

C
Chiramisu

If you work with Visual Studio then it is pretty easy to get persistable settings. Right click on the project in Solution Explorer and choose Properties. Select the Settings tab and click on the hyperlink if settings doesn't exist.

Use the Settings tab to create application settings. Visual Studio creates the files Settings.settings and Settings.Designer.settings that contain the singleton class Settings inherited from ApplicationSettingsBase. You can access this class from your code to read/write application settings:

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

This technique is applicable both for console, Windows Forms, and other project types.

Note that you need to set the scope property of your settings. If you select Application scope then Settings.Default. will be read-only.

Reference: How To: Write User Settings at Run Time with C# - Microsoft Docs


if I have a solution, will this apply for the entire solution or for each project ?
@Four: I've got a .NET 4.0 WinApp project here, and my SomeProperty is not readonly. Settings.Default.SomeProperty = 'value'; Settings.Default.Save(); works like a charm. Or is that because I've got User-settings?
@Four: When I changed a setting from User to Application-scope and save the file, I saw in the generated code the setter disappeared. This also happens with Client profile 4.0 ...
@Four: great link, though your statement that the Settings.Default.Save() does nothing is incorrect. As @aku states in the answer, app-scope settings are read-only: save is ineffective for them. Use that custom PortableSettingsProvider to save user-scope settings to the app.config located where the exe is instead of the one in the user's AppData folder. No, not generally good, but I use it during development to use the same settings from compile to compile (w/o it, they go new unique user folders with each compile).
As of now, with .NET 3.5 it appears you can just use Settings.Default.SomeProperty to assign a value and get strong typecasting. Also, to save others time (took me a while to figure this out), you need to either type Properties.Settings.Default, or add using YourProjectNameSpace.Settings to the top of your file. "Settings" alone is not defined/found.
T
Trevor

If you are planning on saving to a file within the same directory as your executable, here's a nice solution that uses the JSON format:

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

Yes, change the DEFAULT_FILENAME to an absolute path if you want to save to another directory. I think it's most common to save the file to the same directory as the application, or a sub-directory, if you're not saving them to the registry.
No need to change DEFAULT_FILENAME, just call settings.Save(theFileToSaveTo); Being all caps, DEFAULT_FILENAME is supposed to be a constant. If you want a read-write property, make one and have the constructor set it to DEFAULT_FILENAME. Then have the default argument value be null, test for this and use your property as the default value. It is a little more typing, but gives you a more standard interface.
You'll need to reference System.Web.Extensions.dll if you haven't already.
I've created an entire library based on this answer with many improvements and made it available in nuget: github.com/Nucs/JsonSettings
Trevor, it's just extraordinary simple and reliable solution! I was able to save my complex object instantly... with XML I would spend few days to make it work. Thank you again!
H
Hakan Yildizhan

The registry is a no-go. You're not sure whether the user which uses your application, has sufficient rights to write to the registry.

You can use the app.config file to save application-level settings (that are the same for each user who uses your application).

I would store user-specific settings in an XML file, which would be saved in Isolated Storage or in the SpecialFolder.ApplicationData directory.

Next to that, as from .NET 2.0, it is possible to store values back to the app.config file.


Use the registry, though, if you want per-login/user settings.
@thenonhacker: Or use Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
@thenonhacker - The registry is not required and should not be used for storing application settings - ever. The System.Environment.SpecialFolder.LocalApplicationData is the per-user local folder. The .ApplicationData is the per-user roaming folder. See msdn.microsoft.com/en-us/library/…
The user registry can be written to (lots of programs write information there, and user permissions are never a problem). The advantage of using the registry over using settings is that if you have multiple applications sharing the same folder, (say, a setup program and an an application program), they won't share the same settings.
The main disadvantage of Registry is the hard way to export/copy settings to other PC. But I don't agree with "You're not sure whether the user which uses your application, has sufficient rights to write to the registry" - In HKEY_CURRENT_USER you always have rights to write. It can be denied but the filesystem can also be inaccessible for current user (all possible TEMP folders, etc).
P
Peter Mortensen

The ApplicationSettings class doesn't support saving settings to the app.config file. That's very much by design; applications that run with a properly secured user account (think Vista UAC) do not have write access to the program's installation folder.

You can fight the system with the ConfigurationManager class. But the trivial workaround is to go into the Settings designer and change the setting's scope to User. If that causes hardships (say, the setting is relevant to every user), you should put your Options feature in a separate program so you can ask for the privilege elevation prompt. Or forego using a setting.


Could you please expand upon your last sentence? Ask for elevation to write app.config or to write a separate application that would go through all users' home folers, look for user.config and edit these?
The separate program requires a manifest to ask for elevation. Google 'asinvoker requireadministrator' to find the proper syntax. Editing user.config is not practical, nor necessary.
C
Community

The registry/configurationSettings/XML argument still seems very active. I've used them all, as the technology has progressed, but my favourite is based on Threed's system combined with Isolated Storage.

The following sample allows storage of an objects named properties to a file in isolated storage. Such as:

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

Properties may be recovered using:

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

It is just a sample, not suggestive of best practices.

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) { }
        }
    }
}

Or better still; use DataContractJsonSerializer
P
Peter Mortensen

I wanted to share a library I've built for this. It's a tiny library, but a big improvement (IMHO) over .settings files.

The library is called Jot (GitHub). Here is an old The Code Project article I wrote about it.

Here's how you'd use it to keep track of a window's size and location:

public MainWindow()
{
    InitializeComponent();

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

The benefit compared to .settings files: There's considerably less code, and it's a lot less error-prone since you only need to mention each property once.

With a settings files you need to mention each property five times: once when you explicitly create the property and an additional four times in the code that copies the values back and forth.

Storage, serialization, etc. are completely configurable. When the target objects are created by an IoC container, you can [hook it up][] so that it applies tracking automatically to all objects it resolves, so that all you need to do to make a property persistent is slap a [Trackable] attribute on it.

It's highly configurable, and you can configure: - when data is persisted and applied globally or for each tracked object - how it's serialized - where it's stored (e.g. file, database, online, isolated storage, registry) - rules that can cancel applying/persisting data for a property

Trust me, the library is top notch!


D
Dieter Meemken

A simple way is to use a configuration data object, save it as an XML file with the name of the application in the local Folder and on startup read it back.

Here is an example to store the position and size of a form.

The configuration dataobject is strongly typed and easy to use:

[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; }
    }
}

A manager class for saving and loading:

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

Now you can create an instance and use in your form's load and close events:

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

And the produced XML file is also readable:

<?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>

I have this working great in development, but when I deploy the application the average user doesn't have access to the c:\program files\my application folder, so the saving of settings throws an error. I'm looking into saving the xml file in AppData instead, but I just wondered if there was an obvious way around this problem, since this approach seems to have worked for you.
@PhilipStratford Since it is only a normal file, you can save it anywhere. Just find a place with write access.
@PhilipStratford May be the AppData folder is an option for you, see C# getting the path of %AppData%, as mentioned by kite.
Thanks, I've already implemented this, saving the xml file in the AppDate folder. I just wondered whether there was an easy way to save it in the application's folder as per your example, since I assumed you had made it work. Not to worry, the AppData folder is probably a better location anyway!
M
Matt

Yes, it is possible to save the configuration - but it pretty much depends on the way you choose to do it. Let me describe the technical differences so you can understand the options you have:

First, you need to distinguish, whether you want to use applicationSettings or AppSettings in your *.exe.config(aka App.config in Visual Studio) file - there are fundamental differences, being described here.

Both provide different ways of saving changes:

The AppSettings allow you to read and write directly into the config file via config.Save(ConfigurationSaveMode.Modified);, where config is defined as: config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

The applicationSettings allow to read, but if you write changes (via Properties.Settings.Default.Save();) it will be written on a per-user basis, stored in a special place (e.g. C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0). As Hans Passant mentioned in his answer, this is because a user usually has restricted rights to Program Files and cannot write to it without invoking the UAC prompt. A disadvantage is if you're adding configuration keys in the future you need to synchronize them with every user profile.

But there are a couple of other alternative options:

Since .NET Core (and .NET 5 and 6) a 3rd option is the appsettings.json file which uses Microsoft's configuration abstraction (and also the secrets.json file which is stored in your user profile rather than in the assemblies directories). But usually WinForms isn't using it, so I am mentioning it just for completeness. However, here are some references how to read and write the values. Alternatively you can use Newtonsoft JSON to read and write the appsettings.json file, but it is not limited to that: you can also create your own json files with that method.

As mentioned in the question, there is a 4th option: If you treat the configuration file as XML document, you can load, modify and save it by using the System.Xml.Linq.XDocument class. It is not required to use a custom XML file, you can read the existing config file; for querying elements, you can even use Linq queries. I have given an example here, check out the function GetApplicationSetting there in the answer.

A 5th option is to store settings in the registry. How you can do it is described here.

Last not least, there is a 6th option: You can store values in the environment (system environment or environment of your account). In Windows settings (the cogwheel in the Windows menu), type in "environment" in the search bar and add or edit them there. To read them, use var myValue = Environment.GetEnvironmentVariable("MyVariable");. Note that your application usually needs to be restarted to get the updated environment settings.

If you require encryption to protect your values, check out this answer. It describes how to use Microsoft's DPAPI to store values encrypted.

And if you want to support your own files, whether XML or JSON, it might be useful to know the directory of the assembly running:

var assemblyDLL = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyDirectory = System.IO.Path.GetDirectoryName(assemblyDLL.Location);

You can use assemblyDirectory as base directory to store your files.


@NoChance - You're welcome, glad that I could help you!
Nice! Finally figured it all out 😂😂😂
s
shin

I don't like the proposed solution of using web.config or app.config. Try reading your own XML. Have a look at XML Settings Files – No more web.config.


P
Peter Mortensen

"Does this mean that I should use a custom XML file to save configuration settings?" No, not necessarily. We use SharpConfig for such operations.

For instance, if a configuration file is like that

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

We can retrieve values like this

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

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

It is compatible with .NET 2.0 and higher. We can create configuration files on the fly and we can save it later.

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


Very great answer and saved my day. Thank you! How to modify that file using code, assuming I need to add more parameters? thanks again.
See the "Creating a Configuration in-memory" and "Saving a Configuration" in the github.com/cemdervis/SharpConfig.
C
Community

Other options, instead of using a custom XML file, we can use a more user friendly file format: JSON or YAML file.

If you use .NET 4.0 dynamic, this library is really easy to use (serialize, deserialize, nested objects support and ordering output as you wish + merging multiple settings to one) JsonConfig (usage is equivalent to ApplicationSettingsBase)

For .NET YAML configuration library... I haven't found one that is as easy to use as JsonConfig

You can store your settings file in multiple special folders (for all users and per user) as listed here Environment.SpecialFolder Enumeration and multiple files (default read only, per role, per user, etc.)

Sample for getting path of special folder: C# getting the path of %AppData%

If you choose to use multiple settings, you can merge those settings: For example, merging settings for default + BasicUser + AdminUser. You can use your own rules: the last one overrides the value, etc.


P
Peter Mortensen

As far as I can tell, .NET does support persisting settings using the built-in application settings facility:

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


Not true.. see aku's answer above. its possible using Settings and ApplicationSettingsBase
u
user3130351

Sometimes you want to get rid of those settings kept in the traditional web.config or app.config file. You want more fine grained control over the deployment of your settings entries and separated data design. Or the requirement is to enable adding new entries at runtime.

I can imagine two good options:

The strongly typed version and

The object oriented version.

The advantage of the strongly typed version are the strongly typed settings names and values. There is no risk of intermixing names or data types. The disadvantage is that more settings have to be coded, cannot be added at runtime.

With the object oriented version the advantage is that new settings can be added at runtime. But you do not have strongly typed names and values. Must be careful with string identifiers. Must know data type saved earlier when getting a value.

You can find the code of both fully functional implementations HERE.


P
Paul - Soura Tech LLC
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();
    }
}

Can you clarify where is the settings file located ? or how it looks like ?