资源概述
更新:2007 年 11 月
本概述介绍如何使用 WPF 资源作为重用通常定义的对象和值的简单方法。本概述重点介绍如何在 XAML 中使用资源。您还可以通过使用代码或者通过互换使用代码和可扩展应用程序标记语言 (XAML) 的方式来创建和访问资源。有关更多信息,请参见资源和代码。
本主题包括下列各节。
- 在 XAML 中使用资源
- 静态资源和动态资源
- 样式、DataTemplate 和隐式键
- 相关主题
在 XAML 中使用资源
下面的示例定义一个 SolidColorBrush 作为页面根元素的一个资源。此示例随后引用该资源,并用它来设置若干子元素的属性,这些子元素包括一个 Ellipse、一个 TextBlock 和一个 Button。
<Page Name="root"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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)。您可以在任意元素上定义资源。不过,资源通常在根元素(在本示例中为 Page)上定义。
资源字典中的每个资源必须具有一个唯一键。在标记中定义资源时,通过 x:Key 属性 来分配唯一键。通常,该键是字符串;不过,也可以通过使用适当的标记扩展将其设置为其他对象类型。WPF 中的某些功能区使用资源非字符串键(主要用于样式、组件资源和数据样式设置)。
定义资源之后,可以通过使用指定键名的资源标记扩展语法来引用该资源以用于属性值,例如:
<Button Background="{StaticResource MyBrush}"/>
<Ellipse Fill="{StaticResource MyBrush}"/>
在上例中,当 XAML 加载程序处理 Button 的 Background 属性的值 {StaticResource MyBrush} 时,资源查找逻辑首先在资源字典中查找 Button 元素。如果 Button 没有资源键 MyBrush 的定义(它确实没有,其资源集合为空),查找逻辑接下来将检查 Button 的父元素,即 Page。因此,在 Page 根元素中定义某个资源时,Page 的逻辑树中的所有元素都可以访问该资源,并且您可以重用同一资源,以便对接受该资源所表示的 Type 的任何属性值进行设置。在前面的示例中,同一 MyBrush 资源设置两个不同的属性:Button 的 Background 和 Rectangle 的 Fill。
静态资源和动态资源
资源可以作为静态资源或动态资源进行引用。这是通过使用 StaticResource 标记扩展或 DynamicResource 标记扩展完成的。标记扩展是 XAML 的一项功能,您可以使用标记扩展来处理属性字符串并将对象返回到 XAML 加载程序,从而指定对象引用。有关标记扩展行为的更多信息,请参见标记扩展和 XAML。
使用标记扩展时,通常以字符串的形式来提供由该特定标记扩展处理的一个或多个参数,而不是通过在设置的属性上下文中进行求值。StaticResource 标记扩展通过在所有可用的资源字典中查找键的值来处理该键。这发生在加载过程中,即加载过程需要分配采用静态资源引用的属性值时。DynamicResource 标记扩展则通过创建一个表达式来处理键。该表达式直到实际运行应用程序时才进行求值并提供值。
引用资源时,以下事项会影响是使用静态资源引用还是动态资源引用:
如何为应用程序创建资源的总体设计(按页、在应用程序中、在松散的 XAML 中、在纯资源程序集中)。
应用程序功能:实时更新资源是否是应用程序要求的一部分?
该资源引用类型的相应查找行为。
特定属性或资源类型,以及这些类型的本机行为。
静态资源
静态资源引用最适合于以下情况:
您的应用程序设计几乎将所有的应用程序资源集中到页或应用程序级别的资源字典中。静态资源引用不会基于运行时行为(例如重新加载页)进行重新求值,因此,根据您的资源和应用程序设计避免大量不必要的动态资源引用,这样可以提高性能。
您正在设置不在 DependencyObject 或 Freezable 上的属性的值。
您正在创建将编译为 DLL 并打包为应用程序的一部分或在应用程序之间共享的资源字典。
您正在为自定义控件创建一个主题,并定义在主题中使用的资源。对于这种情况,通常不需要动态资源引用查找行为,而需要静态资源引用行为,以使该查找可预测并且独立于该主题。使用动态资源引用时,即使是主题中的引用也会直到运行时才进行求值,并且在应用主题时,某个本地元素有可能会重新定义您的主题试图引用的键,并且本地元素在查找中会位于主题本身之前。如果发生该情况,主题将不会按预期方式运行。
您正在使用资源来设置大量依赖项属性。依赖项属性具有由属性系统启用的有效值缓存功能,因此,如果您为可以在加载时求值的依赖项属性提供值,该依赖项属性将不必查看重新求值的表达式,并且可以返回最后一个有效值。该方法具有性能优势。
您需要为所有使用者更改基础资源,或者需要通过使用 x:Shared 属性为每个使用者维护独立的可写实例。
静态资源查找行为
查找过程在由设置属性的元素定义的资源字典中查找所请求的键。
然后,查找过程向上遍历逻辑树,直到到达父元素及其资源字典为止。该行为在到达根元素之前将一直持续。
接下来,检查应用程序资源。应用程序资源是 Application 对象为您的 WPF 应用程序定义的资源字典中的资源。
资源字典中的静态资源引用必须引用在引用资源之前已在词法上定义的资源。静态资源引用无法解析前向引用。因此,如果您使用静态资源引用,必须设计资源字典结构,以便将逐个使用的资源定义在各相应资源字典的开头或附近。
静态资源查找可以扩展到主题或系统资源中,但支持该功能仅仅是因为 XAML 加载程序延迟了请求。此延迟是必需的,这样,在加载页时运行时主题可以正确地应用于应用程序。但是,建议不要使用对已知仅存在于主题中或作为系统资源存在的键的静态资源引用。这是因为当用户实时更改主题时,不会对此类引用重新求值。请求主题或系统资源时,动态资源引用更可靠。当主题元素自身请求另一个资源时例外。出于前面所述的原因,这些引用应当是静态资源引用。
找不到静态资源引用时的异常行为有多种。如果延迟资源,则会在运行时发生异常。如果未延迟资源,则会在加载时发生异常。
动态资源
动态资源最适合于以下情况:
资源的值取决于直到运行时才知道的情况。这包括系统资源,或用户可设置的资源。例如,您可以创建引用由 SystemColors、SystemFonts 或 SystemParameters 公开的系统属性的 setter 值。这些值是真正动态的,因为它们最终来自于用户和操作系统的运行时环境。您还可以使用可以更改的应用程序级别的主题,在此情况下,页级别的资源访问还必须捕获更改。
您正在为自定义控件创建或引用主题样式。
您希望在应用程序生存期调整 ResourceDictionary 的内容。
您有一个存在依存关系的复杂资源结构,在这种情况下,可能需要前向引用。静态资源引用不支持前向引用,但动态资源引用支持,因为资源直到运行时才需要进行求值,因此,前向引用不是一个相关概念。
从编译或工作集角度来说,您引用的资源特别大,并且加载页时可能无法立即使用该资源。静态资源引用始终在加载页时从 XAML 加载;而动态资源引用直到实际使用时才会加载。
您要创建的样式的 setter 值可能来自受主题或其他用户设置影响的其他值。
您正在将资源应用到元素,而在应用程序生存期中可能会在逻辑树中重新设置该元素的父级。更改此父级还可能会更改资源查找范围,因此,如果您希望基于新范围对重新设置了父级的元素的资源进行重新求值,请始终使用动态资源引用。
动态资源查找行为
如果您调用 FindResource 或 SetResourceReference,则动态资源引用的资源查找行为与您代码中的查找行为相同。
查找过程在由设置属性的元素定义的资源字典中查找所请求的键。
然后,查找过程向上遍历逻辑树,直到到达父元素及其资源字典为止。该行为在到达根元素之前将一直持续。
接下来,检查应用程序资源。应用程序资源是 Application 对象为您的 WPF 应用程序定义的资源字典中的资源。
对于当前活动的主题,检查主题资源字典。如果主题在运行时更改,将对值重新求值。
检查系统资源。
异常行为(如果有)因情况而异:
如果资源是由 FindResource 调用请求的,并且未找到,则引发异常。
如果资源是由 TryFindResource 调用请求的,并且未找到,则不引发异常,但返回的值为 null。如果设置的属性不接受 null,则仍有可能引发更深的异常(这取决于所设置的各个属性)。
如果某个资源由 XAML 中的动态资源引用请求,并且未找到,则行为取决于常规属性系统,但常规行为如同在资源所在的级别未发生属性设置操作的情况一样。例如,如果尝试在一个使用无法求值的资源的按钮元素上设置背景,则不会生成值集,但有效值仍可能来自属性系统和值优先级中的其他参与者。例如,背景值仍可能来自本地定义的按钮样式,或者来自主题样式。对于不是由主题样式定义的属性,如果资源求值失败,则有效值可能来自于属性元数据中的默认值。
限制
动态资源引用存在一些重要的限制。必须至少存在下列一种情况:
要设置的属性必须是 FrameworkElement 或 FrameworkContentElement 上的属性。DependencyProperty 必须支持该属性。
要设置的属性必须是 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(Button)。在标记中,可以将 TargetType 直接指定为类型名称(也可以选择使用 {x:Type...} 返回一个 Type)。
通过 WPF 使用的默认主题样式机制,该样式作为页上的 Button 的运行时样式进行应用,即使 Button 自身并不尝试指定其 Style 属性或对该样式的特定资源引用时也将如此。在查找顺序中,使用与主题字典样式相同的键时,页中定义的样式将先于主题字典样式找到。您只需在页中的任意位置指定 <Button>Hello</Button>,使用 <Button>Hello</Button> 的 TargetType 定义的样式将应用于该按钮。如果需要,您仍然可以对具有与 TargetType 相同的类型值的样式进行显式键控,以使您的标记更清楚,但这是可选的。
如果 OverridesDefaultStyle 为 true,则样式的隐式键不应用于控件(另请注意,可以针对控件类将 OverridesDefaultStyle 设置为本机行为的一部分,而不是针对控件的某个实例显式设置)。同时,为了对派生类方案支持隐式键,控件必须重写 DefaultStyleKey(由作为 WPF 的一部分提供的所有现有控件执行)。有关样式、主题和控件设计的更多信息,请参见可样式化控件的设计准则。
DataTemplate 也具有一个隐式键。DataTemplate 的隐式键是 DataType 属性值。还可以将 DataType 指定为类型的名称,而不是显式使用 {x:Type...}。有关详细信息,请参见数据模板化概述。