在较新的.NET版本中,与WPF中的UserSettings/ApplicationSettings相等的是什么?

101

在使用 >=3.0 的 WPF 应用程序中,最好的持久化用户设置的方式是什么?
.NET 用户设置去哪儿了?

创建了WPF .Net Core 3.0项目(VS2019 V16.3.1)
现在我发现没有Properties.Settings部分了。
[更新2022:使用.NET 6仍然是相同的]
[更新2023:使用.NET 7仍然是相同的]

SolutionExplorer

在进行在线搜索后,开始深入研究Microsoft.Extensions.Configuration。

除了访问设置的冗长代码外,现在更糟糕的是 -> 没有保存?
.NET Core中的用户配置设置

幸运或不幸的是,Microsoft.Extensions.Configuration出于设计考虑不支持保存。在此Github问题中了解更多信息:为什么ConfigurationProvider中没有保存?




  • 将变量添加到属性中。 用户设置

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

  • 当变量改变时,立即保存它。
    Properties.Settings.Default.LanguageSettings = selected.TwoLetterISOLanguageName; Properties.Settings.Default.Save();


5
我在谈论改进用户便利性的设置。在我上面的例子中,似乎是应用程序设置,但我想让用户选择他们的语言。为什么要强迫用户编辑配置文件呢?为什么不提供管理员区域来进行配置呢?为什么不像如何:使用C#在运行时编写用户设置那样做呢?我已经成功地使用/保存这些用户设置大约15年了。我们应该回到注册表吗?回到根源? - MarkusEgle
1
我现在没有“答案”。但是我希望有某种配置抽象,根据用户设置的范围,将其写入LocalApplicationData、ApplicationData或CommonApplicationData。我期望这个抽象会禁止您更改应用程序级别的配置(出于许多原因,包括您的权限级别可能不足)。这就是我对Microsoft客户端应用程序配置代码的期望。您提供的文档是asp.net文档,完全不同。 - Nathan Cooper
1
我认为 Microsoft.Extensions.Configuration 是通往 .Net Core 的正确方式,不仅适用于 ASP.Net,也适用于 WPF/WinForms。我还没有找到其他信息。 - MarkusEgle
1
我也有类似的挫败感。最终我选择了JSON.net,非常简单易用。 - GazTheDestroyer
3
所以,对于最基本的事情,我们又回到了“自己动手(制作糟糕的)解决方案”的阶段。我以为我们在20年前就已经摆脱了这种情况。 - user3700562
显示剩余5条评论
8个回答

97

在此输入图片描述

您可以通过右键单击Properties->Add->New Item并搜索“Settings”来添加相同的旧设置文件。该文件可以在设置设计器中进行编辑,并像以前的 .net framework 项目一样使用(如 ConfigurationManager、Settings.Default.Upgrade()、Settings.Default.Save等)。

还需要将 app.config 文件添加到项目根文件夹中(通过 Add->New Item 的相同方式),再次保存设置,编译项目,然后您将在输出文件夹中找到一个 .dll.config 文件。 现在您可以像以前一样更改默认应用程序值。

在 Visual Studio 1.16.3.5 和 .NET Core 3.0 WPF 项目中测试通过。


4
很容易实现!这应该是有赏金答案...在VS2019 V16.3.6上测试过。 - MarkusEgle
15
在我的 WPF Core 3.1 项目中,解决方案资源管理器的外观与问题描述中的类似,而不是答案中的样子。没有属性选项,这意味着我也无法右键单击它们。 - Peter Huber
3
您还需要安装NuGet包System.Configuration.ConfigurationManager - A_Binary_Story
9
如果找不到“Properties”文件夹,可以手动创建,并在其中添加“.settings”文件。请注意,不要更改原意。 - VT Chiew
2
@PeterHuber 我的项目最初没有包括 Properties 文件夹。我不得不创建一个发布配置文件,它在其中创建了带有发布配置文件的 properties 文件夹。然后按照答案建议添加设置。 - Aaron. S
显示剩余8条评论

32

正如您引用的帖子所指出的那样,Microsoft.Extensions.Configuration API旨在作为应用程序的一次性设置,或者至少是只读的。如果您的主要目标是轻松/快速/简单地保存用户设置,可以自己编写代码来实现。将设置存储在文件夹中,类似于旧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);
    }
}

一个使用最基本的UserSettings的演示

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

我不会提供完整的MVVM示例,但我们将在内存中拥有一个实例,引用_userSettings。一旦加载设置,演示将覆盖其默认属性。当然,在生产环境中,您不会在启动时提供默认值。这仅用于说明目的。

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>

