等效于 WPF.NET5、 .NET6或.NET 核心中的 UserSettings/ApplicationSettings

对于.NET 5、 .NET 6或.NET Core > = 3.0的 WPF 应用程序,持久化用户设置的首选方法是什么?
.NET 用户设置去哪了?

创建 WPF. Net Core 3.0计划(VS2019 V16.3.1) 现在我已经看到没有 属性。设置部分了。 [ 2022年最新情况: .NET 6仍然是一样的]

SolutionExplorer

在线搜索之后,开始深入微软。扩展。配置。

除了臃肿的代码访问设置,现在更糟糕-> 没有保存?
.NET 核心中的用户配置设置

幸运或不幸的是,微软。扩展。配置 不支持按设计保存 在 ConfigurationProvider?

中没有保存

持久化 WPF 应用程序的用户设置的首选(以及简单/快速/简单)方法是什么。净核心 > = 3.0/。NET5/.NET6?
在 < = . Net 4.8之前,这就像:
  • 将变量添加到属性。 User Settings

  • 在启动时读取变量
    var culture = new CultureInfo(Properties.Settings.Default.LanguageSettings);

  • 当一个变量改变-> 立即保存它
    Properties.Settings.Default.LanguageSettings = selected.TwoLetterISOLanguageName; Properties.Settings.Default.Save();

30914 次浏览

As pointed out in the posts you referenced, the Microsoft.Extensions.Configuration API is meant as a one time set up for your app, or at the very least to be read-only. If you're main goal is to persist user settings easy/fast/simple, you could roll something up yourself. Storing the settings in the ApplicationData folder, in resemblance to the old API.

public class SettingsManager<T> where T : class
{
private readonly string _filePath;


public SettingsManager(string fileName)
{
_filePath = GetLocalFilePath(fileName);
}


private string GetLocalFilePath(string fileName)
{
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
return Path.Combine(appData, fileName);
}


public T LoadSettings() =>
File.Exists(_filePath) ?
JsonConvert.DeserializeObject<T>(File.ReadAllText(_filePath)) :
null;


public void SaveSettings(T settings)
{
string json = JsonConvert.SerializeObject(settings);
File.WriteAllText(_filePath, json);
}
}

A demo using the most basic of UserSettings

public class UserSettings
{
public string Name { get; set; }
}

I'm not going to provide a full MVVM example, still we'd have an instance in memory, ref _userSettings. Once you load settings, the demo will have its default properties overridden. In production, of course, you wouldn't provide default values on start up. It's just for the purpose of illustration.

public partial class MainWindow : Window
{
private readonly SettingsManager<UserSettings> _settingsManager;
private UserSettings _userSettings;


public MainWindow()
{
InitializeComponent();


_userSettings = new UserSettings() { Name = "Funk" };
_settingsManager = new SettingsManager<UserSettings>("UserSettings.json");
}


private void Button_FromMemory(object sender, RoutedEventArgs e)
{
Apply(_userSettings);
}


private void Button_LoadSettings(object sender, RoutedEventArgs e)
{
_userSettings = _settingsManager.LoadSettings();
Apply(_userSettings);
}


private void Button_SaveSettings(object sender, RoutedEventArgs e)
{
_userSettings.Name = textBox.Text;
_settingsManager.SaveSettings(_userSettings);
}


private void Apply(UserSettings userSettings)
{
textBox.Text = userSettings?.Name ?? "No settings found";
}
}

XAML

<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="10"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" x:Name="textBox" Width="150" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button Grid.Row="1" Click="Button_FromMemory">From Memory</Button>
<Button Grid.Row="2" Click="Button_LoadSettings">Load Settings</Button>
<Button Grid.Row="3" Click="Button_SaveSettings">Save Settings</Button>
</Grid>
</Window>

You can use a Nuget package System.Configuration.ConfigurationManager. It is compatible with .Net Standard 2.0, so it should be usable in .Net Core application.

There is no designer for this, but otherwise it works the same as .Net version, and you should be able to just copy the code from your Settings.Designer.cs. Also, you can override OnPropertyChanged, so there's no need to call Save.

Here's an example, from the working .Net Standard project:

public class WatchConfig: ApplicationSettingsBase
{
static WatchConfig _defaultInstance = (WatchConfig)Synchronized(new WatchConfig());


public static WatchConfig Default { get => _defaultInstance; }


protected override void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Save();
base.OnPropertyChanged(sender, e);
}


