ResourceDictionary 和 XAML 资源引用

可以使用 XAML 为应用定义 UI 或资源。 资源通常是预期多次使用的某些对象的定义。 若要稍后引用 XAML 资源,请为充当其名称的资源指定密钥。 可以在应用内或应用内的任何 XAML 页面中引用资源。 可以使用 Windows 运行时 XAML 中的 ResourceDictionary 元素定义资源。 然后,可以使用 StaticResource 标记扩展ThemeResource 标记扩展来引用资源。

你可能希望最常声明为 XAML 资源的 XAML 元素包括 StyleControlTemplate、动画组件和 Brush 子类。 我们在此处介绍如何定义 ResourceDictionary 和键控资源,以及 XAML 资源与你定义为应用或应用包一部分的其他资源有何关系。 我们还介绍了资源字典的高级功能,例如:MergedDictionariesThemeDictionaries

注释

WinUI 3/Windows 应用 SDK 和 UWP 应用都支持 C# 和 C++/WinRT。 C++/CX 仅适用于 UWP,WinUI 3 不支持。

先决条件

深入了解 XAML 标记。 我们推荐阅读 XAML 概述

定义和使用 XAML 资源

XAML 资源是多次从标记中引用的对象。 资源在 ResourceDictionary 中定义,通常位于单独的文件或标记页顶部,如下所示。

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Page.Resources>
        <x:String x:Key="greeting">Hello world</x:String>
        <x:String x:Key="goodbye">Goodbye world</x:String>
    </Page.Resources>

    <TextBlock Text="{StaticResource greeting}" Foreground="Gray" VerticalAlignment="Center"/>
</Page>

在本示例中:

  • <Page.Resources>…</Page.Resources> - 定义资源字典。
  • <x:String> - 使用键“greeting”定义资源。
  • {StaticResource greeting} - 查找带有键“greeting”的资源,它已分配给 TextBlockText 属性。

注释

不要将与 ResourceDictionary 相关的概念与 资源 生成作、资源 (.resw) 文件或其他“资源”混淆,这些“资源”是在构建生成应用包的代码项目上下文中讨论的。

资源不必是字符串;它们可以是任何可共享对象,例如样式、模板、画笔和颜色。 但是,控件、形状和其他 FrameworkElement不可共享,因此无法将其声明为可重用资源。 有关共享的详细信息,请参阅本主题后面的 XAML 资源必须可共享部分。

在这里,画笔和字符串都声明为资源,并由页面中的控件使用。

<Page
    x:Class="SpiderMSDN.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Page.Resources>
        <SolidColorBrush x:Key="myFavoriteColor" Color="green"/>
        <x:String x:Key="greeting">Hello world</x:String>
    </Page.Resources>

    <TextBlock Foreground="{StaticResource myFavoriteColor}" Text="{StaticResource greeting}" VerticalAlignment="Top"/>
    <Button Foreground="{StaticResource myFavoriteColor}" Content="{StaticResource greeting}" VerticalAlignment="Center"/>
</Page>

所有资源都需要有一个密钥。 通常,该键是使用 x:Key="myString". 定义的字符串。 但是,还有其他几种指定键的方法:

  • StyleControlTemplate 需要 TargetType,如果未指定 x:Key,则使用 TargetType 作为键。 在这种情况下,键是实际的 Type 对象,而不是字符串。 (请参阅以下示例)
  • 如果未指定 x:Key,具有 TargetTypeDataTemplate 资源将使用 TargetType 作为键。 在这种情况下,键是实际的 Type 对象,而不是字符串。
  • 可以使用 x:Name 而不是 x:Key。 但是,x:Name 还会为资源生成代码隐藏字段。 因此,x:Name 的效率低于 x:Key,因为加载页面时需要初始化该字段。

StaticResource 标记扩展只能使用字符串名称(x:Keyx:Name)检索资源。 有时,XAML 框架在决定未设置 StyleContentTemplateItemTemplate 属性的控件应使用哪种样式和模板时,也会查找隐式样式资源(这些资源使用 TargetType 而不是 x:Key 或 x:Name)。

此处, Style 具有 typeof(Button)的隐式键,并且由于页面底部的 Button 未指定 Style 属性,因此它会查找具有 typeof(Button)键的样式:

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="Red"/>
        </Style>
    </Page.Resources>
    <Grid>
       <!-- This button will have a red background. -->
       <Button Content="Button" Height="100" VerticalAlignment="Center" Width="100"/>
    </Grid>
</Page>

