XAML 如何使用ResourceDictionary与依赖注入选项模式?

zxlwwiss  于 2023-09-28  发布在  其他
关注(0)|答案(1)|浏览(110)

我有一个.NET Framework 4.8 WPF(MVVM)项目,我正在工作。我必须使用.NET Framework,我选择使用我可以使用的最新版本的Framework。不过,我也使用了一些通常与.NET应用程序关联的库,即Microsoft.Extensions.Hosting和相关的依赖注入库。
我把一个项目举例说明的问题,我有here
这个项目从我的真实的代码中简化了很多,但它确实得到了重点,那就是:如何在UserControl中引用ResourceDictionary Style,其中为Style设置的值是在运行时提供的?
在这个项目中,以下是一些关键行:

  • 第13行:ProjectB\ResourceDictionary.xaml
  • 第44行:ProjectC\AnotherControlUserControl.xaml
  • 第28行:ProjectA\App.xaml.cs
  • 第32行:ProjectA\App.xaml.cs
  • 第7行:ProjectA\appsettings.json

基本上,我在appsettings.json中有一个十六进制颜色代码。然后将其添加到IHost配置中。然后我使用Options pattern将其Map到类ColorSettings。我的实际Window是由一个嵌套的UserControl组成的,而UserControl又有另一个嵌套的UserControl,这些最终通过Host.CreateDefaultBuilder().ConfigureServices(...)方法或直接示例化XAML中的某些用户控件(没有相应的ViewModel)(例如,第27行(来自ProjectC\MyDisplayUserControl.xaml)。
我遇到的问题是,当我在AnotherControlUserControl中绑定Rectangle时(见第44行),我希望它从appsettings.json中获得适当的颜色十六进制代码,但它没有,而是将其呈现为白色。
从昨天到今天,我已经花了好几个小时来研究这个问题。所以我不能提供一个详尽的清单,我已经尝试过的一切,但这里有一些事情我记得尝试:

  • 创建了另一个类ColorSettingsFactory,它有一个类型为ColorSettings的公共属性,我在DI代码中设置了它的值,例如:ColorSetting = serviceProvider.GetRequiredService<IOptions<ColorSetting>>().Value(XAML不能示例化没有无参数构造函数的类),然后将其添加到我的ResourceDictionary.xaml中,而不是ColorSettings(ResourceDictionary.xaml的第6行)
  • 以各种方式和格式处理绑定语法…我甚至不能开始列举我尝试过的所有组合。我尝试了DynamicResourceStaticResource。我尝试了各种方法来配置Mode=TwoWayUpdateSourceTrigger=PropertyChanged等。
  • 我最初并没有使用ColorSettings来实现INotifyPropertyChanged(这里我是通过Community Toolkit MVVM NuGet ObservableObject实现的),但是我添加了它,并且它也没有做任何事情
  • 我尝试在App.xaml.cs中强制UI更新为startupWindow.UpdateLayout()
  • 有一件事确实有效,但我不喜欢,那就是我将IOptions<ColorSettings>注入到我的MyDisplayViewModel类中,然后将Fill="{Binding ColorSettings.ColorHeader}"添加到AnotherControlUserControl XAML文件中(第44行,再次)。我不喜欢这个解决方案,因为它不优雅。如果可能的话,我希望将所有与Style相关的定义保存在ResourceDictionary.xaml中,部分原因是为了将这类内容集中到一个地方。此外,我可以看到它在其他场景中很有用,我想为其他对象存储运行时值(在这种情况下,我的Rectangle只是用于Grid“单元格”上的背景颜色,但还有许多其他可能的对象可以配置,我不想将无数的IOptions<...>注入到给定的ViewModel中......我可以继续,基本上我只是不喜欢这种变通方法)
  • 我查看了各种Stack Overflow帖子,包括但不限于thisthisthis

最后,当我的类ColorSettings在DI代码中更新时,我希望ResourceDictionary.xaml中的值也会更新,然后在引用的任何地方都反映出来。在我看来,DI容器和XAML之间存在一些脱节,但我不知道如何补救。虽然我有前面提到的解决方法,但如果有一种方法可以根据我的配置文件appsettings.json更新ResourceDictionary.xaml,然后在任何使用的地方都反映出来,我会非常感兴趣。
谢谢你,谢谢!

vyswwuz2

vyswwuz21#

在ProjectB/ResourceDictionary.xaml中,您正在创建一个ColorSettings对象,但从未设置其ColorHeader属性:

<local:ColorSettings x:Key="Colors"/>

如果你想在appsettings.json文件中使用这个值,你需要获取这个值,并设置你要绑定到的实际资源的属性。
您还应该确保应用中只有一个ColorSettings对象/资源。从资源字典中删除资源,并将其定义为某种类型的单例:

public class ColorSettings : ObservableObject
{
    public static ColorSettings Instance { get; } = new ColorSettings();
    
    private string _colorHeader;

    public string ColorHeader { get => _colorHeader; set => SetProperty(ref _colorHeader, value); }
}

将资源字典中的绑定更改为绑定到单例:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ProjectB;assembly=ProjectB">

    <Style x:Key="RectangleHeaderFillStatic" TargetType="Rectangle">
        <Setter Property="Fill" Value="#F8CBAD"/>
    </Style>

    <Style x:Key="RectangleHeaderFillDynamic" TargetType="Rectangle">
        <Setter Property="Fill" Value="{Binding Source={x:Static local:ColorSettings.Instance}, Path=ColorHeader}"/>
    </Style>

</ResourceDictionary>

然后设置数据绑定单例的属性:

private void Application_Startup(object sender, StartupEventArgs e)
{
    MainWindow startupWindow = _appHost.Services.GetRequiredService<MainWindow>();
    var colorSettings = _appHost.Services.GetService<IOptions<ColorSettings>>();
    ColorSettings.Instance.ColorHeader = colorSettings?.Value?.ColorHeader;
    startupWindow.Show();
}

总结;向资源字典中添加(预期的)应用程序范围的资源并将其合并到多个视图中不是一个好主意。您应该在一个中心位置定义一次资源。

相关问题