在Windows窗体应用程序中保存应用程序设置的最佳实践
我想实现的是非常简单的:我有一个Windows窗体(.NET 3.5)应用程序使用一个path来阅读信息。 该path可以由用户通过使用我提供的选项来修改。
现在,我想将path值保存到一个文件供以后使用。 这将是保存到这个文件的许多设置之一。 该文件将直接位于应用程序文件夹中。
我明白有三个选项可用:
- ConfigurationSettings文件(appname.exe.config)
- 注册处
- 自定义XML文件
我读了.NETconfiguration文件不能预见将值保存到它。 至于registry,我想尽可能远离它。
这是否意味着我应该使用自定义XML文件来保存configuration设置? 如果是这样,我想看看(C#)的代码示例。
关于这个问题我还见过其他的讨论,但是我还不清楚。
如果你使用Visual Studio,那么获得持久化设置是相当容易的。 右键单击解决scheme资源pipe理器中的项目,select属性。 select设置选项卡,如果设置不存在,单击超链接。 使用“设置”选项卡创build应用程序设置。 Visual Studio将创build包含从ApplicationSettingsBaseinheritance的单例类Settings
Settings.settings
和Settings.Designer.settings
文件。 您可以从您的代码访问这个类来读取/写入应用程序设置:
Properties.Settings.Default["SomeProperty"] = "Some Value"; Properties.Settings.Default.Save(); // Saves settings in application configuration file
此技术适用于控制台,Windows窗体和其他项目types。
请注意,您需要设置您的设置的范围属性。 如果您select应用程序范围,那么Settings.Default。<您的属性>将是只读的。
如果您计划将其保存到与可执行文件相同的目录中的文件中,则可以使用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; } } }
registry是一个不行。 您不确定使用您的应用程序的用户是否有足够的权限来写入registry。
您可以使用app.config
文件来保存应用程序级别的设置(对于每个使用您的应用程序的用户来说都是一样的)。
我会将用户特定的设置存储在一个XML文件中,该文件将保存在独立存储或SpecialFolder.ApplicationData目录中。
接下来,从.NET 2.0开始,可以将值存储回app.config
文件。
ApplicationSettings
类不支持将设置保存到app.config文件。 这是非常多的devise,与适当的安全用户帐户(认为Vista的UAC)运行的应用程序没有写入权限的程序的安装文件夹。
你可以用ConfigurationManager
类来对抗系统。 但是一个简单的解决方法是进入设置devise器并将设置的范围更改为用户。 如果这会造成困难(例如,设置与每个用户有关),则应该将“选项”function放在单独的程序中,以便您可以要求提升权限提示。 或者放弃使用设置。
registry/ configurationSettings / XML参数仍然看起来非常活跃。 随着技术的发展,我已经全部使用了它们,但是我最喜欢的是基于Threed系统与隔离存储相结合。
以下示例允许将名为属性的对象存储到独立存储中的文件。 如:
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) { } } } }
我想分享一个我为此而build的图书馆。 这是一个小图书馆,但是对于.settings文件来说是一个很大的改进(恕我直言)。
这个库被称为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文件相比的好处:代码less得多,并且由于您只需提到每个属性一次 ,所以它的错误率要低得多。
使用设置文件,您需要提及每个属性五次 :一次显式创build属性,另外四次在代码中来回复制值。
存储,序列化等是完全可configuration的。 当使用控制反转时 ,可以将它连接起来,以便自动将跟踪应用于所有parsing对象,因此,只需要使属性持久化就可以掌握[Trackable]属性。
我正在写这一切,因为我认为图书馆是一stream的,我想推广它:)
一个简单的方法是使用configuration数据对象,将其作为XML文件保存在本地文件夹中,并在启动时读回。
这里是一个存储表单位置和大小的例子。
configuration数据对象是强types的并且易于使用:
[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(); } } }
现在你可以创build一个实例并在你的表单的加载和closures事件中使用:
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>
我不喜欢使用web.config
或app.config
的build议解决scheme。 尝试阅读您自己的XML。 看看XML设置文件 – 没有更多的web.config 。
据我所知,.NET确实使用内置的应用程序设置工具支持持久化设置:
Windows窗体的“应用程序设置”function可以轻松地在客户端计算机上创build,存储和维护自定义应用程序和用户首选项。 使用Windows Forms应用程序设置,不仅可以存储应用程序数据(如数据库连接string),还可以存储用户特定的数据(如用户应用程序首选项)。 使用Visual Studio或自定义托pipe代码,您可以创build新的设置,读取它们并将其写入磁盘,将它们绑定到表单上的属性,并在加载和保存之前validation设置数据。 – http://msdn.microsoft.com/en-us/library/k4s6c3a0.aspx
有时候你想摆脱保留在传统的web.config或app.config文件中的设置。 您希望对部署设置条目和分离的数据devise进行更细致的控制。 或者需要在运行时启用添加新条目。
我可以想象两个很好的select:
- 强types的版本和
- 面向对象的版本。
强types版本的优点是强types的设置名称和值。 不存在混用名称或数据types的风险。 缺点是更多的设置必须进行编码,不能在运行时添加。
面向对象的版本的好处是可以在运行时添加新的设置。 但是你没有强types的名字和值。 必须小心string标识符。 获取值时,必须知道之前保存的数据types。
你可以在这里find这两个全function实现的代码。
其他选项,而不是使用自定义的XML文件,我们可以使用更友好的文件格式:JSON或YAML文件。
- 如果你使用.NET 4.0 dynamic,这个库实际上很容易使用(序列化,反序列化,嵌套对象支持和按照你的意愿sorting输出+将多个设置合并为一个) JsonConfig (用法相当于ApplicationSettingsBase)
- 对于.NET YAMLconfiguration库…我还没有find像JsonConfig一样易于使用的库
您可以将设置文件存储在多个特殊文件夹(针对所有用户和每个用户),如下所列。Environment.SpecialFolder枚举和多个文件(默认只读,每个angular色,每个用户等)
- 获取特殊文件夹path的示例: C#获取%AppData%的path
如果您select使用多个设置,则可以合并这些设置:例如,合并默认设置+ BasicUser + AdminUser。 你可以使用你自己的规则:最后一个覆盖价值等
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(); } }