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组件树沿着一定的方向 ...
随机推荐
- 【LeetCode刷题】239.滑动窗口最大值
239.滑动窗口最大值(点击跳转LeetCode) 给你一个整数数组nums,有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口内的k个数字.滑动窗口每次只向右移动一位. ...
- SpringBoot如何优雅的进行参数校验
写在前面 上一篇文章中我们学会了如何优雅的接收前端参数,传送门 SpringBoot如何优雅的接收前端参数 接收到参数后,接下来要做的就是校验参数的合法性.这一步的重要性就不用多说了. 即使前端已经对 ...
- java读写txt
/** * 传入txt路径读取txt文件 * * @param txtPath * @return 返回读取到的内容 */ public String readTxt(String txtPath) ...
- KingbaseES V8R6 复制冲突之锁类型冲突
背景 昨天遇到客户现场的一个有关复制冲突的问题 备库报错:ERROR: canceling statement due to conflict with recovery,user was holdi ...
- Atcoder DP contest 题解
动态规划(Atcoder DP 26题) on Atcoder on Luogu 本文同步发表于知乎专栏. Frog 1 $N$ 个石头,编号为 $1,2,...,N$.对于每个 $i(1 \leq ...
- MySQL命令创建只读权限用户
查询用户 select user,authentication_string,host,Select_priv,Delete_priv from mysql.user; 创建用户 CREATE USE ...
- 学习 Tensorflow 的困境与解药
我构建的预测模型 在过去的一段时间里我抓去了小宇宙内上万条播客节目的首日播放量的数据,并利用这些数据构建了一个用于预测播客节目播放量的模型.包含以下六个输入参数: 节目发布于一周中的哪一天 节目发布于 ...
- #Kruskal,可撤销并查集#CF891C Envy
题目 给出一个 \(n\) 个点 \(m\) 条边的无向图,每条边有边权,共 \(Q\) 次询问, 每次给出 \(k_i\) 条边,问这些边能否同时在一棵最小生成树上. 分析 考虑最小生成树选择的边权 ...
- #dp#D 导出子图
代码 #include <cstdio> #include <cctype> #include <algorithm> #define rr register us ...
- 使用OHOS SDK构建libjpeg-turbo
参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone --depth=1 https://github.com/libjpeg-turb ...