【WPF学习】第三十四章 资源基础
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学习】第三十四章 资源基础的更多相关文章
- 【WPF学习】第二十四章 基于范围的控件
WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件——ScrollBar.ProgressBar以及Slider——都继承自RangeBase类(该类又继承自Co ...
- 【WPF学习】第十四章 事件路由
由上一章可知,WPF中的许多控件都是内容控件,而内容控件可包含任何类型以及大量的嵌套内容.例如,可构建包含图形的按钮,创建混合了文本和图片内容的标签,或者为了实现滚动或折叠的显示效果而在特定容器中放置 ...
- “全栈2019”Java多线程第三十四章:超时自动唤醒被等待的线程
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java第三十四章:可变参数列表
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Gradle 1.12用户指南翻译——第三十四章. JaCoCo 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- 风炫安全web安全学习第三十四节课 文件包含漏洞防御
风炫安全web安全学习第三十四节课 文件包含漏洞防御 文件包含防御 在功能设计上不要把文件包含的对应文件放到前台去操作 过滤各种../,https://, http:// 配置php.ini文件 al ...
- C++ Primer Plus学习:第十四章
第十四章 C++中的代码重用 包含对象成员的类 将类的对象作为新类的成员.称为has-a关系.使用公有继承的时候,类可以继承接口,可能还有实现(纯虚函数不提供实现,只提供接口).使用包含时,可以获得实 ...
- 【WPF学习】第二十六章 Application类——应用程序的生命周期
在WPF中,应用程序会经历简单的生命周期.在应用程序启动后,将立即创建应用程序对象,在应用程序运行时触发各种应用程序事件,你可以选择监视其中的某些事件.最后,当释放应用程序对象时,应用程序将结束. 一 ...
- 【WPF学习】第二十八章 程序集资源
WPF应用程序中的程序集资源与其他.NET应用程序中的程序集资源在本质上是相同的.基本概念是为项目添加文件,从而Visual studio可将其嵌入到编译过的应用程序的EXE或DLL文件中.WPF程序 ...
随机推荐
- 软RAID和硬RAID的区别
要实现RAID可以分为硬件实现和软件实现两种.所谓硬RAID就是指通过硬件实现,同理软件实现就作为软RAID. 硬RAID 就是用专门的RAID控制器将硬盘和电脑连接起来,RAID控制器负责将所有 ...
- $Codeforces\ 522D\ Closest\ Equals$ 线段树
正解:线段树 解题报告: 传送门$QwQ$ 题目大意是说给定一个数列,然后有若干次询问,每次询问一个区间内相同数字之间距离最近是多少$QwQ$.如果不存在相同数字输出-1就成$QwQ$ 考虑先预处理出 ...
- map类型转为实体类
BareBaseRequest fromJson = JSON.parseObject(JSON.toJSONString(map), BareBaseRequest.class);
- 如何解决Selenium句柄、多窗口问题
有时我们在打开浏览器浏览网页时,当点击网页上某些链接时,它不是直接在当前页面上跳转,而是重新打开一个新标签页面,对于这种情况,想在新页面上操作,就得先切换窗口了.获取窗口的唯一标识用句柄表示,所以只需 ...
- Eclipse中安装Jetty服务器
1. 在eclipse中安装jetty适配器 方法一: (1) 打开 Windows -> Preference -> Server -> Runtime Environment , ...
- await Task.Yield()和await Task.CompletedTask有什么不同
有时候我们在代码中要执行一些非常耗时的操作,我们不希望这些操作阻塞调用线程(主线程)的执行,因为调用线程(主线程)可能还有更重要的工作要做,我们希望将这些非常耗时的操作由另外一个线程去执行,这个时候就 ...
- 机器学习-特征工程-Missing value和Category encoding
好了,大家现在进入到机器学习中的一块核心部分了,那就是特征工程,洋文叫做Feature Engineering.实际在机器学习的应用中,真正用于算法的结构分析和部署的工作只占很少的一部分,相反,用于特 ...
- cogs 247. 售票系统 线段树
247. 售票系统 ★★☆ 输入文件:railway.in 输出文件:railway.out 简单对比时间限制:1 s 内存限制:128 MB [问题描述] 某次列车途经C个城市,城市 ...
- 构造分组背包(CF)
Ivan is a student at Berland State University (BSU). There are n days in Berland week, and each of t ...
- 公文流转系统v0.1
河北金力集团公文流转系统 1.项目需求: 河北金力集团是我省机械加工的龙头企业,主要从事矿山机械制造及各种机械零部件加工.企业有3个厂区,主厂区位于省高新技术开发区,3个分厂分别在保定.邢台和唐山.为 ...