【问题标题】:How can I save application settings in a Windows Forms application?如何在 Windows 窗体应用程序中保存应用程序设置?
【发布时间】:2021-07-12 04:38:19
【问题描述】:

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

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

我知道有三种选择:

  • ConfigurationSettings 文件 (appname.exe.config)
  • 注册表
  • 自定义 XML 文件

我读到 .NET 配置文件无法将值保存回它。至于注册表,我想离它越远越好。

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

如果是这样,我想看看那个 (C#) 的代码示例。

我看过其他关于这个主题的讨论,但我仍然不清楚。

【问题讨论】:

  • 这是一个 .NET WinForms 应用程序吗?如果是这样,您正在开发哪个版本的 .NET?
  • 是的,它是一个 .NET framework 版本 3.5 WinForms 应用程序。
  • 您是否需要保存密码或机密值?也许需要任何加密

标签: c# xml winforms configuration-files application-settings


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 如果我有解决方案,这适用于整个解决方案还是每个项目?
  • @Four:我这里有一个 .NET 4.0 WinApp 项目,我的 SomeProperty 不是只读的。 Settings.Default.SomeProperty = 'value'; Settings.Default.Save(); 就像一个魅力。还是因为我有用户设置?
  • @Four:当我将设置从 User 更改为 Application-scope 并保存文件时,我在生成的代码中看到 setter 消失了。这也发生在客户端配置文件 4.0 ...
  • @Four:很好的链接,尽管您关于 Settings.Default.Save() 什么都不做的说法是不正确的。正如@aku 在答案中所说,应用程序范围设置是只读的:保存对他们无效。使用该自定义 PortableSettingsProvider 将用户范围设置保存到 exe 所在的 app.config,而不是用户 AppData 文件夹中的那个。不,通常不是很好,但我在开发过程中使用它来使用从编译到编译的相同设置(没有它,每次编译它们都会进入新的唯一用户文件夹)。
  • 到目前为止,使用 .NET 3.5,您似乎可以只使用 Settings.Default.SomeProperty 来分配值并获得强类型转换。此外,为了节省其他人的时间(我花了一些时间才弄清楚这一点),您需要键入 Properties.Settings.Default,或使用 YourProjectNameSpace.Settings 添加到文件顶部。没有单独定义/找到“设置”。
【解决方案2】:

如果您打算保存到与可执行文件位于同一目录中的文件,这里有一个很好的解决方案,它使用 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 更改为绝对路径。我认为最常见的做法是将文件保存到与应用程序相同的目录或子目录中,如果您不将它们保存到注册表。
  • 不用改DEFAULT_FILENAME,直接调用settings.Save(theFileToSaveTo);全部大写,DEFAULT_FILENAME 应该是一个常量。如果您想要一个读写属性,请创建一个并让构造函数将其设置为DEFAULT_FILENAME。然后让默认参数值为null,对此进行测试并将您的属性用作默认值。它需要更多的输入,但为您提供更标准的界面。
  • 如果您还没有引用 System.Web.Extensions.dll,则需要引用。
  • 我已经基于这个答案创建了一个完整的库,并进行了许多改进,并使其在 nuget 中可用:github.com/Nucs/JsonSettings
  • Trevor,这是一个非常简单可靠的解决方案!我能够立即保存我的复杂对象......使用 XML 我会花几天时间让它工作。再次感谢您!
【解决方案3】:

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

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

我会将用户特定的设置存储在一个 XML 文件中,该文件将保存在 Isolated StorageSpecialFolder.ApplicationData 目录中。

除此之外,从 .NET 2.0 开始,可以将值存储回 app.config 文件。

【讨论】:

  • 如果您需要按登录名/用户设置,请使用注册表。
  • @thenonhacker:或者使用 Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
  • @thenonhacker - 注册表不是必需的,也不应该用于存储应用程序设置 - 永远。 System.Environment.SpecialFolder.LocalApplicationData 是每个用户的本地文件夹。 .ApplicationData 是每个用户的漫游文件夹。见msdn.microsoft.com/en-us/library/…
  • 可以写入用户注册表(很多程序会在那里写入信息,用户权限从来都不是问题)。使用注册表优于使用设置的优势在于,如果您有多个应用程序共享同一个文件夹(例如,一个安装程序和一个应用程序),它们将不会共享相同的设置。
  • 注册表的主要缺点是很难将设置导出/复制到其他 PC。但我不同意“您不确定使用您的应用程序的用户是否有足够的权限写入注册表” - 在 HKEY_CURRENT_USER 中,您始终拥有写入权限。它可以被拒绝,但当前用户也可能无法访问文件系统(所有可能的 TEMP 文件夹等)。
【解决方案4】:

ApplicationSettings 类不支持将设置保存到 app.config 文件。这在很大程度上是设计使然。使用适当保护的用户帐户(想想 Vista UAC)运行的应用程序对程序的安装文件夹没有写入权限。

您可以使用ConfigurationManager 类来对抗系统。但简单的解决方法是进入设置设计器并将设置的范围更改为用户。如果这会导致困难(例如,该设置与每个用户相关),您应该将您的选项功能放在一个单独的程序中,以便您可以请求权限提升提示。或者放弃使用设置。

【讨论】:

  • 能否请您扩展您的最后一句话?请求提升以编写 app.config 或编写一个单独的应用程序,该应用程序将通过所有用户的主文件夹,查找 user.config 并编辑这些?
  • 单独的程序需要一个清单来请求提升。谷歌“asinvoker requireadministrator”找到正确的语法。编辑 user.config 既不实用,也没有必要。
【解决方案5】:

registry/configurationSettings/XML 参数似乎仍然很活跃。随着技术的进步,我都使用了它们,但我最喜欢的是基于Threed's system 结合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) { }
        }
    }
}