[UserScopedSetting]
[global::System.Configuration.DefaultSettingValueAttribute(
@"<?xml    version=""1.0"" encoding=""utf-16""?>
<ArrayOfString>
<string>C:\temp</string>
<string>..\otherdir</string>
</ArrayOfString>")]
public StringCollection Directories
{
get { return (StringCollection)this[nameof(Directories)]; }
set { this[nameof(Directories)] = value; }
}
}

Just double click the Settings.settings file in your project. It will still open up in the designer just like before. You just do not have it listed in Properties windows anymore.

enter image description here

You can add the same old good settings file e.g. via the right click on the Properties -> Add -> New Item and search for the "Settings". The file can be edited in the settings designer and used as in the .net framework projects before (ConfigurationManager, Settings.Default.Upgrade(), Settings.Default.Save, etc. works).

Add also the app.config file to the project root folder (the same way via the Add -> New Item), save the settings once again, compile the project and you will find a .dll.config file in the output folder. You can change now default app values as before.

Tested with Visual Studio 1.16.3.5 and a .net core 3.0 WPF project.

For Wpf Net.Core

Project click Right Mouse Button -> Add New Item -> Settings File (General)

Use

Settings1.Default.Height = this.Height;
Settings1.Default.Width = this.Width;


this.Height = Settings1.Default.Height;
this.Width = Settings1.Default.Width;


Settings1.Default.Save();

Where 'Settings1' created file name

EXAMPLE

Double click 'Settings1.settings' file and Edit

private void MainWindowRoot_SourceInitialized(object sender, EventArgs e)
{
this.Top = Settings1.Default.Top;
this.Left = Settings1.Default.Left;
this.Height = Settings1.Default.Height;
this.Width = Settings1.Default.Width;
// Very quick and dirty - but it does the job
if (Settings1.Default.Maximized)
{
WindowState = WindowState.Maximized;
}
}


private void MainWindowRoot_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (WindowState == WindowState.Maximized)
{
// Use the RestoreBounds as the current values will be 0, 0 and the size of the screen
Settings1.Default.Top = RestoreBounds.Top;
Settings1.Default.Left = RestoreBounds.Left;
Settings1.Default.Height = RestoreBounds.Height;
Settings1.Default.Width = RestoreBounds.Width;
Settings1.Default.Maximized = true;
}
else
{
Settings1.Default.Top = this.Top;
Settings1.Default.Left = this.Left;
Settings1.Default.Height = this.Height;
Settings1.Default.Width = this.Width;
Settings1.Default.Maximized = false;
}


Settings1.Default.Save();
}

Based on Funk's answer here's an abstract generic singleton-style variation that removes some of the administration around SettingsManager and makes creating additional settings classes and using them as simple as possible:

Typed Settings class:

//Use System.Text.Json attributes to control serialization and defaults
public class MySettings : SettingsManager<MySettings>
{
public bool SomeBoolean { get; set; }
public string MyText { get; set; }
}

Usage:

//Loading and reading values
MySettings.Load();
var theText = MySettings.Instance.MyText;
var theBool = MySettings.Instance.SomeBoolean;


//Updating values
MySettings.Instance.MyText = "SomeNewText"
MySettings.Save();

As you can see the number of lines to create and use your settings are just as minimal, and a bit more rigid as there are no parameters.

The base class defines where settings are stored and allows only for one settings file per MySettings subclass - assembly and class names determine its location. For the purpose of replacing a properties file that is enough.

using System;
using System.IO;
using System.Linq;
using System.Reflection;


public abstract class SettingsManager<T> where T : SettingsManager<T>, new()
{
private static readonly string filePath = GetLocalFilePath($"{typeof(T).Name}.json");


public static T Instance { get; private set; }


private static string GetLocalFilePath(string fileName)
{
string appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var companyName = Assembly.GetEntryAssembly().GetCustomAttributes<AssemblyCompanyAttribute>().FirstOrDefault();
return Path.Combine(appData, companyName?.Company ?? Assembly.GetEntryAssembly().GetName().Name, fileName);
}


public static void Load()
{
if (File.Exists(filePath))
Instance = System.Text.Json.JsonSerializer.Deserialize<T>(File.ReadAllText(filePath));
else
Instance = new T();
}


public static void Save()
{
string json = System.Text.Json.JsonSerializer.Serialize(Instance);
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
File.WriteAllText(filePath, json);
}
}

Some improvements might be made in disabling the constructor of the settings subclass and creation of SettingsManager<T>.Instance without Load()ing it; that depends on your own use cases.

My improvements to the accepted answer were rejected, so here as separate answer.

There is no need for any nuget package and no need to roll your own JSON etc.
By default when creating new .NET Core or .NET5/6 projects the settings section is missing and you have to manually add it.

Just manually create the Properties folder in the solution. As you name the new folder Properties you will see that the folder icon will change slightly.

Properties folder

Right click on this new Properties folder and add New Item

Add a Settings File and to be same as in old projects rename the proposed name from Settings1.settings to Settings.settings

Add Settings File

Here you are. Settings are back already.

Settings dialog

You might add an Application Configuration File to get the .config file in the output directory

Add Application Configuration File