有关隐式样式及其工作原理的更多信息,请参阅 控件样式控件模板

在代码中查找资源

像任何其他字典一样,访问资源字典的成员。

警告

在代码中执行资源查找时,只会查看字典中的 Page.Resources 资源。 与 StaticResource 标记扩展不同,如果未在第一个字典中找到资源,则代码不会回退到 Application.Resources 字典。

此示例演示如何从页面的资源字典中检索 redButtonStyle 资源:

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Page.Resources>
        <Style TargetType="Button" x:Key="redButtonStyle">
            <Setter Property="Background" Value="red"/>
        </Style>
    </Page.Resources>
</Page>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            Style redButtonStyle = (Style)this.Resources["redButtonStyle"];
        }
    }
    MainPage::MainPage()
    {
        InitializeComponent();
        Windows::UI::Xaml::Style style = Resources().TryLookup(winrt::box_value(L"redButtonStyle")).as<Windows::UI::Xaml::Style>();
    }

若要从代码中查找应用范围资源,请使用 Application.Current.Resources 获取应用的资源字典,如下所示。

<Application
    x:Class="MSDNSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SpiderMSDN">
    <Application.Resources>
        <Style TargetType="Button" x:Key="appButtonStyle">
            <Setter Property="Background" Value="red"/>
        </Style>
    </Application.Resources>

</Application>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            Style appButtonStyle = (Style)Application.Current.Resources["appButtonStyle"];
        }
    }
    MainPage::MainPage()
    {
        InitializeComponent();
        Windows::UI::Xaml::Style style = Application::Current().Resources()
                                                               .TryLookup(winrt::box_value(L"appButtonStyle"))
                                                               .as<Windows::UI::Xaml::Style>();
    }

还可以在代码中添加应用程序资源。

执行此作时,需要记住两点。

  • 首先,需要在任何页面尝试使用该资源之前添加资源。
  • 其次,不能在应用的构造函数中添加资源。

如果在 Application.OnLaunched 方法中添加资源,可以避免这两个问题,如下所示。

// App.xaml.cs
    
sealed partial class App : Application
{
    protected override void OnLaunched(LaunchActivatedEventArgs e)
    {
        Frame rootFrame = m_window?.Content as Frame; // WinUI 3: Window.Current is always null; store the Window instance in a field
        if (rootFrame == null)
        {
            SolidColorBrush brush = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 0, 255, 0)); // green
            this.Resources["brush"] = brush;
            // … Other code that VS generates for you …
        }
    }
}
// App.cpp

void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
    Frame rootFrame{ nullptr };
    auto content = m_window.Content(); // WinUI 3: Window::Current() is always null; store the Window instance in a member field
    if (content)
    {
        rootFrame = content.try_as<Frame>();
    }

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == nullptr)
    {
        Microsoft::UI::Xaml::Media::SolidColorBrush brush{ Windows::UI::ColorHelper::FromArgb(255, 0, 255, 0) };
        Resources().Insert(winrt::box_value(L"brush"), winrt::box_value(brush));
        // … Other code that VS generates for you …

每个 FrameworkElement 都可以有 ResourceDictionary

FrameworkElement 是一个供控件继承的基类,并且拥有 Resources 属性。 因此你可以将本地资源字典添加到任何 FrameworkElement

此时,PageBorder 都具有资源字典,并且都具有名为“greeting”的资源。 名为“ textBlock2”的 TextBlock 位于 边框内,因此其资源查找首先查找 边框的资源,然后是 页面的资源,然后是 应用程序 资源。 TextBlock 将显示“Hola mundo”。

若要从代码访问该元素的资源,请使用该元素的 Resources 属性。 在代码中访问 FrameworkElement的资源(而不是在 XAML 中访问)时,只会在该字典中查找,而不会在父元素的字典中查找。

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <x:String x:Key="greeting">Hello world</x:String>
    </Page.Resources>
    
    <StackPanel>
        <!-- Displays "Hello world" -->
        <TextBlock x:Name="textBlock1" Text="{StaticResource greeting}"/>

        <Border x:Name="border">
            <Border.Resources>
                <x:String x:Key="greeting">Hola mundo</x:String>
            </Border.Resources>
            <!-- Displays "Hola mundo" -->
            <TextBlock x:Name="textBlock2" Text="{StaticResource greeting}"/>
        </Border>

        <!-- Displays "Hola mundo", set in code. -->
        <TextBlock x:Name="textBlock3"/>
    </StackPanel>
</Page>

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            textBlock3.Text = (string)border.Resources["greeting"];
        }
    }
    MainPage::MainPage()
    {
        InitializeComponent();
        textBlock3().Text(unbox_value<hstring>(border().Resources().TryLookup(winrt::box_value(L"greeting"))));
    }

