XAML 资源概述
资源是可在应用中的不同位置重复使用的对象。 资源的示例包括画笔和样式。 本概述介绍如何在可扩展应用程序标记语言(XAML)中使用资源。 还可以使用代码创建和访问资源。
说明
本文中所述的 XAML 资源不同于 应用资源 通常添加到应用的文件,例如内容、数据或嵌入文件。
在 XAML 中使用资源
以下示例将 SolidColorBrush 定义为页面根元素上的资源。 然后,该示例引用资源,并使用它设置多个子元素的属性,包括 Ellipse、TextBlock和 Button。
<Page Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<Style TargetType="Border" x:Key="PageBackground">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style TargetType="TextBlock" x:Key="TitleText">
<Setter Property="Background" Value="Blue"/>
<Setter Property="DockPanel.Dock" Value="Top"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="#4E87D4"/>
<Setter Property="FontFamily" Value="Trebuchet MS"/>
<Setter Property="Margin" Value="0,40,10,10"/>
</Style>
<Style TargetType="TextBlock" x:Key="Label">
<Setter Property="DockPanel.Dock" Value="Right"/>
<Setter Property="FontSize" Value="8"/>
<Setter Property="Foreground" Value="{StaticResource MyBrush}"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,3,10,0"/>
</Style>
</Page.Resources>
<StackPanel>
<Border Style="{StaticResource PageBackground}">
<DockPanel>
<TextBlock Style="{StaticResource TitleText}">Title</TextBlock>
<TextBlock Style="{StaticResource Label}">Label</TextBlock>
<TextBlock DockPanel.Dock="Top" HorizontalAlignment="Left" FontSize="36" Foreground="{StaticResource MyBrush}" Text="Text" Margin="20" />
<Button DockPanel.Dock="Top" HorizontalAlignment="Left" Height="30" Background="{StaticResource MyBrush}" Margin="40">Button</Button>
<Ellipse DockPanel.Dock="Top" HorizontalAlignment="Left" Width="100" Height="100" Fill="{StaticResource MyBrush}" Margin="40" />
</DockPanel>
</Border>
</StackPanel>
</Page>
每个框架级元素(FrameworkElement 或 FrameworkContentElement)都有一个 Resources 属性,该属性是包含已定义资源的 ResourceDictionary 类型。 可以在任何元素上定义资源,例如 Button。 但是,资源最常在根元素上定义,该根元素在示例中 Page。
资源字典中的每个资源都必须具有唯一键。 在标记中定义资源时,可通过 x:Key 指令来分配唯一键。 通常,键是字符串;但是,也可以使用相应的标记扩展将其设置为其他对象类型。 WPF 中的某些功能区域(尤其是样式、组件资源和数据样式)使用资源的非字符串键。
可以将定义的资源与资源标记扩展语法一起使用,该语法指定资源的键名称。 例如,使用资源作为另一个元素上的属性的值。
<Button Background="{StaticResource MyBrush}"/>
<Ellipse Fill="{StaticResource MyBrush}"/>
在前面的示例中,当 XAML 加载程序处理 Button上 Background 属性的值 {StaticResource MyBrush}
时,资源查找逻辑首先检查 Button 元素的资源字典。 如果 Button 没有定义资源键 MyBrush
(在此示例中它确实没有,其资源集合为空),则接下来将查找 Button的父元素,即 Page。 如果在 Page 根元素上定义资源,则 Page 逻辑树中的所有元素都可以访问它。 可以重复使用同一资源来设置接受资源所表示的相同类型的任何属性的值。 在前面的示例中,同一个 MyBrush
资源设置两个不同的属性:Button的 Background,以及 Rectangle的 Fill。
静态和动态资源
资源可引用为静态资源或动态资源。 使用 StaticResource 标记扩展 或 DynamicResource 标记扩展创建引用。 标记扩展是 XAML 的一项功能,可以通过使用标记扩展来处理属性字符串并将对象返回到 XAML 加载程序,从而指定对象引用。 有关标记扩展行为的详细信息,请参阅 标记扩展和 WPF XAML。
使用标记扩展时,通常以字符串形式提供一个或多个由该特定标记扩展处理的参数。 StaticResource 标记扩展通过在所有可用的资源字典中查找键值来处理键。 在加载期间会进行处理,即加载过程需要分配属性值。 DynamicResource 标记扩展则通过创建表达式来处理键,而且表达式会保持未计算状态,直至应用运行为止。当应用实际运行时,表达式会进行计算并提供一个值。
引用资源时,以下注意事项可能会影响使用静态资源引用还是动态资源引用:
确定如何为应用创建资源的整体设计(在每页上、在应用程序中、在宽松的 XAML 中或在仅包含资源的程序集中)时,请考虑以下事项:
应用的功能。 实时更新资源是否是您的应用程序要求的一部分?
该资源引用类型的相应查找行为。
特定属性或资源类型和这些类型的本地化行为。
静态资源
静态资源引用最适合以下情况:
应用设计将大部分资源集中在页面或应用程序级资源字典中。 静态资源引用不会根据运行时行为(例如重新加载页面)重新评估。 因此,根据资源和应用设计,如果避免不必要地使用大量动态资源引用,可能会一定程度地提高性能。
要设置不在 DependencyObject 或 Freezable 上的属性的值。
你要创建一个资源字典,该字典将编译成 DLL,并被打包为应用的一部分或在应用之间共享。
你要为自定义控件创建主题,并定义主题中使用的资源。 在这种情况下,通常不希望执行动态资源引用查找行为,而是希望执行静态资源引用行为,以确保查找可预测并自包含到主题中。 使用动态资源引用时,即使主题中的引用也会在运行时前保持未计算状态。 并且在应用主题时,有可能某些本地元素会重新定义主题试图引用的键,而这些本地元素在查找中会优先于主题本身。 如果发生这种情况,主题的行为将偏离预期方式。
你正在使用资源来设置大量的依赖属性。 依赖属性具有属性系统启用的有效值缓存,因此,如果为可在加载时计算的依赖属性提供值,则依赖属性不必检查重新计算的表达式,并且可以返回最后一个有效值。 此方法可能具有性能优势。
想为所有使用者更改基础资源,或想通过使用 x:Shared 属性为每个使用者维护单独的可写实例。
静态资源查找行为
下面介绍了属性或元素引用静态资源时自动发生的查找过程:
查找进程在用于设置属性的元素所定义的资源字典中查找请求的键。
然后,查找过程将逻辑树向上遍历到父元素及其资源字典。 此过程将继续执行,直到到达根元素。
应用资源已被检查。 应用资源是 WPF 应用 Application 对象定义的资源字典中的这些资源。
资源字典中的静态资源引用必须引用已在资源引用之前已按词法定义的资源。 静态资源引用无法解析前向引用。 因此,请设计资源字典结构,以便在每个相应资源字典的开头或附近定义资源。
静态资源查找可以扩展到主题或系统资源,但仅支持此查找,因为 XAML 加载程序会延迟请求。 延迟是必要的,以便在页面加载时运行时主题能够正确应用于应用程序。 但是,不建议使用对已知仅在主题中存在或作为系统资源存在的键的静态资源引用,因为如果用户实时更改主题,不会重新计算此类引用。 请求主题或系统资源时,动态资源引用更可靠。 例外情况是当主题元素本身请求另一个资源时。 由于前面提到的原因,这些引用应该是静态资源引用。
如果未找到静态资源引用,异常行为会有所不同。 如果资源已延迟,则异常在运行时发生。 如果未延迟资源,则会在加载时发生异常。
动态资源
动态资源在以下情况下最能正常工作:
资源的值(包括系统资源或其他用户可设置的资源)取决于运行时之前未知的条件。 例如,您可以创建 setter 值,以引用由 SystemColors、SystemFonts或 SystemParameters提供的系统属性。 这些值确实是动态的,因为它们最终来自用户和操作系统的运行时环境。 或许还拥有可能会发生变化的应用程序级主题,而页面级资源访问也必须捕获其中的变化。
你要为自定义控件创建或引用主题样式。
打算在应用生存期内调整 ResourceDictionary 的内容。
拥有存在相互依赖关系且可能需要进行前向引用的复杂资源结构。 静态资源引用不支持转发引用,但动态资源引用确实支持它们,因为资源不需要在运行时之前进行评估,因此转发引用不是相关的概念。
从编译集或工作集的角度来看,你引用的资源很大,并且页面加载时可能不会立即使用该资源。 加载页面时,静态资源引用始终从 XAML 加载。 但是,动态资源引用在使用前不会加载。
要创建的样式的 setter 值可能来自受主题或其他用户设置影响的其他值。
要将资源应用于可能会在应用生存期内在逻辑树中重定父级的元素。 父级更改后,资源查找范围也可能会随之更改;因此,如果希望重定父级的元素的资源基于新范围重新进行计算,请始终使用动态资源引用。
动态资源查找行为
如果调用 FindResource 或 SetResourceReference,动态资源引用的资源查找行为与代码中的查找行为并行:
查找在用于设置属性的元素所定义的资源字典中查找请求的键:
如果元素定义 Style 属性,则该元素的 System.Windows.FrameworkElement.Style 将检查其 Resources 字典。
如果元素定义 Template 属性,则检查该元素的 System.Windows.FrameworkTemplate.Resources 字典。
查找功能沿着逻辑树向上遍历,直到父元素及其资源字典。 此过程将继续执行,直到到达根元素。
检查应用资源。 应用资源是 WPF 应用 Application 对象定义的资源字典中的资源。
检查主题资源字典中当前处于活动状态的主题。 如果主题在运行时发生更改,则会重新计算该值。
检查系统资源。
异常行为(如果有)会有所不同:
如果 FindResource 调用请求了某个资源但未找到该资源,则会引发异常。
如果资源由 TryFindResource 调用请求但未找到,则不会引发异常,并且返回值为
null
。 如果所设置的属性不接受null
,则仍可能会引发更深层次的异常,具体取决于要设置的单个属性。如果资源是由 XAML 中的动态资源引用请求的,但未找到,则行为取决于常规属性系统。 常规行为即存在资源的级别上没有发生属性设置操作时执行的行为。 例如,如果尝试使用无法评估的资源在单个按钮元素上设置背景,则无法设置值,但有效值仍可能来自属性系统中的其他组件和值的优先级。 例如,背景值可能仍来自本地定义的按钮样式或主题样式。 对于未由主题样式定义的属性,失败的资源评估后的有效值可能来自属性元数据中的默认值。
限制
动态资源引用存在一些明显的限制。 必须至少满足以下条件之一:
要设置的属性必须是 FrameworkElement 或 FrameworkContentElement的属性。 该属性必须由 DependencyProperty 支持。
该引用用于
StyleSetter
内的值。要设置的属性必须是 Freezable(以 FrameworkElement 或 FrameworkContentElement 属性的值或 Setter 值的形式提供)上的属性。
由于所设置的属性必须是 DependencyProperty 或 Freezable 属性,因此大多数属性更改可以传播到 UI,因为属性更改(更改的动态资源值)由属性系统确认。 大多数控件都包含相应的逻辑;当 DependencyProperty 有所更改且该属性可能会影响布局时,该逻辑将强制使用控件的其他布局。 但是,并不保证所有使用 DynamicResource 标记扩展作为其值的属性都能在 UI 中提供实时更新。 该功能仍可能会因属性而异,以及取决于拥有该属性的类型,甚至是应用的逻辑结构。
样式、DataTemplate 和隐式键
尽管 ResourceDictionary 中的所有项都必须具有键,但这并不意味着所有资源都必须具有显式 x:Key
。 在定义为资源时,多个对象类型支持隐式键,其中键值绑定到另一个属性的值。 这种类型的密钥称为隐式键,而 x:Key
属性是显式键。 可以通过指定显式密钥来覆盖任何隐式密钥。
关于资源,一个重要的方案就是用于定义 Style。 事实上,Style 几乎总是定义为资源字典中的条目,因为样式本质上是用于重复使用的。 有关样式的详细信息,请参阅 样式和模板化。
控件样式可通过隐式键来创建和引用。 定义控件的默认外观的主题样式依赖于此隐式键。 从请求的角度来看,控件本身的隐式键是 Type。 从定义资源的角度来看,样式的隐式键是 TargetType。 因此,如果要创建自定义控件的主题或要创建会与现有主题样式交互的样式,则无需为该 Style 指定 x:Key 指令。 如果要使用主题样式,则根本不需要指定任何样式。 例如,尽管 Style 资源似乎没有键,但以下样式定义也有效:
<Style TargetType="Button">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="AliceBlue"/>
<GradientStop Offset="1.0" Color="Salmon"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="18"/>
</Style>
该样式确实具有一个键:隐式键 typeof(System.Windows.Controls.Button)
。 在标记中,可以直接将 TargetType 作为类型名称指定(或者可以选择使用 {x:Type...} 以返回 Type。
通过 WPF 使用的默认主题样式机制,即使 Button 本身不尝试指定其 Style 属性或对样式的特定资源引用,该样式也将作为页面上 Button 的运行时样式应用。 在页面中定义的样式位于查找序列中的靠前位置(在主题字典样式之前),其所用的键与主题字典样式的键相同。 只需在页面中的任意位置指定 <Button>Hello</Button>
,您用 TargetType 的 Button
定义的样式将应用于该按钮。 如果需要,仍可为此样式显式指定与 TargetType 的类型值相同的键,以求在标记中清楚明示,但这是可选的。
如果 OverridesDefaultStyle 为 true
,则样式的隐式键不会应用于控件。 (另请注意,OverridesDefaultStyle 可能被设置为控件类的本机行为的一部分,而不是在控件的实例上显式设置。)此外,为了支持在派生类方案中使用隐式键,控件必须替代 DefaultStyleKey(作为 WPF 的一部分提供的所有现有控件都包括此替代)。 有关样式、主题和控件设计的详细信息,请参阅 设计样式控件指南。
DataTemplate 还具有隐式密钥。 DataTemplate 的隐式键是 DataType 属性值。 DataType 也可以指定为类型的名称,而不是显式使用 {x:Type...}。 有关详细信息,请参阅 数据模板化概述。