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. MVC 之集合类转化为DataTable

    private static DataTable ToDataTableTow(IList list) { DataTable result = new DataTable(); if (list.C ...

  2. 20191024-3 互评Alpha阶段作品

    此作业要求参见https://edu.cnblogs.com/campus/nenu/2019fall/homework/9860 本组对构建之法组评价的博客链接:https://www.cnblog ...

  3. 洛谷$P5330\ [SNOI2019]$数论 数论

    正解:数论 解题报告: 传送门$QwQ$ ,,,这题还蛮妙的$QwQ$(,,,其实所有数论题对我来说都挺妙的$kk$然后我真的好呆昂我理解了好久$QAQ$ 考虑先建$Q$个点,编号为$[0,Q)$,表 ...

  4. Java函数式接口与Lambda表达式

    什么是函数式接口? 函数式接口是一种特殊的接口,接口中只有一个抽象方法. 函数式接口与Lambda表达式有什么关系? 当需要一个函数式接口的对象时,可以提供一个lambda表达式. package l ...

  5. html页脚固定在底部的方法

    <style type="text/css"> html { height: 100%; } body { height: 100%; margin: 0; paddi ...

  6. Http请求头安全策略

    今天在网上浪了许久,只是为了找一个很简单的配置,却奈何怎么都找不到. 好不容易找到了,我觉得还是记录下来的好,或许省得许多人像我一样浪费时间. 1.X-Frame-Options 如果网站可以嵌入到I ...

  7. Django ORM调优实践

    一.分析请求慢响应的主要原因 将请求执行的任务按功能分为几块,用time.time()打印每个模块的执行时间,大部分情况下性能会主要消耗在某一个模块上,即80%的性能问题是出在20%的代码上 找到主要 ...

  8. 小小知识点(二十三)circularly symmetric complex zero-mean white Gaussian noise(循环对称复高斯噪声)

    数学定义 http://en.wikipedia.org/wiki/Complex_normal_distribution 通信中的定义 在通信里,复基带等效系统的噪声是复高斯噪声,其分布就是circ ...

  9. 12款好用的Visual Studio插件,最后一款良心推荐

    目录 01 CodeMaid 02 Markdown Editor 03 ReSharper 04 GitHub Extension for Visual Studio 05 ZenCoding 06 ...

  10. 解决vs2017创建.net core失败以及不能登录问题

    创建.net core web 提示值不能为空,开始以为是vs安装错误,重新安装了一遍也是这样,之前都好好的,然后登录也不行了(提示我们无法刷新此账号的凭据),百度了下,是ie不能上网的问题.解决了登 ...