合并的资源字典

合并的资源字典将一个资源字典合并到通常在其他文件中的另一个字典。

提示 在 Microsoft Visual Studio 中,可以使用“>”菜单中的“添加 > 新项...”选择“ 资源字典”来创建资源字典文件。

在这里,你将在名为 Dictionary1.xaml 的单独 XAML 文件中定义资源字典。

<!-- Dictionary1.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MSDNSample">

    <SolidColorBrush x:Key="brush" Color="Red"/>

</ResourceDictionary>

若要使用该字典,请将其与页面的字典合并:

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>

            <x:String x:Key="greeting">Hello world</x:String>

        </ResourceDictionary>
    </Page.Resources>

    <TextBlock Foreground="{StaticResource brush}" Text="{StaticResource greeting}" VerticalAlignment="Center"/>
</Page>

下面是此示例中发生的情况。 在 <Page.Resources> 中,声明 <ResourceDictionary>。 将资源添加到 <Page.Resources>时,XAML 框架会隐式创建资源字典;但是,在这种情况下,你不需要任何资源字典,而需要包含合并字典的资源字典。

因此请声明 <ResourceDictionary>,然后将内容添加到其 <ResourceDictionary.MergedDictionaries> 集合。 其中每个条目都采用 <ResourceDictionary Source="Dictionary1.xaml"/> 形式。 若要添加多个字典,只需在第一个条目后添加一个 <ResourceDictionary Source="Dictionary2.xaml"/> 条目。

之后 <ResourceDictionary.MergedDictionaries>…</ResourceDictionary.MergedDictionaries>,可以选择将其他资源放在主字典中。 你可以使用要合并到的字典中的资源,如同使用常规字典一样。 在上面的示例中,{StaticResource brush} 在子字典或合并字典(Dictionary1.xaml)中找到资源,而 {StaticResource greeting} 在主页字典中找到其资源。

在资源查找序列中,仅在检查 ResourceDictionary 的所有其他键控资源后,才会检查 MergedDictionaries 字典。 搜索该级别后,查找会深入到合并后的字典,且对 MergedDictionaries 中的每一项进行检查。 如果存在多个合并的字典,会按在 MergedDictionaries 属性中声明这些字典的顺序的相反顺序来检查它们。 在以下示例中,如果 Dictionary2.xaml 和 Dictionary1.xaml 都声明了同一个键,则首先使用 Dictionary2.xaml 中的键,因为它是 MergedDictionaries 集中的最后一个键。

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml"/>
                <ResourceDictionary Source="Dictionary2.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Page.Resources>

    <TextBlock Foreground="{StaticResource brush}" Text="greetings!" VerticalAlignment="Center"/>
</Page>

在任一 ResourceDictionary 范围内,均会检查字典中键的唯一性。 但是,该范围不会扩展到不同 MergedDictionaries 文件中的不同条目。

可以结合使用查找序列和跨合并字典范围不强制使用唯一键来创建 ResourceDictionary 资源的回退值序列。 例如,可以使用同步到应用状态和用户首选项数据的资源字典,将特定画笔颜色的用户首选项存储在序列中最后一个合并的资源字典中。 但是,如果尚不存在用户首选项,则可以在初始 MergedDictionaries 文件中为 ResourceDictionary 资源定义相同的键字符串,并且可以用作回退值。 请记住,始终会在检查合并字典之前检查你在主要资源字典中提供的任何值,所以如果希望使用回退技术,则不要在主要资源字典中定义该资源。

主题资源和主题字典

ThemeResource 类似于 StaticResource,但在主题更改时会重新评估资源查找。

在此示例中,将 TextBlock 的前景设置为当前主题中的值。

<TextBlock Text="hello world" Foreground="{ThemeResource FocusVisualWhiteStrokeThemeBrush}" VerticalAlignment="Center"/>

主题字典是一种特殊类型的合并字典,用于保存用户当前在其设备上使用的主题的不同资源。 例如,“浅色”主题可能使用白色画笔,而“深色”主题可能使用深色画笔。 画笔会更改它所溶入的资源,但使用该画笔作为资源的控件的组成可能保持不变。 若要在自己的模板和样式中重现主题切换行为,而不是使用 MergedDictionaries 作为属性将项合并到主字典中,请使用 ThemeDictionaries 属性。

