【WPF学习】第三十四章 资源基础

  WPF容许在代码中以及在标记中的各个位置定义资源(和特定的控件、窗口一块儿定义,或在整个应用程序中定义)。编程

  资源具备许多重要的优势,以下所述:app

  •   高效。能够经过资源定义对象,并在标记中的多个地方使用。这会精简代码,使其更加高效。
  •   可维护性。可经过资源使用低级的格式化细节(如字号),并将它们移到便于对其进行修改的中央位置。在XAML中建立资源相对于在代码中建立常量。
  •   适应性。一旦特定信息与应用程序的其余部分分离开来,并放置到资源部分中,就能够动态地吸怪这些信息。例如,可能但愿根据用户的我的喜爱或当语言修改资源的细节。

1、资源集合字体

  每一个元素都有Resources属性,该属性存储了一个资源字典集合(他是ResourceDictionary类的实例)。资源集合可包含任意类型的对象,并根据字符串编写索引。this

  尽管每一个元素都提供了Resources属性(该属性做为FrameworkElement类的一部分定义),但一般在窗口级别定义资源。这是由于每一个元素均可以访问各自资源集合中的资源,也能够访问全部父元素的资源集合中的资源。spa

  例如,分析下图中显示的包含三个按钮的窗口。其中的两个按钮使用了相同的画刷——绘制笑脸图像的平铺式的图像画刷。操作系统

 

   在该例中,显然但愿顶部和底部的两个按钮具备相同的样式。不过,之后可能但愿改变图像画刷的特征。所以,在窗口的资源中定义图像画刷并在须要时重用该画刷是合理的。3d

  下面的标记显示了如何定义画刷: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>

  为使用XAML标记中的资源,须要一种引用资源的方法。这是经过标记扩展完成的。实际上有两个标记扩展可提供使用:一个用于动态资源,另外一个用于静态资源。静态资源在首次建立窗口时一次性地设置完毕。而对于动态资源,若是发生了改变,就会从新应用资源。在该例中,图像画刷永远不会改变,因此使用静态资源是合适的。orm

  下面是一个使用该资源的按钮:xml

<Button Background="{StaticResource TileBrush}" Padding="5"
              FontWeight="Bold" FontSize="14" Margin="5"
              >A Tiled Button</Button>

  上面的代码检索资源并将资源指定给Button.Background属性。可以使用动态资源执行相同的操做(但开销稍大些):

<Button background="{DynamicResource TileBrush}"></Button>

2、资源的层次

  每一个元素都有本身的资源集合,为了找到指望的资源,WPF在元素树中进行递归搜索。在当前示例中,可将图像画刷从窗口的资源集合移到包含这三个按钮的StackPanel面板的资源集合中,而没必要改变应用程序的工做方式。也可将图像画刷放到Button.Resources集合中,不过,须要定义画刷两次——为每一个按钮分别定义一次。

  须要考虑的另外一个问题是,当使用静态资源时,必须老是在引用资源以前的标记中定义资源。这意味着尽管从标记角度看,将Window.Resources部分放在窗口的主要内容(包含全部按钮的StackPanel面板)以后是彻底合法的,但这会破坏当前示例。当XAML解析器遇到它不知道的资源的静态引用时,会抛出异常(但是使用动态资源避免这一问题,但不必增长额外的开销)。

  所以,若是但愿在按钮元素中放置资源,须要稍微从新排列标记,从而在设置背景以前定义资源。下面的实现该操做的一种方法:

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

  这个示例中的静态资源标记扩展语法稍有不一样,由于资源被放在嵌套元素中(而不是特性中),为指向正确的资源,使用ResourceKey属性指定资源键。

  有趣的是,只要不在同一集合中屡次使用相同的资源名,就能够重用资源名称。这意味着可以使用以下所示的标记建立窗口,该标记在两个地方建立图像画刷。

<Window x:Class="Resources.TwoResources"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TwoResources" 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 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>

    </StackPanel>

</Window>

  效果图以下所示:

 

   在上面的代码中,按钮使用找到的第一个资源。由于是从本身的资源集合开始查找,因此第二个按钮使用按钮内部定义的资源,而第一个按钮从包含窗口获取画刷。

3、静态资源和动态资源

  由于上面的示例使用了静态资源(在该例中是图形画刷),因此你可能认为对于资源的任何改变都不会有什么反应。然而,事实并不是如此。

  例如,设想在应用了资源而且显示了窗口以后,执行下面的代码:

ImageBrush brush = (ImageBrush)this.Resources["TileBrush"];
brush.Viewport = new Rect(0, 0, 5, 5);    

  上面的代码从Window.Resources集合中检索画刷,并对它进行操做(从技术角度看,代码改变了每一个平铺图像的尺寸,缩小了笑脸图像并压缩图像模式使其更加紧凑)。当运行此代码时,可能不但愿用户界面有任何反应——毕竟,它是静态资源。但这一变化会传播给两个按钮。实际上,会使用新设置的Viewport属性进行更新,而无论是经过静态资源仍是动态资源使用画刷。

  这是由于Brush类继承自Freezable类。Freezable类有一个基本的变化跟踪特性(若是不须要改变,能被“冻结”为只读状态)。这意味着,不管什么时候在WPF中改变画刷,全部使用该画刷的控件都会自动更新。控件是不是经过资源获取其画刷可有可无。

  如今,你可能想弄清楚静态资源和动态资源之间到底有什么区别。区别在于静态资源只从资源集合中获取对象一次。根据对象的类型(以及使用对象的方式),对象的任何便哈均可能被当即注意到。然而,动态资源在每次须要对象时都会从新从资源集合中查找对象。这意味着可在同一键下放置一个全新对象,并且动态资源会应用该变化。

  下面经过一个示例演示它们之间的区别,分析下面的代码,这段代码用全新的(而且有些乏味的)存蓝色画刷替换了当前的图像画刷:

