WPF 将 StaticResource 和 ResourceDictionary 放在一起的魔幻行为
本文将记录一些在 WPF 里面,使用 StaticResource 将 ResourceDictionary 玩坏的做法。大家可以放心的是,这些玩法基本只有高级玩家或逗比开发者才会使用到
后加入的资源无法被 StaticResource 找到
在 App.xaml.cs 后台代码里面,手动加入资源字典,手动加入的资源字典包含的资源,无法被提前在 App.xaml 加入的资源里面的 StaticResource 找到
测试方式如下
定义两个资源字典,分别是 Dictionary1.xaml 和 Dictionary2.xaml 字典。在 Dictionary1 里定义资源,在 Dictionary2 使用 StaticResource 引用 Dictionary1 的资源。在 App.xaml 引用 Dictionary2.xaml 字典,在 App.xaml.cs 加入 Dictionary1.xaml 字典。此时运行将会发现 Dictionary2 里使用 StaticResource 的属性的值是 DependencyProperty.UnsetValue 值,表示找不到资源
细节的步骤如下
定义两个资源字典,分别是 Dictionary1.xaml 和 Dictionary2.xaml 字典
在 Dictionary1.xaml 里面定义资源,如以下代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="SolidColorBrush" Color="Black"></SolidColorBrush>
</ResourceDictionary>
在 Dictionary2 使用 StaticResource 引用 Dictionary1 的资源,如以下代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="RectangleStyle" TargetType="Rectangle">
<Setter Property="Fill" Value="{StaticResource SolidColorBrush}"></Setter>
</Style>
</ResourceDictionary>
在 App.xaml 里只引用 Dictionary2.xaml 字典,如以下代码
<Application x:Class="JayabawwiWhenenearfajay.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:JayabawwiWhenenearfajay"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- <ResourceDictionary Source="Dictionary1.xaml"></ResourceDictionary> -->
<ResourceDictionary Source="Dictionary2.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
在 App.xaml.cs 加入 Dictionary1.xaml 字典
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
var resourceDictionary = new ResourceDictionary()
{
Source = new Uri("/Dictionary1.xaml", UriKind.RelativeOrAbsolute)
};
Resources.MergedDictionaries.Add(resourceDictionary);
base.OnStartup(e);
}
}
接着在 MainWindow.xaml 使用 Dictionary2.xaml 定义的资源,如以下代码
<Window x:Class="JayabawwiWhenenearfajay.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:JayabawwiWhenenearfajay"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Rectangle Style="{StaticResource RectangleStyle}"></Rectangle>
</Grid>
</Window>
运行程序,将提示以下代码
System.InvalidOperationException:““{DependencyProperty.UnsetValue}”不是属性“Fill”的有效值。”
这就证明了定义在 Dictionary2.xaml 的 RectangleStyle 里的 Fill 属性找不到资源。也就是 Setter Property="Fill" Value="{StaticResource SolidColorBrush}" 这里的 StaticResource 无法找到定义在 Dictionary1.xaml 的资源
可以通过如下方式获取本文的源代码,先创建一个名为 JayabawwiWhenenearfajay 的空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin abef940468bef7af6bd9ceed8566229aafda5016
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin abef940468bef7af6bd9ceed8566229aafda5016
获取代码之后,进入 JayabawwiWhenenearfajay 文件夹
自定义 ResourceDictionary 资源可影响 StaticResource 寻找策略
以上的例子看起来还算正常,接下来来点魔幻的玩法
测试方式如下
在后台代码定义继承 ResourceDictionary 的类型,在此类型里面定义好和 Dictionary1.xaml 里的资源重名的资源,此时 Dictionary2.xaml 的 StaticResource 在运行将找对资源
也就是经过一番玩法,居然发现 StaticResource 又从 Dictionary1.xaml 里找对资源了
细节的步骤如下
在上一个例子的项目前提下,再新建一个名为 FooResourceDictionary 的类型,在构造函数添加上和 Dictionary1.xaml 里的资源重名的资源,代码如下
public class FooResourceDictionary : ResourceDictionary
{
public FooResourceDictionary()
{
Add("SolidColorBrush", this);
}
protected override void OnGettingValue(object key, ref object value, out bool canCache)
{
Debug.WriteLine(key);
base.OnGettingValue(key, ref value, out canCache);
}
}
以上代码在构造函数特别有趣的加入了 "SolidColorBrush" 资源,且设置资源的 Value 是 this 值。这就意味着如果 StaticResource 直接使用 FooResourceDictionary 里的 "SolidColorBrush" 资源,将拿到 FooResourceDictionary 类型的资源,完全无法转换为 Brush 类型,将会失败。然而实际上有趣的是最终 StaticResource 还是能找对资源
以上代码为了方便调试,也重写了 OnGettingValue 方法,这个方法是为了后文的另一个魔幻行为。不重写也不会影响当前的例子的行为
接着将这个自定义的 FooResourceDictionary 类型加入到 App.xaml 里面,必须放在 Dictionary2.xaml 之前,如以下代码
<Application x:Class="JayabawwiWhenenearfajay.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:JayabawwiWhenenearfajay"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- <ResourceDictionary Source="Dictionary1.xaml"></ResourceDictionary> -->
<local:FooResourceDictionary/>
<ResourceDictionary Source="Dictionary2.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
接着依然是在 App.xaml.cs 里面加入 Dictionary1.xaml 资源,代码和之前的完全相同,没有做任何改动。同样的 MainWindow.xaml 里面也没有做任何的改动
运行代码,可以看到这一次执行正常,静态资源寻找到了定义在 Dictionary1.xaml 的资源,不会受到在 FooResourceDictionary 定义的影响
可以通过如下方式获取本文的源代码,先创建一个名为 JayabawwiWhenenearfajay 的空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin ac01fffe3908bcf5b69b459e1d3a6e50aa207b9c
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin ac01fffe3908bcf5b69b459e1d3a6e50aa207b9c
获取代码之后,进入 JayabawwiWhenenearfajay 文件夹
通过以上的两个例子可以说明 StaticResource 的行为是在资源加载的过程中就会执行,执行时将会尝试从资源字典里寻找静态资源 Key 的定义,如果有找到 Key 的记录,则加入延迟初始化逻辑。延迟初始化逻辑还没有绑定到具体哪个资源字典,是在实际需要获取值的时候,才进行重新确定实际的资源。这也就是为什么 FooResourceDictionary 的 OnGettingValue 方法没有进入的原因,因为 StaticResource 实际获取值是从 Dictionary1.xaml 获取的,完全不在 FooResourceDictionary 里获取
如果没有找到 Key 的记录,那就直接给属性赋值为 DependencyProperty.UnsetValue 属性,结束寻找。即使后续加入的资源字典添加了对应的资源,也不会重新更新。这个行为符合微软的文档,试试看交换两个有依赖关系的资源字典加入 App.xaml 的顺序,可以看到顺序倒了之后将导致静态资源找不到。这个行为和资源字典加入顺序导致的找不到资源是相同的
在此例子里面是通过在 FooResourceDictionary 的构造里面,构建了 "SolidColorBrush" 资源,从而让 StaticResource 静态绑定资源引用设置给属性一个延迟初始化值,在实际的界面使用时,获取到 Dictionary1.xaml 覆盖 FooResourceDictionary 的资源
有些资源如果想要延迟加入到 App.xaml 里面,延迟初始化资源字典的话,就需要考虑 StaticResource 寻找资源的问题。一个可选的方式是自己定义继承 ResourceDictionary 的类型,如本文的 FooResourceDictionary 类型,在类型的构造函数里面写满了 StaticResource 可能使用的资源,从而让 StaticResource 加入延迟初始化逻辑
在后台代码加入新资源字典之前读取静态资源引用的值
上一个例子可以正确获取到资源,在上一个例子的基础上,后台代码加入 Dictionary1.xaml 之前,尝试获取 StaticResource 静态绑定资源引用的值。获取到的值,可以看到获取到的是定义在 FooResourceDictionary 里的资源,很符合预期。但有趣的是,之后尽管加入了 Dictionary1.xaml 但静态资源引用的值不会更新,应用无法跑起来,将提示以下代码
System.InvalidOperationException:““JayabawwiWhenenearfajay.FooResourceDictionary”不是属性“Fill”的有效值。”
详细的步骤如下
只在 App.xaml.cs 的 Dictionary1.xaml 加入之前,添加以下代码用来获取静态绑定资源引用属性的值
var value = ((System.Windows.Setter) (Resources.MergedDictionaries)[1].Values.OfType<Style>().First().Setters[0]).Value;
修改之后的代码如下
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
// 注释这句话试试
var value = ((System.Windows.Setter) (Resources.MergedDictionaries)[1].Values.OfType<Style>().First().Setters[0]).Value;
var resourceDictionary = new ResourceDictionary()
{
Source = new Uri("/Dictionary1.xaml", UriKind.RelativeOrAbsolute)
};
Resources.MergedDictionaries.Add(resourceDictionary);
base.OnStartup(e);
}
}
这里可以看到 value 获取时,将进入 FooResourceDictionary 的 OnGettingValue 函数。拿到的 value 是 FooResourceDictionary 类型,也就是这个资源是在 FooResourceDictionary 提供的。符合预期,因为此时 Dictionary1.xaml 还没加入
但有趣的是在应用运行的时候,即使 Dictionary1.xaml 已经加入,此时拿到的还是原来的 FooResourceDictionary 类型,从而运行失败
这个行为不算魔幻,这是因为 StaticResource 只执行一次,即使后续的字典变更了,也不会重新执行。这是 StaticResource 和 DynamicResource 的差别,这也就是使用 StaticResource 时性能更高的原因。以上的代码在 Dictionary1.xaml 加入之前,获取 StaticResource 静态资源引用绑定的属性的值,从而让 StaticResource 执行,找到了在 FooResourceDictionary 定义的资源。由于 StaticResource 只执行一次,这就导致了即使后续加入 Dictionary1.xaml 资源字典,也不会更新 StaticResource 静态资源引用绑定的属性的值为 Dictionary1.xaml 资源字典的资源,于是应用程序就拿到了错误的对象放入 Fill 属性,运行失败
可以通过如下方式获取本文的源代码,先创建一个名为 JayabawwiWhenenearfajay 的空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin cf93266c7077a9b4acea939ce198bd7a8abe6536
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin cf93266c7077a9b4acea939ce198bd7a8abe6536
获取代码之后,进入 JayabawwiWhenenearfajay 文件夹
资源字典树引用与资源寻找的坑
详细请参阅 WPF 已知问题 资源字典树引用与资源寻找的坑
WPF 将 StaticResource 和 ResourceDictionary 放在一起的魔幻行为的更多相关文章
- WPF 绑定StaticResource到控件的方法
原文:WPF 绑定StaticResource到控件的方法 资源文件内的属性能否直接通过绑定应用到控件?答案是肯定的. 比如,我们要直接把下面的<SolidColorBrush x:Key=&q ...
- WPF当中StaticResource调用方法
1.先在Converter命名空间当中,定义转换功能类: public sealed class BoolToValueConverter : System.Windows.Data.IValueCo ...
- 《深入浅出WPF》读书笔记
依赖属性: 节省实例对内存的开销: 属性值可以通过Binding依赖到其他对象上. WPF中,依赖对象的概念被DependencyObject类实现,依赖属性被DependencyProperty类实 ...
- WPF之神奇的资源
原文:WPF之神奇的资源 WPF中的资源有两种,一种称为"程序集资源"(assembly resource),另一种称为"对象资源"(object resour ...
- WPF学习(9)样式和行为
在asp.net世界中,我们的美工人员会为我们准备好静态页面,它注意包括三个部分:html.css和js.而在WPF世界里,也同样有着类似这三个部分的静态页面:Xaml.Style和Behaviors ...
- 【WPF】城市级联(XmlDataProvider)
首先在绑定的时候进行转换: public class RegionConverter : IValueConverter { public object Convert(object value, T ...
- C# WPF可拖拽的TabControl
微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. C# WPF可拖拽的TabControl 阅读导航 本文背景 代码实现 本文参考 源码 1. ...
- 《Programming WPF》翻译 第7章 3.笔刷和钢笔
原文:<Programming WPF>翻译 第7章 3.笔刷和钢笔 为了在屏幕上绘制一个图形,WPF需要知道你想要为图形填充什么颜色以及如何绘制它的边框.WPF提供了一些Brush类型支 ...
- WPF学习(10)模板
在前面一篇我们粗略说了Style和Behaviors,如果要自定义一个个性十足的控件,仅仅用Style和Behaviors是不行的,Style和Behaviors只能通过控件的既有属性来简单改变外观, ...
- WPF 学习笔记 路由事件
1. 可传递的消息: WPF的UI是由布局组建和控件构成的树形结构,当这棵树上的某个节点激发出某个事件时,程序员可以选择以传统的直接事件模式让响应者来响应之,也可以让这个事件在UI组件树沿着一定的方向 ...
随机推荐
- python基础六(函数基础及参数使用)
一. 函数定义 1.什么是函数 函数就相当于具备某一功能的工具 函数的使用必须遵循一个原则: 先定义 后调用2.为何要用函数 代码冗余,程序的组织结构不清晰,可读性差 可维护性.扩展性差3.如何用函数 ...
- nohup操作
nohup操作 背景:vscode 连接服务器远程运行时经常会出现断连需要重新输入密码,如果是在终端跑程序那么中断后无法再恢复在终端的输出,为了能够让程序保留实验结果或需要长时间跑程序而无需值守,需要 ...
- KingbaseES V8R6备份恢复系列之 -- system-Id不匹配备份故障
KingbaseES V8R6备份恢复案例之---system-Id不匹配备份故障 案例说明: 在KingbaseES V8R6执行备份时,在sys_log日志中出现system-id不一致的故障 ...
- Games101-作业5
说明 本次作业主要实现Whitted-光线追踪,作业框架只需要我们编写两个部分,一个是求解观测光线--从摄像机到每个像素的向量:第二个是判断射线与三角形的交点. 求解观测光线 需要对每个像素求解在实际 ...
- 4 PyExecJS模块
PyExecJS模块 pyexecjs是一个可以帮助我们运行js代码的一个第三方模块. 其使用是非常容易上手的. 但是它的运行是要依赖能运行js的第三方环境的. 这里我们选择用node作为我们运行js ...
- 初识urllib与requests
urllib与requests 一.urllib的学习 学习目标 了解urllib的基本使用 1.urllib介绍 除了requests模块可以发送请求之外, urllib模块也可以实现请求的发送,只 ...
- #NIM游戏#CodeChef A Game With a Sheet of Paper
SHGAME 分析 可以发现每次相当于去掉上下左右的若干行列,也就是 \(x-1,n-1-x,y-1,m-1-y\) 题目就转换成了取石子的问题,先手必胜当且仅当 \((x-1)\) xor \((n ...
- OpenHarmony Docker移植实践
Docker简介 从操作系统诞生之日起,虚拟化技术就不断的演进与发展,结合目前云原生的发展态势,容器无疑是其中的重要一环. Docker是一个开源的软件项目,可以在Linux操作系统上提供一层额外的 ...
- 直播预告丨OpenHarmony标准系统多媒体子系统之视频解读
5月19日(周四)晚上19点,OpenHarmony开源开发者成长计划知识赋能第五期"掌握OpenHarmony多媒体的框架原理"的第五节直播课,即将开播! 深开鸿资深技术专家胡浩 ...
- SQL PRIMARY KEY 约束- 唯一标识表中记录的关键约束
SQL NOT NULL 约束 SQL NOT NULL 约束用于强制确保列不接受 NULL 值.这意味着该字段始终包含一个值,而不允许插入新记录或更新记录时不提供此字段的值. 在 CREATE TA ...