WPF资源

  WPF资源系统是一种保管一系列有用的对象的简单方法,从而可以更容易的重用这些对象。尽管可以在代码中创建和操作资源,当通常在XAML标记中定义资源。一旦定义了一个资源,就可以在窗口中标记的所有其他部分使用该资源。这种技术简化了标记,保存了重复的编码,并且可以在*位置存储用户界面的细节,以便方便的修改它们。

10.1 资源基础

  WPF允许在代码中以及在标记中的各个位置定义资源。资源具有许多重要的优点:

  • 高效。通过资源可以定义一个对象,并在标记中的多个地方使用。这会使代码变得更加精简,并且更加高效
  • 可维护性。通过资源可以使用低级的格式化细节,并将他们移到方便对其进行修改的*位置。在XAML中创建资源,相当于在代码中创建常量
  • 适应性。一旦特定的信息和应用程序的其他部分相分离,并且放置到资源部分中,就可以动态地修改它。

10.1.1 资源集合

  每个元素都有一个Resources属性,该属性存储了一个资源字典集合(它是Resource Dictionary类的实例)。资源集合可以包含任意类型的对象,并根据字符串编写索引。尽管每个元素都提供了Resources属性(该属性是作为FrameworkElement类的一部分定义的),但是通常在窗口级别上定义资源。这是因为每个元素都可以访问他自己的资源集合中的资源,也可以访问所有父元素的资源集合中的资源。如下示例中:

<Window x:Class="Resources.WindowResource"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Resources" Height="300" Width="300"
    >
  <Window.Resources>
    <ImageBrush x:Key="TileBrush" TileMode="Tile"
                ViewportUnits="Absolute" Viewport="0 0 32 32"
                ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
  </Window.Resources>
    <StackPanel Margin="5">
      <Button Background="{StaticResource TileBrush}" Padding="5"
              FontWeight="Bold" FontSize="14" Margin="5"
              >A Tiled Button</Button>

      <Button Padding="5" Margin="5"
              FontWeight="Bold" FontSize="14">A Normal Button</Button>
      <Button Background="{StaticResource TileBrush}" Padding="5" Margin="5"
              FontWeight="Bold" FontSize="14"
              >Another Tiled Button</Button>
      
    </StackPanel>
</Window>
View Code

把一个画刷定义为资源

  <Window.Resources>
    <ImageBrush x:Key="TileBrush" TileMode="Tile"
                ViewportUnits="Absolute" Viewport="0 0 32 32"
                ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
  </Window.Resources>

,重要的是第一个特性,即Key特性(以名称空间前缀x:开头,这会将该画刷放置到XAML名称空间中,而不是WPF名称空间中)。该特性指定了在Window.Resources.Collection集合中编写画刷索引的名称。为了使用XAML标记中的资源,需要一种引用资源的方法。这是通过标记扩展完成的。有两种标记扩展的方法,一种是静态资源,另一种是动态资源。静态资源在第一次创建窗口时一次性的设置完毕。而对于动态资源,如果发生了变化,则会重新应用资源。下面分别是静态和动态资源的引用:

//静态资源
      <Button Background="{StaticResource TileBrush}" Padding="5"
              FontWeight="Bold" FontSize="14" Margin="5"
              >A Tiled Button</Button>
//动态资源
      <Button Background="{DynamicResource TileBrush}" Padding="5"
              FontWeight="Bold" FontSize="14" Margin="5"
              >A Tiled Button</Button>

10.1.2 资源的层次

  每个元素都有自己的资源集合,并且为了找到期望的资源,WPF在元素树中进行递归搜索。当使用静态资源时,必须在应用资源之前在标记中定义资源。如果希望在按钮元素中放置资源,需要稍微重新安排标记,在设置背景之前定义资源,如下:

    <Button Padding="5" Margin="5"
            FontWeight="Bold" FontSize="14">
      <Button.Resources>
        <ImageBrush x:Key="TileBrush" TileMode="Tile"
                    ViewportUnits="Absolute" Viewport="0 0 10 10"
                    ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
      </Button.Resources>
      <Button.Background><StaticResource ResourceKey="TileBrush" /></Button.Background>
      <Button.Content>Another Tiled Button</Button.Content>
    </Button>
