本文将记录一些在 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 的资源

以上测试代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个名为 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 定义的影响

以上测试代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个名为 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 属性,运行失败

以上测试代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个名为 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 放在一起的魔幻行为的更多相关文章

  1. WPF 绑定StaticResource到控件的方法

    原文:WPF 绑定StaticResource到控件的方法 资源文件内的属性能否直接通过绑定应用到控件?答案是肯定的. 比如,我们要直接把下面的<SolidColorBrush x:Key=&q ...

  2. WPF当中StaticResource调用方法

    1.先在Converter命名空间当中,定义转换功能类: public sealed class BoolToValueConverter : System.Windows.Data.IValueCo ...

  3. 《深入浅出WPF》读书笔记

    依赖属性: 节省实例对内存的开销: 属性值可以通过Binding依赖到其他对象上. WPF中,依赖对象的概念被DependencyObject类实现,依赖属性被DependencyProperty类实 ...

  4. WPF之神奇的资源

    原文:WPF之神奇的资源 WPF中的资源有两种,一种称为"程序集资源"(assembly resource),另一种称为"对象资源"(object resour ...

  5. WPF学习(9)样式和行为

    在asp.net世界中,我们的美工人员会为我们准备好静态页面,它注意包括三个部分:html.css和js.而在WPF世界里,也同样有着类似这三个部分的静态页面:Xaml.Style和Behaviors ...

  6. 【WPF】城市级联(XmlDataProvider)

    首先在绑定的时候进行转换: public class RegionConverter : IValueConverter { public object Convert(object value, T ...

  7. C# WPF可拖拽的TabControl

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. C# WPF可拖拽的TabControl 阅读导航 本文背景 代码实现 本文参考 源码 1. ...

  8. 《Programming WPF》翻译 第7章 3.笔刷和钢笔

    原文:<Programming WPF>翻译 第7章 3.笔刷和钢笔 为了在屏幕上绘制一个图形,WPF需要知道你想要为图形填充什么颜色以及如何绘制它的边框.WPF提供了一些Brush类型支 ...

  9. WPF学习(10)模板

    在前面一篇我们粗略说了Style和Behaviors,如果要自定义一个个性十足的控件,仅仅用Style和Behaviors是不行的,Style和Behaviors只能通过控件的既有属性来简单改变外观, ...

  10. WPF 学习笔记 路由事件

    1. 可传递的消息: WPF的UI是由布局组建和控件构成的树形结构,当这棵树上的某个节点激发出某个事件时,程序员可以选择以传统的直接事件模式让响应者来响应之,也可以让这个事件在UI组件树沿着一定的方向 ...

随机推荐

  1. uni学习笔记分享

    目录介绍 01.遇到问题汇总 02.关于布局设置 03.基础语法总结 04.关于交互问题 06.关于回传数据 07.关于网络请求 08.关于页面刷新 09.关于注意问题 10.待解决和思考 01.遇到 ...

  2. Toast源码深度分析

    目录介绍 1.最简单的创建方法 1.1 Toast构造方法 1.2 最简单的创建 1.3 简单改造避免重复创建 1.4 为何会出现内存泄漏 1.5 吐司是系统级别的 2.源码分析 2.1 Toast( ...

  3. PowerDesigner操作要点

    一.PowerDesigner解决name和code同步问题 工具-常规选项-General  Options-Dialog-Name to Code mirroring√去掉 二.PowerDesi ...

  4. 开发必会系列:J2EE是什么

    为什么Java是跨平台的? 高级语言通过编译器,转为汇编语言,汇编语言通过汇编器转为0和1. 当c转为汇编时,不同厂家cpu,用不同的指令集,所以有不同的汇编语言结果,导致c不能跨平台. java在各 ...

  5. 【K8S】如何进入kubernetes的一个pod

    如何进入kubernetes的一个pod呢,其实和进入docker的一个容器相似: 进入docker容器 : docker exec -ti <your-container-name> / ...

  6. Linux开发相关命令整理

    1. 反转shell 2. ldd 3. objdump 4. ldconfig 5. telnet 6. nc 7. netstat 8. ss 9. tcpdump 10. lsof 11. st ...

  7. 通过位运算修改指定bit位的值

    通过位运算将指定位的值置0或1 问题样例 假如现在有一个8bit二进制数A,其可以为任何值,所以这里不妨先设A=(xxxxxxxx)2,{x|0,1} 现在需要你将A的几个指定位修改为1或0,例如将A ...

  8. #线性dp#洛谷 5999 [CEOI2016]kangaroo

    题目 问有多少个长度为 \(n\) 的排列满足首项为 \(st\),末项为 \(ed\), 并且 \(\forall i\in (1,n),\left[a_{i-1}<a_i \oplus a_ ...

  9. 【直播回顾】参与文档贡献,开启OpenHarmony社区贡献

      5月25日晚上19点,战"码"先锋第二期直播 <参与文档贡献,开启OpenHarmony社区贡献> ,在OpenHarmony社群内成功举行.   本期课程,由华为 ...

  10. 使用谷歌浏览器打开PDF文件,怎么关闭缩略图

    我们在使用谷歌浏览器浏览PDF文件时,总是会出现章节预览缩略图和工具栏,我们可以使用 参数来控制浏览器不显示出工具栏 #scrollbars=0&toolbar=0&statusbar ...