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

  资源具有许多重要的优点,如下所述:

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

一、资源集合

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

  尽管每个元素都提供了Resources属性(该属性作为FrameworkElement类的一部分定义),但通常在窗口级别定义资源。这是因为每个元素都可以访问各自资源集合中的资源,也可以访问所有父元素的资源集合中的资源。

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

  在该例中,显然希望顶部和底部的两个按钮具有相同的样式。不过,以后可能希望改变图像画刷的特征。因此,在窗口的资源中定义图像画刷并在需要时重用该画刷是合理的。

  下面的标记显示了如何定义画刷:

<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标记中的资源,需要一种引用资源的方法。这是通过标记扩展完成的。实际上有两个标记扩展可提供使用:一个用于动态资源,另一个用于静态资源。静态资源在首次创建窗口时一次性地设置完毕。而对于动态资源,如果发生了改变,就会重新应用资源。在该例中,图像画刷永远不会改变,所以使用静态资源是合适的。

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

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

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

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

二、资源的层次

  每个元素都有自己的资源集合,为了找到期望的资源,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>

  效果图如下所示:

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

三、静态资源和动态资源

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

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

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

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

  这是因为Brush类继承自Freezable类。Freezable类有一个基本的变化跟踪特性(如果不需要改变,能被“冻结”为只读状态)。这意味着,无论何时在WPF中改变画刷,所有使用该画刷的控件都会自动更新。控件是否是通过资源获取其画刷无关紧要。

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

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

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

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

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

  通常不需要使用动态资源,使用静态资源应用程序也能够很完美地工作。创建依赖于Windows设置(例如系统颜色)的资源明显属于例外情况,对于这种情况,如果希望能够响应当前颜色方案的任何改变,就需要使用动态资源(否则,如果使用静态资源,将仍使用原来的颜色方案,直到用户重新启动应用程序为止)。在稍后介绍系统资源时,将讨论动态资源工作原理的更多相关内容。

  作为一般规则,只有在下列情况下才需要使用动态属性。

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

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

四、非共享资源

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

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

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

  很少有理由需要使用非共享的资源。如果希望以后分别修改资源实例,可考虑使用非共享资源。例如,可创建包含几个使用同一画刷按钮的窗口,并关闭共享行为,从而可以分别改变每个画刷。由于效率底下,这种方式不常见。在这个示例中,开始时告诉所有按钮使用同一个画刷,当需要时在创建并应用新的画刷,这样可能更好。这样,只有当确定需要时才承担额外的画刷对象开销。

  使用非共享资源的另一个原因是,可能希望以一种原本不允许的方式重用某个对象。例如,使用该技术,可将某个元素(如一幅图像或一个按钮)定义为资源,然后再窗口的多个不同位置显示该元素。

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

五、通过代码访问资源

  通常在标记中定义和使用资源。如有必要,也可在代码中使用资源集合。

  正如已经看到的,可通过名称从资源集合中提取资源。为此,需要使用正确元素的资源集合。如前所述,对于标记没有这一限制。控件(如按钮)能够检索资源,而不需要知道定义资源的确位置。当尝试为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()方法。然而,通常在标记中定义资源。

六、应用程序资源

  窗口不是查找资源的最后一站。如果在控件或其容器中(直到包含窗口或页面)找不到指定的资源,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>

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

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

七、系统资源

  动态资源主要用于辅助应用程序对系统环境设置的变化做出响应。但这会导致一个问题——开始时如何检索系统环境设置并在代码中使用它们。

  为此需要使用三个类,分别是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控件能够无缝地更新自身。