ThemeDictionaries 内的每个 ResourceDictionary 元素必须具有一个 x:Key 值。 该值是一个字符串,用于命名相关主题,例如“Default”、“Dark”、“Light”或“HighContrast”。 通常, Dictionary1Dictionary2 定义具有相同名称但不同值的资源。

在这里,浅色主题使用红色文本,深色主题使用蓝色文本。

<!-- Dictionary1.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MSDNSample">

    <SolidColorBrush x:Key="brush" Color="Red"/>

</ResourceDictionary>

<!-- Dictionary2.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MSDNSample">

    <SolidColorBrush x:Key="brush" Color="blue"/>

</ResourceDictionary>

在此示例中,将 TextBlock 的前景设置为当前主题中的值。

<Page
    x:Class="MSDNSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <ResourceDictionary>
            <ResourceDictionary.ThemeDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml" x:Key="Light"/>
                <ResourceDictionary Source="Dictionary2.xaml" x:Key="Dark"/>
            </ResourceDictionary.ThemeDictionaries>
        </ResourceDictionary>
    </Page.Resources>
    <TextBlock Foreground="{StaticResource brush}" Text="hello world" VerticalAlignment="Center"/>
</Page>

对于主题字典,每当使用 ThemeResource 标记扩展 进行引用并且系统检测到主题更改时,用于资源查找的当前字典将会动态改变。 系统执行的查找行为基于将活动主题映射到特定主题字典的 x:Key 操作。

了解默认 XAML 设计资源中主题字典的结构方式可能很有用,这些字典与 Windows 运行时默认用于控件的模板相对应。 使用文本编辑器或 IDE 在 \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK 版本>\Generic 中打开 XAML 文件。 请注意如何在 generic.xaml 中首先定义主题字典,以及每个主题字典如何定义相同的键。 然后每个这样的键由构成各种键控元素的元素所引用,这些键控元素位于主题字典外部并且稍后在 XAML 中定义。 还有一个单独的 themeresources.xaml 文件用于设计,该文件仅包含主题资源和额外模板,而不是默认控件模板。 主题区域是 generic.xaml 中看到的内容的副本。

使用 XAML 设计工具编辑样式和模板的副本时,设计工具会从 XAML 设计资源字典中提取部分,并将其作为作为应用和项目的 XAML 字典元素的本地副本放置。

有关详细信息以及适用于应用的主题和系统资源的列表,请参阅 XAML 主题资源

针对 XAML 资源引用的查找行为

查找行为 是描述 XAML 资源系统如何尝试查找 XAML 资源的术语。 当在应用的 XAML 中某个地方以 XAML 资源引用的形式引用一个键时,会触发查找。 首先,资源系统具有可预测行为,它将为此类行为根据作用域检查是否存在资源。 如果在初始范围中找不到资源,则范围会扩展。 将在应用或系统可能定义了 XAML 资源的位置和作用域上继续查找行为。 如果所有可能的资源查找尝试都失败,通常会导致错误。 通常可以在开发过程中消除这些错误。

XAML 资源引用的查找行为从应用实际使用的对象及其自身的 Resources 属性开始。 如果此处存在 ResourceDictionary,则会检查该“ResourceDictionary”以查看其中是否存在具有所请求键的项。 第一级查找很少是相关的,因为你通常不会在同一对象上定义并引用资源。 事实上,此处通常不存在 Resources 属性。 可以从 XAML 中的几乎任何位置进行 XAML 资源引用;不限于 FrameworkElement 子类的属性。

然后,查找序列检查应用的运行时对象树中的下一个父对象。 如果存在一个 FrameworkElement.Resources 并且它持有一个 ResourceDictionary,将请求具有指定键字符串的字典项。 如果找到资源,则查找序列将停止,并将对象提供到引用所在的位置。 否则,查找行为会向对象树根方向前进到下一个父级对象。 此搜索会继续递归前进,直至到达 XAML 的根元素,从而完成对所有可能直接资源位置的搜索。

注释

在页面根级别定义所有即时资源是一种常见做法,既要利用此资源查找行为,又要作为 XAML 标记样式的约定。

如果在即时资源中找不到请求的资源,下一个查找步骤是检查 Application.Resources 属性。 Application.Resources 是放置任何应用程序专用资源的最佳位置,这些资源被你应用程序导航结构中的多个页面引用。

重要