View Code

因为资源被放在一个嵌套的元素中,为了指向正确的资源,资源键是使用ResourceKey属性具*定的。只要在不同一集合中多次使用相同的资源名,就可以重用相同的资源名称。

10.1.3 静态资源和动态资源

  静态资源和动态资源的区别在于静态资源只从资源集合中获取对象一次。根据对象的类型,对象的任何变化都可能被立即注意到。然而,动态资源在每次需要对象时都会重新从资源集合中查找对象。这意味着可以在同一键下放置一个全新的对象,并且动态资源会应用该变化。作为一般规则,只有在下列情况下才需要使用动态属性:

  • 资源具有依赖与系统设置的属性(如当前Windows操作系统的颜色或字体)
  • 计划通过编程替换资源对象

但是,不应过度使用动态资源。主要问题是对资源的修改未必会触发对用户界面的更新。在许多情况下,需要在控件中显示动态内容,并且控件随着内容的改变需要调整自身,对于这种情况,使用数据绑定更合理。

10.1.4 非共享资源

  通常,当在多个地方使用某种资源时,使用的是同一个对象实例。这种行为——被称为共享。然而,也可能希望告诉解析器在每次使用时创建单独的对象实例。为了关闭共享行为,需要使用Shared特性,如下所示:

<ImageBrush x:key="TileBrush" x:Shared="False">...</ImageBrush>

10.1.5 通过代码访问资源

  我们一般在标记中定义和使用资源。如果需要,我们也可以在代码中使用资源集合。可以从资源中提取资源。为此,需要使用正确元素的资源集合。可以使用FrameworkElement.FindResource()方法以相同的方式查找资源。可以使用TryFindResource()方法替代FindResource()方法。如果找不到资源,该方法会返回一个空引用,而不是抛出一个异常。此外,还可以通过编写代码添加资源。选择希望放置资源的元素,并使用资源集合的Add()方法。

10.1.6 应用程序资源

  窗口不是查找资源的最后一站。如果在控件中或其他容器中找不到指定的资源,WPF会继续检查为应用程序定义的资源集合。在Visual Studio中,这些资源是在App.xaml文件的标记中定义的资源,如下所示:

<Window x:Class="Resources.WindowResource"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Resources" Height="300" Width="300"
    >
  <Application.Resources>
    <ImageBrush x:Key="TileBrush" TileMode="Tile"
                ViewportUnits="Absolute" Viewport="0 0 32 32"
                ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
  </Application.Resources>
</Application>

应用程序资源位在整个应用程序中重用对象提供给了一种很好的方法。当某个查找资源时,应用程序资源仍然不是最后一站。如果在应用程序资源中没有找到所需要的资源,元素还会继续查找系统资源。

10.1.7 系统资源

  动态资源主要用于辅助应用程序对系统环境设置的变化做出响应。但这会导致一个问题,如何检索系统环境设置并在代码中使用它们?为此需要使用三个类,分别是SystemColors、SystemFonts和SystemParameters,所有这些类都位于System.Windows名称空间中。SystemColors类用于访问颜色设置;SystemFonts类用于访问字体设置;而SystemParameters类包装了大量的设置列表,这些设置列表描述了各种屏幕元素的标准尺寸、键盘和鼠标设置、屏幕尺寸以及各种图形效果是否已经打开。SystemColors、SystemFonts和SystemParameters类通过静态属性提供了他们所有的细节。例如,SystemColors.WindowTextColor属性提供一个Color结构,可以方便的使用该结构。下面的示例使用该属性创建一个画刷,并填充元素的前景:

label.Foreground=new SolidBrush(SystemColors.WindowTextColor);

或者为了提高效率,可以使用现成的画刷属性:

label.Foreground=SystemColors.WindowTextBrush;