3
建议切换到 System.Text.Json 而不是 Newtonsoft。 - Sam Mackrill
这是更好的解决方案!使用“旧”的设置方法与MSIX相结合,会导致您的应用程序设置在每次自动更新时被覆盖。 - Mr. Muh
1
Property.Settings API有一个save方法,它不是只读的。这个API非常棒,而且使用起来超级简单,可以将用户设置存储和加载到本地appdata中,而像这样的设置就属于该位置。它是类型安全的,而不依赖于糟糕的字符串内容,例如settings["LeftHotkey"]。 - Welcor
@Blechdose,你是否已经阅读了OP?我们正在谈论Microsoft.Extensions.Configuration,它没有保存方法。我提出的解决方案既是类型安全的,又可以将设置保存在本地appdata中,或者任何你想要的地方。 - Funk
1
@Funk OP帖子涉及Property.Settings,在.NET Core中缺少该设置,而你提到了“Configuration API”,它实际上指的是Property.Settings。虽然你是正确的,但我错过了“你引用的帖子”部分(你是指链接吗?)。但这仍然令人困惑。请把“Configuration API”改为“Microsoft.Extensions.Configuration”,行吗? - Welcor
1
这算什么进步?从一个运作良好且已经建立的用户设置存储系统,易于使用且集成到IDE中,变成了“噢,那就自己动手做吧,在某个txt文件里存储或者其他什么方式”。整个.NET Core项目与.NET Framework相比是一个巨大的倒退,真是让人难以置信。 - user3700562

14

您可以使用Nuget包 System.Configuration.ConfigurationManager。它与 .Net Standard 2.0 兼容,因此应该可以在 .Net Core 应用程序中使用。

虽然没有为此设计师,但除此之外它的工作方式与 .Net 版本相同,您应该能够从您的 Settings.Designer.cs 中直接复制代码。另外,您可以重写 OnPropertyChanged,因此无需调用 Save

这是一个示例,来自于工作中的 .Net Standard 项目:

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

1
对我来说,它似乎是目前最简单的替代缺失用户设置的方法。它将保持ApplicationData中的保存方案,就像之前一样,您不需要创建app.config。已在VS 2019 Preview 16.2.0 Preview 3.0和.Net Core SDK 3.0.0-preview6-27804以及Nuget包System.Configuration.ConfigurationManager 4.6.0-preview6.199303.8上进行了成功测试。 - MarkusEgle
只需将“custom-settings-class”添加到您的项目中,其中必须将设置变量作为属性添加,并使用一些注释,您可以在其他旧的Settings.Designer.cs中查看它应该是什么样子。 - MarkusEgle

13

我的对已接受答案的改进被拒绝了,所以我在这里单独回答。

没有必要使用任何NuGet包,也不需要自己编写JSON等。
默认情况下,在创建新的.NET Core或.NET5/6/7项目时缺少设置部分,您需要手动添加它。

<2023年更新>

正如Viacheslav所描述的那样,有一个解决方案,几乎只需点击一个按钮。打开项目属性,转到“设置”,然后只需点击链接“创建或打开应用程序设置”。
在VS 2022和上进行了测试。

Settings in project properties

描述的旧手动方法在此更新中保留。

</Update 2023>


只需在解决方案中手动创建一个名为“Properties”的文件夹。 当您命名新文件夹为Properties时,您会发现文件夹图标会稍微改变。

Properties folder

右键单击此新的“属性”文件夹,并添加新项目。
添加一个设置文件,并将建议的名称从Settings1.settings改为Settings.settings,以与旧项目相同。

Add Settings File

给你。设置已经恢复了。

Settings dialog

你可以添加一个应用程序配置文件,以便在输出目录中获取.config文件。

Add Application Configuration File


7

对于 Wpf Net.Core

项目,右键单击鼠标 -> 添加新项 -> 设置文件(常规)

使用

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

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

Settings1.Default.Save();

'Settings1' 是创建的文件名称

示例

双击 'Settings1.settings' 文件进行编辑

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

有趣的是,它在WPF中运行(Net Core 3.1),但在控制台应用程序(Net Core 3.1)中不起作用。控制台应用程序版本根本没有.Save()方法。 - Welcor
@Welcor 我的.NET控制台应用程序在安装了“System.Configuration.ConfigurationManager”NuGet包之前没有保存方法。然后它就可以工作了。 - Manuel Hoffmann

6

根据Funk在这里的回答,这是一个抽象的通用的单例风格变体,它消除了SettingsManager周围的一些管理,并使创建其他设置类和使用它们尽可能简单:

类型化的设置类:

//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; }
}

使用方法:

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

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

您可以看到,创建和使用设置所需的行数非常少,并且有点更加严格,因为没有参数。

基类定义了存储设置的位置,并且每个MySettings子类仅允许一个设置文件 - 组装和类名称确定其位置。为了替换属性文件,这已经足够了。

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

在禁用设置子类的构造函数和创建未经Load()装载的SettingsManager<T>.Instance方面可以进行一些改进;这取决于你自己的使用情况。


2

只需点击一个按钮

项目 => 属性 -> 资源

Project Resources Settings


你的截图只展示了如何创建/打开程序集资源。你应该在截图底部包含“设置”部分。 - MarkusEgle

-1

只需在项目中双击 Settings.settings 文件。它仍将像以前一样在设计器中打开。您只是不再在属性窗口中列出它。


我在哪里可以找到这个设置文件?在 WPF .Net Core 应用程序的解决方案中,甚至在文件夹级别上也没有这样的文件。我刚刚使用 VS2019 V16.3.3 创建了一个新项目并进行了测试。 - MarkusEgle
2
当您右键单击项目并选择“添加新项”时,“设置文件”将出现在文件类型列表中。但是,它是只读的设置文件,因为没有“Save()”方法。(您也无法调用“Upgrade()”或“Reload()”,因此无法将设置从旧版本移动到新版本。) - skst

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接