【讨论】:

  • 或者更好;使用 DataContractJsonSerializer
【解决方案6】:

我想分享一个我为此构建的库。这是一个很小的库,但对 .settings 文件有很大的改进(恕我直言)。

该库名为Jot (GitHub)。这是我写过的旧的The Code Project article

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

public MainWindow()
{
    InitializeComponent();

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

与 .settings 文件相比的优势:代码少得多,而且出错率也低得多,因为您只需提及每个属性一次 p>

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

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

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

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

【讨论】:

    【解决方案7】:

    一种简单的方法是使用配置数据对象,将其保存为 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();
            }
        }
    }
    

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

        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>
    

    【讨论】:

    • 我在开发中工作得很好,但是当我部署应用程序时,普通用户无法访问c:\program files\my application 文件夹,因此保存设置会引发错误。我正在考虑将 xml 文件保存在 AppData 中,但我只是想知道是否有解决此问题的明显方法,因为这种方法似乎对您有用。
    • @PhilipStratford 因为它只是一个普通文件,你可以将它保存在任何地方。找一个有写权限的地方就行了。
    • 谢谢,我已经实现了,将 xml 文件保存在 AppDate 文件夹中。我只是想知道是否有一种简单的方法可以根据您的示例将其保存在应用程序的文件夹中,因为我假设您已经让它工作了。不用担心,AppData 文件夹可能是一个更好的位置!
    【解决方案8】:

    我不喜欢使用web.configapp.config 的建议解决方案。尝试阅读您自己的 XML。看看XML Settings Files – No more web.config

    【讨论】:

      【解决方案9】:

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

      首先,您需要区分是要在 *.exe.config(在 Visual Studio 中也称为 App.config)文件中使用 applicationSettings 还是 AppSettings - 有根本区别, being described here.

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

      • AppSettings允许您通过config.Save(ConfigurationSaveMode.Modified);直接读写配置文件,其中config定义为:
        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 mentioned 在他的回答中那样,这是因为用户通常对程序文件具有受限的权限,并且在不调用 UAC 提示的情况下无法对其进行写入。一个缺点是,如果您以后要添加配置密钥,则需要将它们与每个用户配置文件同步。

      但还有其他几种选择:

      • 由于 .NET Core(以及 .NET 5 和 6),第三个选项是使用 Microsoft 配置抽象的 appsettings.json 文件。但通常 WinForms 不使用它,所以我提到它只是为了完整性。但是,这里有一些参考如何readwrite 的值。或者你可以使用Newtonsoft JSON to read and writeappsettings.json文件,但不限于此:你也可以使用该方法创建自己的json文件。

      • 正如问题中提到的,有一个第4个选项:如果你把配置文件当作XML文档,你可以通过使用System.Xml.Linq.XDocument 班级。不需要使用自定义的 XML 文件,可以读取已有的配置文件;对于查询元素,您甚至可以使用 Linq 查询。我已经举了一个例子here,看看答案里面的函数GetApplicationSetting

      • 第五个选项是将设置存储在注册表中。描述了如何做到这一点here

      • 最后,还有一个第六个选项:您可以将值存储在环境(系统环境或帐户环境)中。在 Windows 设置(Windows 菜单中的齿轮)中,在搜索栏中输入“环境”并在那里添加或编辑它们。要阅读它们,请使用
        var myValue = Environment.GetEnvironmentVariable("MyVariable");
        请注意,您的应用程序通常需要重新启动才能获得更新的环境设置。

      如果您需要加密来保护您的值,请查看 this 答案。它描述了如何使用 Microsoft 的 DPAPI 来存储加密的值。

      如果您想支持自己的文件,无论是 XML 还是 JSON,了解正在运行的程序集的目录可能会很有用:

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

      您可以使用assemblyDirectory 作为基本目录来存储您的文件。

      【讨论】:

      • @NoChance - 不客气,很高兴我能帮助你!
      • 不错!终于明白了???
      【解决方案10】:

      “这是否意味着我应该使用自定义 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

      【讨论】:

      • 非常好的答案,拯救了我的一天。谢谢!假设我需要添加更多参数,如何使用代码修改该文件?再次感谢。
      • 参见github.com/cemdervis/SharpConfig 中的“在内存中创建配置”和“保存配置”。
      【解决方案11】:

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

      • 如果你使用.NET 4.0动态,这个库真的好用 (序列化、反序列化、嵌套对象支持和排序输出 如你所愿 + 将多个设置合并为一个)JsonConfig(用法等同于 ApplicationSettingsBase)
      • 对于 .NET YAML 配置库...我还没有找到一个 易于用作 JsonConfig

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

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

      【讨论】:

        【解决方案12】:

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

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

        【讨论】:

        • 不正确.. 见上面 aku 的回答。可以使用 Settings 和 ApplicationSettingsBase
        【解决方案13】:

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

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

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

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

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

        您可以找到两个全功能实现的代码HERE

        【讨论】:

          【解决方案14】:
          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();
              }
          }
          

          【讨论】:

          • 你能澄清设置文件的位置吗?或者它的样子?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多