【WPF学习】第三十四章 资源基础的更多相关文章

  1. 【WPF学习】第二十四章 基于范围的控件

    WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件——ScrollBar.ProgressBar以及Slider——都继承自RangeBase类(该类又继承自Co ...

  2. 【WPF学习】第十四章 事件路由

    由上一章可知,WPF中的许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容.例如,可构建包含图形的按钮,创建混合了文本和图片内容的标签,或者为了实现滚动或折叠的显示效果而在特定容器中放置 ...

  3. “全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. “全栈2019”Java第三十四章:可变参数列表

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  6. 风炫安全web安全学习第三十四节课 文件包含漏洞防御

    风炫安全web安全学习第三十四节课 文件包含漏洞防御 文件包含防御 在功能设计上不要把文件包含的对应文件放到前台去操作 过滤各种../,https://, http:// 配置php.ini文件 al ...

  7. C++ Primer Plus学习:第十四章

    第十四章 C++中的代码重用 包含对象成员的类 将类的对象作为新类的成员.称为has-a关系.使用公有继承的时候,类可以继承接口,可能还有实现(纯虚函数不提供实现,只提供接口).使用包含时,可以获得实 ...

  8. 【WPF学习】第二十六章 Application类——应用程序的生命周期

    在WPF中,应用程序会经历简单的生命周期.在应用程序启动后,将立即创建应用程序对象,在应用程序运行时触发各种应用程序事件,你可以选择监视其中的某些事件.最后,当释放应用程序对象时,应用程序将结束. 一 ...

  9. 【WPF学习】第二十八章 程序集资源

    WPF应用程序中的程序集资源与其他.NET应用程序中的程序集资源在本质上是相同的.基本概念是为项目添加文件,从而Visual studio可将其嵌入到编译过的应用程序的EXE或DLL文件中.WPF程序 ...

随机推荐

  1. js对当前时间进行处理

    //1.JS获取两个日期之间相差的天数 function getDaysBetween(dateString1, dateString2) { var startDate = Date.parse(d ...

  2. C# 初识接口 Interface

    什么是接口? 接口(interface)用来定义一种程序的协定.实现接口的类或者结构要与接口的定义严格一致.有了这个协定,就可以抛开编程语言的限制(理论上).C#接口可以从多个基接口继承,而类或结构可 ...

  3. $CF809C\ Find\ a\ car$ 数位$dp$

    正解:数位$dp$ 解题报告: 传送门! 然后因为没有翻译所以先放个翻译$QAQ$ 有一个无穷大的矩阵,第$i$行第$j$列的数是$(i-1)\ xor\ (j-1)+1$,有$q$次询问,每次询问一 ...

  4. DecoratorPattern(装饰器模式)-----Java/.Net

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装

  5. Pandas 数据分析,高中体测练习

    分析体测成绩 需求: 体侧成绩转变成分数 开卷考试 excel完成可以 pandas读取excel代码中 完成 一个手输入 进一步,画图,分布,体重正常,肥胖,偏瘦比例,绘制饼图 男生跑步1000成绩 ...

  6. JVM之GC(三)

    前面介绍了GC和几种主流的GC算法,这节准备说一下垃圾收集器.垃圾收集器可以分为三类,Yong GC, Old GC, Mixed GC Yong GC 1.Serial 单线程处理,采用复制算法,通 ...

  7. Linux Cgroup浅析

    cgroup从2.6.4引入linux内核主线,目前默认已启用该特性.在cgroup出现之前,只能对一个进程做资源限制,比如通过sched_setaffinity设置进程cpu亲和性,使用ulimit ...

  8. MySQL故障演习

    MySQL故障演习 接上次的 MySQL定时备份 该次实验主要是练习在MySQL数据库发生误删等意外情况下,利用全量备份文件和增量备份文件恢复数据. 1. 实验环境 -- 创建数据库 create d ...

  9. [Debug]IOS微信浏览器不支持form表单的target=_blank

    测试代码如下 <?php echo '<meta name="viewport" content="width=device-width,minimum-sc ...

  10. Flask DBUtils

    作用:创建连接池,解决多线程问题 1.安装模块 pip3 install -i https://pypi.douban.com/simple DBUtils 2.settings.py(配置文件) f ...