在WPF中,可以使用静态标记扩展访问静态属性。例如,下面的标记演示了如何使用XAML为同一标签设置前景色:

<Label Foreground="{x:Static SystemColors.WindowTextBrush}">Ordinary text</Label>

上面的示例没有使用资源,当解析窗口并且创建标签时,会根据当前窗口文本颜色的“快照”创建画刷。如果在应用程序运行时改变了Windows颜色,Label控件不会更新它自身。为此,不能将Foreground属性直接设置为画刷对象。而是需要将它设置为包装了该系统资源的DynamicResource对象。幸运的是,所有SystemXxx类都提供了用于返回ResourceKey对象引用的补充属性集,使用这些引用可以从系统资源集合中提取资源。这些属性与直接返回对象的破同属性同名,后面加上单词Key。下面是标记显示了如何使用来自SystemXxx类的资源:

<Label Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}">Ordinary text</Label>

上面示例中首先定义了一个动态资源,但该动态资源不是从应用程序的资源集合中提取资源。而是使用了一个由SystemColors.WindowTextBrushKey属性定义的键。因为该属性是静态属性,所以还需要使用一个静态标记扩展,从而让解析器理解正在尝试进行什么操作。

10.2 资源字典

  如果希望在多个项目之间共享资源,可以创建一个资源字典。资源字典是一个简单的XAML文档,该文档除了存储希望使用的资源之外,不做其他任何事情。

10.2.1 创建资源字典

  下面是一个资源字典示例,它包含一个资源:

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

 
    <ImageBrush
      x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomResources}, ResourceId=SadTileBrush}"
      TileMode="Tile"
      ViewportUnits="Absolute" Viewport="0 0 32 32"
      ImageSource="ResourceLibrary;component/sadface.jpg" Opacity="0.3">
    </ImageBrush>

</ResourceDictionary>
View Code

当为应用程序添加资源字典时,需要确保生成操作属性设置为Page。这样可以保证为了得到最佳性能将资源字典便以为BAML。不过,将资源字典的生成操作属性设置为Resource也是非常完美的,这样它会被嵌入到程序集中,但是不会被编译。

10.2.2 使用资源字典

  为了使用资源字典,需要将其合并到应用程序中某些位置的资源集合中。例如,可以在特定窗口中执行此操作,但是通常将其合并到应用程序的资源集合中,如下

<Application x:Class="Resources.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    StartupUri="Menu.xaml"
    >
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="AppBrushes.xaml"/>
                <ResourceDictionary Source="AppBrushes.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
View Code

上面的标记通过明确的创建一个ResourceDictionary对象进行工作。资源集合总是ResourceDictionary对象,但是这只是需要明确指定细节从而可以设置ResourceDictionary.MergedDictionaries属性的一种情况。如果不执行这一步骤,MergedDictionaries属性将为空。MergedDictionaries集合是一个ResourceDictionary对相机和,可以使用该集合提供自己希望使用的资源集合。在这个示例中,有两个资源集合:一个是在AppBrushes.xaml资源字典中定义的,列一个是在WizardBrushes.xaml中定义的。如果希望添加自己的资源,并合并到资源字典中,只需要在MergedDictionaries部分之前或之后放置资源就可以了。使用资源字典的一个原因是定义一个或多个可以重用的应用程序“皮肤”,可将“皮肤”应用到控件上。另一个原因是存储需要被本地化的内容。

10.2.3 在程序集之间共享资源

  如果希望在多个应用程序之间共享资源字典,可以复制并分发包含资源字典的XAML文件。但是这样不能对版本进行任何控制。更有条理的方法是将资源字典编译到一个单独的类库程序集中,并分发组件。当共享包含一个或多个资源字典的编译过的程序集时,还需要面对另一种挑战,需要一种方法提取所希望的资源并在应用程序中使用资源。我们可以使用两种方法。最直观的解决方法是使用代码创建合适的ResourceDictionary对象。例如,如果在一个类库程序集中有一个名为ReusableDictionary.xaml的资源字典,可以使用下面的代码手动创建该资源字典:

