XAML 如何动态地或在编译时合并资源字典?

ejk8hzay  于 2022-12-07  发布在  其他
关注(0)|答案(1)|浏览(107)

假设我们有一个资源字典,定义如下

<Application.Resources>
   <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
          <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
          <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
      </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

如果我想切换Android特定的库,那么为了示例起见,定义这个像

<Application.Resources>
   <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
          <!-- There seem to be no way to choose based on platform.
          <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
          <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
          -->
          <ResourceDictionary Source="Resources/Styles/Android/Colors.xaml" />
          <ResourceDictionary Source="Resources/Styles/Android/Styles.xaml" />
      </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

有没有一种官方的方法可以在编译时基于目标(比如多目标和编译到Android)?
如果我通过MSBuild尝试multitargeting,它要求提供整个路径。
此外,使用OnPlatform的多目标也不可用(即使现在不是编译时,但将来可能会)。
我可以想象MSBuild中的某种正则表达式读取这个App.xaml文件路径,在操作这个文件的步骤之前切换文件,然后再切换回来(这样版本控制就不会受到影响)。但是似乎要弄清楚如何做是一个费力的路径...也许有更好的方法?或者有吗?
这与How to properly add Microsoft Fluent Design colors to Maui application?有关。看起来Material的定义与Fluent的定义有很大的不同,所以不创建包含所有内容的胖资源而是只发送实际需要的内容是有意义的。

ghhkc1vu

ghhkc1vu1#

实际上,多目标定位是一种有效的方法。你只需要在运行时合并字典,就像你设置自定义颜色主题一样。
在应用的 .csproj 文件中,添加以下<ItemGroup>元素(仅显示Android和Windows,但同样适用于iOS等):

<!-- Platform specific XAML Android -->
  <ItemGroup Condition="$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-android')) == true">
    <MauiXaml Update="Resources\Styles\Platform\SpecialStyles.android.xaml">
      <Generator>MSBuild:Compile</Generator>
    </MauiXaml>
  </ItemGroup>
  <ItemGroup Condition="$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-android')) != true">
    <MauiXaml Remove="Resources\Styles\Platform\SpecialStyles.android.xaml" />
    <None Include="Resources\Styles\Platform\SpecialStyles.android.xaml" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
    <Compile Remove="Resources\Styles\Platform\SpecialStyles.android.xaml.cs" />
    <None Include="Resources\Styles\Platform\SpecialStyles.android.xaml.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
  </ItemGroup>

  <!-- Platform specific XAML Windows -->
  <ItemGroup Condition="$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-windows')) == true">
    <MauiXaml Update="Resources\Styles\Platform\SpecialStyles.windows.xaml">
      <Generator>MSBuild:Compile</Generator>
    </MauiXaml>
  </ItemGroup>
  <ItemGroup Condition="$(TargetFramework.StartsWith('net')) == true AND $(TargetFramework.Contains('-windows')) != true">
    <MauiXaml Remove="Resources\Styles\Platform\SpecialStyles.windows.xaml" />
    <None Include="Resources\Styles\Platform\SpecialStyles.windows.xaml" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
    <Compile Remove="Resources\Styles\Platform\SpecialStyles.windows.xaml.cs" />
    <None Include="Resources\Styles\Platform\SpecialStyles.windows.xaml.cs" Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder)" />
  </ItemGroup>

然后,在Resources/Styles文件夹下,您可以创建一个名为Platform或类似文件夹的新文件夹,并添加一个新的资源XAML文件,例如SpecialStyles.android.xaml(和 SpecialStyles.android.xaml.cs)。确保重命名该类并从名称中删除“android”,并添加一些您只想在Android上应用的特殊样式,例如红色背景的按钮:

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                    x:Class="MauiSamples.Resources.Styles.SpecialStyles">

  <Style ApplyToDerivedTypes="True" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Red" />
  </Style>

</ResourceDictionary>

同时在程式码后置中重新命名类别:

namespace MauiSamples.Resources.Styles;

public partial class SpecialStyles : ResourceDictionary
{
    public SpecialStyles()
    {
        InitializeComponent();
    }
}

对Windows执行相同的操作,但将Button设置为绿色:

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSamples.Resources.Styles.SpecialStyles">

  <Style ApplyToDerivedTypes="True" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Green" />
  </Style>

</ResourceDictionary>

最后,在App.xaml.cs中,可以将资源字典与默认资源字典合并,如下所示:

#if ANDROID || WINDOWS
    ICollection<ResourceDictionary> mergedDictionaries = Current.Resources.MergedDictionaries;
    if (mergedDictionaries != null)
    {
        mergedDictionaries.Clear();
        mergedDictionaries.Add(new MauiSamples.Resources.Styles.SpecialStyles());
    }
#endif

注意:请注意预处理器指令,只有在SpecialStyles类只存在于Android或Windows中时才需要。如果您为每个平台分别提供一个带有后缀的SpecialStyles类(例如SpecialStyles.ios.xaml等),则不需要预处理器指令。

由于我对此很好奇,我很快在我的开源MAUI示例GitHub repo中实现了完整的示例:https://github.com/ewerspej/maui-samples

Android上的结果:

Windows上的结果:

更新

这种方法是有效的,我只注意到新创建的XAML文件可能会从解决方案资源管理器中消失,但在构建过程中仍然会处理它们。显然,<MauiXaml>建置动作不会在[方案总管]中根据选取的目的架构重新评估。我认为这是一种不便,而且我'如果我找到更好的解决方案,或者有人提出改进建议,我很乐意更新答案。

相关问题