首页 文章

在Windows窗体应用程序中保存应用程序设置的最佳实践[关闭]

提问于
浏览
530

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

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

我理解有三种选择:

  • ConfigurationSettings文件(appname.exe.config)

  • 注册表

  • 自定义XML文件

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

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

我已经看过关于这个问题的其他讨论,但我仍然不清楚 .

13 回答

  • 1

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

  • 12

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

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

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

    • 强类型版本和

    • 面向对象的版本 .

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

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

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

  • 6

    registry / configurationSettings / XML参数似乎仍然非常活跃 . 我已经全部使用了它们,因为技术已经取得了进展,但我最喜欢的是基于Threed's systemIsolated Storage相结合 .

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

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

    如果您计划保存到与可执行文件相同的目录中的文件,这是一个使用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;
            }
        }
    }
    
  • 0

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

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

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

    接下来,从.NET 2.0开始,可以将值存储回 app.config 文件 .

  • 19

    如果您使用Visual Studio,那么很容易获得可持久的设置 . 在Solution Explorer中右键单击项目,选择Properties . 选择“设置”选项卡,如果设置不存在,请单击超链接 . 使用“设置”选项卡创建应用程序设置 . 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窗体和其他项目类型 .

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

  • 9

    我想分享一个我为此建造的图书馆 . 它是一个很小的库,但对.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();
    }
    

    The benefit compared to .settings files: 由于你只需要提及每个属性 once ,所以's considerably less code, and it'的错误要少得多 .

    使用设置文件,您需要提及每个属性 five 次:一次显式创建属性,另外四次在代码中复制值来回 .

    存储,序列化等是完全可配置的 . 使用inversion of control时,您可以将其连接起来,以便自动将跟踪应用于它所解析的所有对象,这样您只需要执行属性持久化就可以在其上执行[Trackable]属性 .

    我正在写这一切,因为我认为图书馆是一流的,我想推广它:)

  • 63

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

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

  • 13

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

    • 如果使用.NET 4.0动态,这个库非常容易使用(序列化,反序列化,嵌套对象支持和排序输出,因为您希望将多个设置合并为一个)JsonConfig(用法等同于ApplicationSettingsBase)

    • 对于.NET YAML配置库...我还没有找到一个像JsonConfig一样易于使用的程序库

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

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

  • 2

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

    我希望它有所帮助 .

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

    ApplicationSettings 类在很大程度上不受设计影响,使用正确安全的用户帐户运行的应用程序(想想Vista UAC)没有对程序安装文件夹的写入权限 .

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

相关问题