ResourceDictionary resourceDictionary = new ResourceDictionary();
ResourceDictionary.Source = new Uri("ResourceLibrary;component/themes/ReusableDictionary.xaml", UriKind.Relative);

上面的代码片段使用了之前学过的pack URI语法。他构造了一个相对的URI,该URI指向另一个程序集中的名为ReusableDictionary.xaml的编译过的XAML资源。一旦创建了ResourceDictionary对象,就可以从集合中手动检索所需要的资源了:cmd.Bakcground=(Brush)resourceDictionary["TileBrush"];这样,不需要手动指定资源。当加载一个新的资源字典时,窗口中的所有DynamicResource引用都会被自动重新评估。如果不想编写任何代码,可以使用ComponentResourceKey标记扩展,该标记扩展是专门针对这种情况而设计的。使用ComponentResourceKey为资源创建键名。通过这一步骤,告知WPF你准备在程序集之间共享资源。但是,你首先要保证为资源字典提供了正确的名称,资源字典必须真实存在。下一步是为存储在资源共享的程序集中希望共享的资源创建键名。当使用ComponentResourceKey时,需要提供两部分信息:类库程序集中类的引用和描述性的资源ID。类引用是WPF允许和其他程序集共享资源的关键部分。当使用资源时,需要提供相同的类引用和资源ID。这个类不需要包含代码。定义该类型的程序集就是Component ResourceKey将要从中查找资源的程序集。现在可以使用这个类和资源ID创建键名了:

x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomResources}, ResourceId=SadTileBrush}"

下面是资源字典generic.xaml文件的完整标记,它包含了一个单独的资源

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

 
    <ImageBrush
      x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:CustomResources}, ResourceId=SadTileBrush}"
      TileMode="Tile"
      ViewportUnits="Absolute" Viewport="0 0 32 32"
      ImageSource="ResourceLibrary;component/sadface.jpg" Opacity="0.3">
    </ImageBrush>

</ResourceDictionary>
View Code

现在已经创建了资源字典,可以在另一个应用程序中使用它了。首先,确保已经为类库程序集定义了前缀,如下所示

<Window x:Class="Resources.ResourceFromLibrary"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:res="clr-namespace:ResourceLibrary;assembly=ResourceLibrary"
    >

然后可以使用包含ComponentResourceKey的DynamicResource。在使用资源字典的应用程序中使用的ComponentResourceKey,就是在类库中使用的ComponentResourceKey。在此,提供了对同一个类的引用和相同的资源ID。唯一的区别是可能使用不同的XML名称空间前缀。该示例使用res前缀而不是local前缀,以签掉CustomResources类是在另一个程序集中定义的这样一个事实:

    <Button Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type res:CustomResources}, ResourceId=SadTileBrush}}"
            Padding="5" Margin="5"
            FontWeight="Bold" FontSize="14">
      A Resource From ResourceLibrary</Button>

现在该示例完成了。但还可以采取一个附加步骤,以使资源更容易使用。可以定义一个静态属性,让它返回需要使用的正确的ComponentResourceKey。通常,将在组件的类中定义该属性。如下所示:

    public class CustomResources
    {
        public static ComponentResourceKey SadTileBrush
        {
            get
            {
                return new ComponentResourceKey(
                    typeof(CustomResources), "SadTileBrush");
            }
        }
    }

现在可以使用Static标记扩展访问该属性并应用资源了,而不需要在标记中使用很长的ComponentResourceKey。

    <Button Background="{DynamicResource {x:Static res:CustomResources.SadTileBrushKey}}"
            Padding="5" Margin="5"
            FontWeight="Bold" FontSize="14">
      A Resource From ResourceLibrary</Button>

本次学习了WPF资源系统使得在应用不同部分可以重用相同对象的原理。介绍了如何在代码和标记中生命资源,如何提取系统资源,以及如何使用类库程序集在应用程序之间共享资源。对象资源最大的实际用途之一是存储样式,可以应用到多个元素的属性设置的集合。

相关推荐