【问题标题】:Equivalent to UserSettings / ApplicationSettings in WPF .NET 5, .NET 6 or .Net Core等效于 WPF .NET 5、.NET 6 或 .Net Core 中的 UserSettings / ApplicationSettings
【发布时间】:2019-11-12 19:30:20
【问题描述】:

为使用 .NET 5、.NET 6 或 .Net Core >=3.0 的 WPF 应用程序保留用户设置的首选方法是什么?

创建 WPF .Net Core 3.0 项目(VS2019 V16.3.1) 现在我看到不再有 Properties.Settings 部分了。

在线搜索后,开始深入Microsoft.Extensions.Configuration。

除了访问设置的臃肿代码之外,现在更糟 -> 没有保存?
User Configuration Settings in .NET Core

幸运或不幸的是 Microsoft.Extensions.Configuration 不支持按设计保存。在这个 Github 问题中阅读更多内容Why there is no save in ConfigurationProvider?


使用 .Net Core >=3.0 为 WPF 应用程序持久化用户设置的首选(简单/快速/简单)方式是什么?
之前`
  • 将变量添加到属性。

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

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

【问题讨论】:

  • 我很好奇,是什么导致你想要在运行时改变你的配置文件?是什么促成了这些变化?您是否尝试将您的配置用作某种用户配置文件?
  • 我说的是提高用户便利性的设置。在我上面的例子中,它看起来像是应用程序设置,但我喜欢允许用户选择他们的语言。为什么我要强制我的用户编辑配置文件?为什么不应该提供一个管理区域来让它配置?为什么不应该像How To: Write User Settings at Run Time with C# 那样做呢?我已经成功使用/保存这些用户设置大约 15 年了。我们应该回到注册表吗?回归本源?
  • 我现在没有“答案”。但我希望有某种配置抽象可以写入 LocalApplicationData、ApplicationData 或 CommonApplicationData,具体取决于用户设置的范围。我希望这种抽象会禁止您更改应用程序的应用程序级别配置(出于多种原因,包括您的权限级别可能不足)。这是我对 Microsoft 的客户端应用程序配置代码的期望。您链接的文档是asp.net文档,完全不同。
  • 我认为 Microsoft.Extensions.Configuration 甚至适用于 WPF/WinForms .Net Core,而不仅仅是 ASP.Net。我还没有找到任何其他信息。
  • 我也有类似的挫败感。最后我只是选择了 JSON.net。超级简单

标签: c# wpf .net-core .net-5 .net-6.0


【解决方案1】:

您可以添加相同的旧设置文件,例如通过右键单击“属性”->“添加”->“新项目”并搜索“设置”。该文件可以在设置设计器中进行编辑,并可以在之前的 .net 框架项目中使用(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 项目进行测试。

【讨论】:

  • 原来如此简单!这应该是赏金的答案......用VS2019 V16.3.6测试
  • 据我所知,无法更改赏金分配,但我已将接受的答案更改为您的。包括至少 15 分... :-)
  • 在我的 WPF Core 3.1 项目中,解决方案资源管理器看起来像问题中的样子,而不是答案中的样子。没有属性,这意味着我也无法右键单击它们。
  • 你还需要安装 NuGet 包System.Configuration.ConfigurationManager
  • @PeterHuber 如果您找不到 Properties 文件夹,只需创建它们,然后在其中添加一个 .settings 文件
【解决方案2】:

正如您在引用的帖子中所指出的,Microsoft.Extensions.Configuration API 旨在为您的应用一次性设置,或者至少是只读的。如果您的主要目标是简单/快速/简单地保持用户设置,您可以自己卷起一些东西。将设置存储在 ApplicationData 文件夹中,类似于旧 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最基本的demo

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>

【讨论】:

  • 建议切换到 System.Text.Json 而不是 newtonsoft
  • 这是更好的解决方案!将“旧”设置方法与 MSIX 结合使用会导致您的应用程序设置在每次自动更新时被覆盖。
  • Property.Settings api 有一个保存方法。它不是只读的。在本地 appdata 中存储和加载用户设置非常棒,而且超级容易使用,像这样的设置。它是 TYPE SAFE 而不依赖像设置 [“LeftHotkey”] 这样的蹩脚字符串。
  • @Blechdose 你有没有读过 OP,我们在谈论 Microsoft.Extensions.Configuration,它没有保存方法。我提出的解决方案既是类型安全的,又可以将设置保存在本地 appdata 或您想要的任何位置。
  • @Funk OP 的帖子是关于他在 .NET Core 中缺少的 Property.Settings,而您说的是“配置 API”,然后它指的是 Property.Settings。虽然你是对的,但我错过了“你引用的帖子”部分(你的意思是链接?)。但这仍然令人困惑。您能否将其编辑为“配置 API”到“Microsoft.Extensions.Configuration”?
【解决方案3】:

您可以使用 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; }
    }
}

【讨论】:

  • 对我来说,它似乎是当前最简单的缺失用户设置的替代品。它会将保存方案保留在 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 成功测试
  • 只是将此“自定义设置类”添加到您的项目中,您必须将设置变量添加为带有一些注释的属性,您可以在其中查看它在其他旧 @ 中的样子987654326@.
【解决方案4】:

对于 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() 方法
【解决方案5】:

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

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

只需在解决方案中手动创建 Properties 文件夹。 当您将新文件夹命名为 Properties 时,您会看到文件夹图标会略有变化。

右键单击这个新的属性文件夹并添加新项目

添加一个设置文件并与旧项目相同,将建议的名称从 Settings1.settings 重命名为 Settings.settings

给你。设置已恢复。

您可以添加一个应用程序配置文件来获取输出目录中的 .config 文件

【讨论】:

    【解决方案6】:

    基于 Funk 的answer,这是一个抽象的通用单例样式变体,它删除了一些围绕 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);
        }
    }
    

    在禁用设置子类的构造函数和创建SettingsManager&lt;T&gt;.Instance 而不使用Load() 方面可能会进行一些改进;这取决于您自己的用例。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 我在哪里可以找到这个设置文件? WPF .Net Core App 的解决方案中没有这样的文件,即使在文件夹级别也是如此。刚刚用VS2019 V16.3.3新建的项目再次测试
      • “设置文件”在您右键单击项目并选择添加新项目时位于文件类型列表中。但是,它是一个只读设置文件,因为没有 Save() 方法。 (您也无法调用 Upgrade()Reload(),因此您无法将设置从旧版本移动到新版本。)
      猜你喜欢
      • 1970-01-01
      • 2018-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-05
      • 1970-01-01
      • 2019-04-12
      相关资源
      最近更新 更多