添加到 ResourceDictionary 的资源的顺序会影响应用资源的顺序。 XamlControlsResources 字典会替代许多默认资源键,因此应该首先将其添加到Application.Resources,从而不覆盖程序中的任何其他自定义样式或资源。

控件模板在引用查找中具有另一个可能的位置:主题字典。 主题字典是一个 XAML 文件,其中包含 ResourceDictionary 元素作为其根。 主题字典可能是 Application.Resources 中的合并字典。 主题字典也可能是一个特定于控件的主题字典,用于模板化的自定义控件。

最后,还有一种针对平台资源的资源查找。 平台资源包括为每个系统 UI 主题定义的控件模板,以及定义用于 Windows 运行时应用中 UI 的所有控件的默认外观。 平台资源还包括一组与系统范围外观和主题相关的命名资源。 这些资源在技术上是 MergedDictionaries 项,因此可在应用加载后从 XAML 或代码查找。 例如,系统主题资源包括名为“SystemColorWindowTextColor”的资源,该 资源提供颜色 定义,以将应用文本颜色与来自作系统和用户首选项的系统窗口的文本颜色匹配。 您的应用中的其他 XAML 样式可以引用此样式,或者代码可以获取资源查找值(在示例中将其转换为颜色 )。

有关详细信息以及适用于使用 XAML 的 Windows 应用可用的特定于主题的系统资源的列表,请参阅 XAML 主题资源

如果请求的密钥仍然未在任何这些位置中找到,则会发生 XAML 分析错误/异常。 在某些情况下,XAML 分析异常可能是 XAML 标记编译作或 XAML 设计环境未检测到的运行时异常。

由于资源字典的分层查找行为,可以特意定义多个资源项,这些资源项与键具有相同的字符串值,前提是每个资源在不同的级别定义。 换句话说,尽管键在任何给定 ResourceDictionary 中必须是唯一的,但唯一性要求不会扩展到整个查找行为序列。 在查找期间,只有成功检索的第一个此类对象用于 XAML 资源引用,然后查找将停止。 你可以利用此行为在应用 XAML 内的各个位置通过键请求相同的 XAML 资源,但会取回不同的资源,具体取决于进行 XAML 资源引用的范围以及特殊查找的行为方式。

ResourceDictionary 中的前向引用

特定资源字典中的 XAML 资源引用必须引用已使用键定义的资源,并且该资源必须在资源引用之前按词法显示。 XAML 资源引用无法解析前向引用。 因此,如果从另一个资源中使用 XAML 资源引用,则必须设计资源字典结构,以便先在资源字典中定义其他资源使用的资源。

在应用级别定义的资源不能引用即时资源。 这类似于尝试进行前向引用,因为应用程序资源实际上是在应用首次启动时以及在加载任何导航页面内容之前就被处理了。 但是,任何即时资源都可以引用应用资源,这可以是避免前向引用情况的有用技术。

XAML 资源必须可共享

要使对象可存在于 ResourceDictionary 中,该对象必须可共享

可共享是必需的,因为当在运行时构造和使用应用的对象树时,树中的多个位置不能存在对象。 在内部,资源系统会在请求每个 XAML 资源时创建资源值的副本,以在应用的对象图中使用。

通常,ResourceDictionary 和 Windows 运行时 XAML 支持将下列对象用于共享:

如果遵循必要的实现模式,还可以将自定义类型用作可共享资源。 可以在支持代码(或包含的运行时组件)中定义此类,然后将这些类实例化为 XAML 中的资源。 示例包括对象数据源和用于数据绑定的 IValueConverter 实现。

自定义类型必须具有默认构造函数,因为这是 XAML 分析程序用来实例化类的构造函数。 用作资源的自定义类型在其继承中不能有 UIElement 类,因为 UIElement 永远无法被共享(它始终代表运行时应用程序对象图中某个特定位置的一个 UI 元素)。

UserControl 用法范围

UserControl 元素在资源查找的行为方面具有特殊性,因为它具有定义范围和使用范围的内在概念。 从其定义范围内进行 XAML 资源引用的 UserControl 必须能够支持在其自己的定义范围查找序列中查找该资源,也就是说,它不能访问应用资源。 在 UserControl 使用范围中,将资源引用视为在朝向其使用页面根方向的查询序列内(就像从加载的对象树中的一个对象执行的任何其他资源引用一样),并且可访问应用资源。

ResourceDictionary 和 XamlReader.Load