this.Resources["TileBrush"] = new SolidColorBrush(Colors.LightBlue);

  动态资源会应用该变化,而静态资源不知道它的画刷已在Resources集合中被其余内容替换了,它仍然继续使用原来的ImageBrush。

  下图显示了该示例,该窗口包含动态资源(顶部按钮)和静态资源(底部资源)。

 

   一般不须要使用动态资源,使用静态资源应用程序也可以很完美地工做。建立依赖于Windows设置(例如系统颜色)的资源明显属于例外状况,对于这种状况,若是但愿可以响应当前颜色方案的任何改变,就须要使用动态资源(不然,若是使用静态资源,将仍使用原来的颜色方案,直到用户从新启动应用程序为止)。在稍后介绍系统资源时,将讨论动态资源工做原理的更多相关内容。

  做为通常规则,只有在下列状况下才须要使用动态属性。

  •   资源具备依赖于系统设置的属性(如当前Windows操做系统的颜色或字体)。
  •   准备经过编程方式替换资源对象(例如,实现几类动态皮肤特性)

  然而,不该该过分使用动态资源。主要问题是对资源的修改未必会触发对用户界面的更新(在画刷示例中,由于构造画刷对象的方式——画刷具备内置的通知支持,确实更新了用户界面)。许多状况下,须要在控件中显示动态内容,并且控件须要随着内容的改变调整自身。对于这种状况,使用数据绑定更合理。

4、非共享资源

  一般,在多个地方使用某种资源时,使用的是同一个对象实例。这种行为——称为共享——一般这也正是所但愿的。然而,也可能但愿告诉解析器在每次使用时建立单独的对象实例。

  为关闭共享行为,须要使用Shared特性,以下所示:

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

  不多有理由须要使用非共享的资源。若是但愿之后分别修改资源实例,可考虑使用非共享资源。例如,可建立包含几个使用同一画刷按钮的窗口,并关闭共享行为,从而能够分别改变每一个画刷。因为效率底下,这种方式不常见。在这个示例中,开始时告诉全部按钮使用同一个画刷,当须要时在建立并应用新的画刷,这样可能更好。这样,只有当肯定须要时才承担额外的画刷对象开销。

  使用非共享资源的另外一个缘由是,可能但愿以一种本来不容许的方式重用某个对象。例如,使用该技术,可将某个元素(如一幅图像或一个按钮)定义为资源,而后再窗口的多个不一样位置显示该元素。

  一样,一般这不是最佳方法。例如,若是但愿重用Image元素,再合理的作法是存储相关信息(例如,用于指定图像源的BitmapImage对象)并在多个Image元素之间共享。若是只是但愿标准化控件,让他们共享相同的属性,最好使用样式。经过样式可为任意元素建立相同或几乎相同的副本,当属性值尚未被应用时,能够重用他们并且能够关联不一样的事件处理程序。若是简单地使用非共享资源克隆元素,就会丢失这两个特性。

5、经过代码访问资源

  一般在标记中定义和使用资源。若有必要,也可在代码中使用资源集合。

  正如已经看到的,可经过名称从资源集合中提取资源。为此,须要使用正确元素的资源集合。如前所述,对于标记没有这一限制。控件(如按钮)可以检索资源,而不须要知道定义资源的确位置。当尝试为Background属性指定画刷时,WPF会在按钮的资源集合中检索名为TileBrush的资源,而后检查包含StackPanel的资源集合,接下来检查包含窗口(这个过程实际上海会继续检查应用程序资源和系统资源)。

  可以使用FrameworkElement.FindResource()方法以相同的方式查找资源。下面是一个示例,当引起Click事件时,会查找按钮资源(或它的一个更高级的容器):

private void cmdChange_Click(object sender,RoutedEventArgs e)
{
    Button cmd=(Button)sender;
    ImageBrush brush=(ImageBrush)sender.FindResource("TileBrush");
    ...
}

  可以使用TryFindResource()方法代替FindResource()方法,若是找不到资源,该方法会返回null引用,而不是抛出异常。

  此外,还可经过编写代码添加资源。选择但愿放置资源的元素,并使用资源集合的Add()方法。然而,一般在标记中定义资源。

6、应用程序资源

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

<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>
        <ImageBrush x:Key="TileBrush" x:Name="DynamicBrush" TileMode="Tile"
                ViewportUnits="Absolute" Viewport="0 0 32 32"
                ImageSource="happyface.jpg" Opacity="0.3"></ImageBrush>
    </Application.Resources>
</Application>

  应用程序资源为在整个应用程序中重用对象提供了一种极佳的方法。在这个示例中,若是计划在多个窗口中使用图像画刷,这是一种很好的选择。

  当某个元素查找资源时,应用程序资源仍然不是最后一站。若是没有在应用程序资源中找到所需的资源,元素还会继续查找系统资源。

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。例如,SystemColors.WindowTextBrush的资源键时SystemColors.WindowTextBrushKey。

  下面的标记显示了如何使用来自SystemXxx类的资源:

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

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

  现已完成修改,当系统设置变化时,Label控件可以无缝地更新自身。

相关文章
相关标签/搜索