如何在Windows窗体应用程序中保存应用程序设置?

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

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

我知道有三种选择:

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

我读到。net配置文件不能将值保存回配置文件。至于注册表,我想尽量远离它。

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

如果是这样,我想看看代码的例子(c#)。

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

529295 次浏览

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

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

我将把用户特定的设置存储在一个XML文件中,该文件将保存在独立存储SpecialFolder。ApplicationData目录中。

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

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

Windows窗体的应用程序设置功能可以很容易地在客户端计算机上创建、存储和维护自定义应用程序和用户首选项。通过Windows窗体应用程序设置,不仅可以存储应用程序数据(如数据库连接字符串),还可以存储特定于用户的数据(如用户应用程序首选项)。使用Visual Studio或自定义托管代码,您可以创建新的设置,从磁盘读取它们并将它们写入磁盘,将它们绑定到表单上的属性,并在加载和保存之前验证设置数据。 - # EYZ0 < / p >

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

使用Settings选项卡创建应用程序设置。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.<你的属性>将是只读的。

参考:如何:在运行时用c#编写用户设置 -微软文档

ApplicationSettings类不支持将设置保存到app.config文件中。这在很大程度上是故意的;使用安全用户帐户运行的应用程序(如Vista UAC)没有对程序安装文件夹的写访问权。

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

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

如果你打算保存到与你的可执行文件在同一目录下的文件,这里有一个很好的解决方案,使用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/configurationSettings/XML参数似乎仍然非常活跃。随着技术的进步,我都用过了,但我最喜欢的是基于Threed的系统独立存储的组合。

下面的示例允许将名为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) { }
}
}
}

有时你想要去掉传统网络中保留的那些设置。Config或app.config文件。您希望对设置项的部署和分离的数据设计进行更细粒度的控制。或者需求是支持在运行时添加新条目。

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

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

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

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

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

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

    如果你使用。net 4.0动态,这个库真的很容易使用 (序列化,反序列化,嵌套对象支持和排序输出 JsonConfig(使用相当于ApplicationSettingsBase) 对于。net YAML配置库…我还没找到一个 易于使用的JsonConfig

您可以将您的设置文件存储在多个特殊文件夹中(针对所有用户和每个用户),如环境。SpecialFolder枚举和多个文件(默认为只读,每个角色,每个用户等)。

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

一种简单的方法是使用配置数据对象,将其保存为本地文件夹中具有应用程序名称的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>

我想分享我为此建立的一个库。这是一个很小的库,但比.settings文件有很大的改进(恕我冒昧)。

这个库名为记(GitHub)。这是我写的关于它的旧代码项目文章

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

public MainWindow()
{
InitializeComponent();


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

代码相当少,而且由于您只需要提到每个属性一次,因此出错的可能性要小得多。

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

存储、序列化等是完全可配置的。当目标对象由国际奥委会容器创建时,您可以[将其连接起来][],以便它自动应用跟踪到它所解析的所有对象,因此要使属性持久,您所需要做的就是在其上添加[Trackable]属性。

它是高度可配置的,你可以配置: -当数据被持久化并应用于全局或每个跟踪对象时 -它是如何序列化的 -存储在哪里(例如文件,数据库,在线,隔离存储,注册表) -可以取消为

属性应用/持久化数据的规则

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

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

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

是的,存钱是可能的配置-但这很大程度上取决于你选择的方式来做它。让我来描述一下技术上的差异,这样你就可以理解你有哪些选择:

首先,你需要区分,无论你想在你的*.exe.config(也就是Visual Studio中的App.config)文件中使用applicationSettings还是AppSettings -有本质的区别, 被描述在这里

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

  • < a href = " https://stackoverflow.com/a/11841175/1016343 " > AppSettings < / >允许您通过config.Save(ConfigurationSaveMode.Modified);直接读写配置文件,其中配置被定义为:
    config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
  • < a href = " https://stackoverflow.com/a/9704380/1016343 " > applicationSettings < / >允许读取,但如果您写入更改(通过Properties.Settings.Default.Save();),它将在每个用户的基础上写入,存储在一个特殊的地方(例如C:\Documents and Settings\USERID\Local Settings\Application Data\FIRMNAME\WindowsFormsTestApplicati_Url_tdq2oylz33rzq00sxhvxucu5edw2oghw\1.0.0.0)。正如汉斯·帕桑提到过在他的回答中所述,这是因为用户通常对程序文件的权限受到限制,在不调用UAC提示符的情况下不能写入程序文件。缺点是如果你将来要添加配置键,你需要将它们与每个用户配置文件同步

但也有一些其他的选择:

  • 由于. net Core(以及。net 5和。net 6), 第三个选项appsettings.json文件,它使用微软的配置抽象(也是secrets.json文件,它存储在用户配置文件中,而不是在程序集目录中)。但通常WinForms不使用它,所以我只是为了完整起见才提到它。然而,这里有一些参考如何的值。或者,您可以使用Newtonsoft JSON读写 appsettings.json文件,但不仅限于此:您还可以使用该方法创建自己的json文件。

  • 正如问题中提到的,如果你把配置文件作为XML文档,,你可以使用System.Xml.Linq.XDocument类来加载、修改和保存它。它不需要使用自定义XML文件,可以读取现有的配置文件;为了查询元素,你甚至可以使用Linq查询。我已经给出了一个例子这里的< a href = " https://stackoverflow.com/a/9704380/1016343 " > < / >,检查函数GetApplicationSetting在那里的答案。

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

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

如果你需要加密保护你的价值,检查这< a href = " https://stackoverflow.com/a/15342230/1016343 " > < / >的答案。它描述了如何使用微软的DPAPI来存储加密的值。

如果你想支持你自己的文件,无论是XML还是JSON,知道程序集运行的目录可能会很有用:

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

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