可以将 ResourceDictionary 用作 XamlReader.Load 方法的根或 XAML 输入的一部分。 您也可以在 XAML 中包含 XAML 资源引用,前提是所有此类引用都完整地包含在提交加载的 XAML 中。 XamlReader.Load 在不了解任何其他 ResourceDictionary 对象(甚至不了解 Application.Resources)的上下文中分析 XAML。 此外,请勿在提交给 {ThemeResource} 的 XAML 中使用

通过代码使用 ResourceDictionary

大部分情况下,ResourceDictionary 均在 XAML 中专门处理。 将 ResourceDictionary 容器和资源声明为 UI 定义文件中的 XAML 文件或 XAML 节点集。 然后使用 XAML 资源引用从 XAML 的其他部分请求这些资源。 不过,在某些情况下,你的应用可能想要使用运行应用时执行的代码来调整 ResourceDictionary 的内容,或者至少要查询 ResourceDictionary 的内容以查看资源是否已定义。 这些代码调用在“ResourceDictionary”实例上执行,所以必须首先通过获得 FrameworkElement.Resources 来检索对象树中的即时 ResourceDictionary,或者检索 Application.Current.Resources

在 C# 中,可以使用索引器(Item)引用给定 ResourceDictionary 中的资源。 ResourceDictionary 是字符串键式字典,因此索引器使用字符串键而不是整数索引。 在 Visual C++ 组件扩展 (C++/CX) 代码中,请使用 Lookup

当使用代码检查或更改 ResourceDictionary 时,API 的行为(如 LookupItem)不会从直接资源遍历到应用资源;这是仅在加载 XAML 页面时发生的 XAML 分析程序行为。 在运行时,密钥的范围限于你当时所用的 ResourceDictionary 实例内。 但是,该作用域不会扩展到 MergedDictionaries 中。

此外,如果请求 ResourceDictionary 中不存在的密钥,则可能没有错误;返回值可能只是以 null 的形式提供。 不过,如果尝试将返回的 null 用作值,仍可能会收到错误。 错误可能来自于属性的设置器,而不是你的 ResourceDictionary 调用。 避免错误的唯一方法是属性接受 null 作为有效值。 请注意,此行为与 XAML 分析时 XAML 查找行为形成鲜明对比;在分析时无法从 XAML 解析提供的密钥会导致 XAML 分析错误,即使在属性可能接受 null 的情况下也是如此。

合并的资源字典包含在主要资源字典的索引范围内,该字典在运行时引用合并字典。 换句话说,可以使用主要字典的“Item”或 Lookup 查找在合并字典中实际定义的任何对象。 在这种情况下,查找行为与分析时 XAML 查找行为类似:如果合并字典中存在多个对象,每个对象具有相同键,则返回上一个添加字典中的对象。

可以通过调用 AddInsert(C++/CX)将项添加到现有 ResourceDictionary。 可以将项添加到即时资源或应用资源。 其中任一 API 调用都需要一个密钥,这满足 ResourceDictionary 中的每个项必须具有密钥的要求。 但是,在运行时添加到 ResourceDictionary 的项与 XAML 资源引用无关。 在加载应用后首次分析 XAML 时,将执行必要的 XAML 资源引用查找。 此时,在运行时添加到集合的资源不可用,而更改 ResourceDictionary 并不会使已从中检索到的资源失效,即使更改该资源的值也是如此。

还可以在运行时从 ResourceDictionary 中删除项、创建某些项或所有项的副本或其他作。 ResourceDictionary 的成员列表指示哪些 API 可用。 请注意,由于 ResourceDictionary 具有支持其基础集合接口的投影 API,因此 API 选项因是否使用 C# 与 C++/CX 而异。

ResourceDictionary 资源字典与本地化

一个 XAML ResourceDictionary 可能最初包含要本地化的字符串。 如果是,请将这些字符串存储为项目资源,而不是存储在 ResourceDictionary 中。 应该将字符串从 XAML 中取出,而应该为所属元素指定一个 x:Uid 指令 值。 然后,在资源文件中定义资源。 以 XUIDValue.PropertyName 的形式提供资源名称,并提供应该本地化的字符串的资源值。

自定义资源查找

对于高级方案,可以实现一个类,该类的行为可能与本主题中所述的 XAML 资源引用查找行为不同。 为此,你需要实现类 CustomXamlResourceLoader,然后可以通过使用资源引用的 CustomResource 标记扩展 来访问该行为,而不是使用 StaticResourceThemeResource。 大多数应用都不会出现需要这种情况的情况。 有关详细信息,请参阅 CustomXamlResourceLoader