Freezable ---探索WPF中Freezable承载数据的原理
引言
在之前写的一篇文章【WPF --- 如何以Binding方式隐藏DataGrid列】中,我先探索了 DataGridTextColumn 为什么不在可视化树结构内?又给出了解决方案,使用 Freezable ,该抽象类是 DependencyObject 的子类,能使用依赖属性在 Xaml 进行绑定,它承载了 DataContext 且有属性变化通知功能,触发 VisibilityConverter转换器,实现了预期功能。
然后有群友问了这样一个问题:

这里有两个问题:
- 非可视化树中的元素不能通过
RelativeSource或者ElementName访问到可视化树中的数据,为何可以通过resource的方式访问? Freezable类为何能够中转数据,DependencyObject不行?
那么本篇文章就来探索一下 Freezable实现了上述功能的原理是什么?
原理探索
准备
我们还是使用上一篇文章中的示例,让后为了便于剖析源码,做了部分改动。
首先,准备自定义 Freezable 类:
public class CustomFreezable : Freezable
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(CustomFreezable));
public object Value
{
get => (object)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
protected override void OnChanged()
{
base.OnChanged();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
}
protected override Freezable CreateInstanceCore()
{
return new CustomFreezable();
}
}
然后准备界面,但是这回跟之前不一样的是所有 DataGridTextColumn 列不在 XAML 中绑定,我们放在后台绑定:
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter" />
<local:CustomFreezable x:Key="customFreezable" Value="{Binding IsVisibility, Converter={StaticResource VisibilityConverter}}" />
</Window.Resources>
<Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
ItemsSource="{Binding Persons}"
SelectionMode="Single">
</DataGrid>
<CheckBox
Grid.Column="1"
Content="是否显示年龄列"
IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
然后准备 Code-Behind 代码,增加 InitDataGrid() ,手动绑定所有列。
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindow()
{
InitializeComponent();
Persons = new ObservableCollection<Person>() { new Person() { Age = 11, Name = "Peter" }, new Person() { Age = 19, Name = "Jack" } };
DataContext = this;
InitDataGrid();
}
private void InitDataGrid()
{
DataGridTextColumn columen1 = new DataGridTextColumn();
columen1.Header = "年龄";
columen1.Binding = new Binding("Age");
columen1.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
Binding binding = new Binding("Value");
binding.Source = FindResource("customFreezable");
BindingOperations.SetBinding(columen1, DataGridTextColumn.VisibilityProperty, binding);
dataGrid.Columns.Add(columen1);
DataGridTextColumn columen2 = new DataGridTextColumn();
columen2.Header = "姓名";
columen2.Binding = new Binding("Name");
columen2.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
dataGrid.Columns.Add(columen2);
}
private bool isVisibility = true;
public bool IsVisibility
{
get => isVisibility;
set
{
isVisibility = value;
OnPropertyChanged(nameof(IsVisibility));
}
}
private ObservableCollection<Person> persons;
public ObservableCollection<Person> Persons
{
get { return persons; }
set { persons = value; OnPropertyChanged(); }
}
}
源码剖析
在源码剖析之前,如果大家还不会如何使用VS调试.Net源码,建议先阅读我的另一篇文章【编程技巧 --- VS如何调试.Net源码】,学习如何调试源码。
接下来,在程序启动之前,我们在 CustomFreezable 的重载方法 OnChanged() 设置断点,然后使用VS调试源码,查看调用堆栈:

可以看到,从 InitDataGrid() 开始,到属性变化触发变化事件,整个流程都可以在调用堆栈中看到,我们可以逐帧分析,来解决开篇的两个问题。
剖析步骤
我们将上述调用链编号,逐步分析:

- 编号1:FindResource(...)

- 编号2:FrameworkElement.FindResourceInternal(...)

- 编号3:FindResourceInTree(...)

- 编号4:FetchResource(...)

- 编号5~6:GetValue(...),在这里已经获取到字典中资源了。

- 编号7~8 OnGettingValue(...)

- 编号9~10 AddInheritanceContext(...)

编号11~12 ProvideSelfAsInheritanceContext(...)

编号13 AddInheritanceContext(...)


后面的就不用看了,后面的就是因为 Freezable 更换了 InheritanceContext 触发了OnInheritanceContextChanged()后又触发了 NotifyPropertyChange。
接下来看看为什么当 IsVisibility 变化时,能通知到 Freezable?
- NotifySubPropertyChange(...)

- FireChanged(...)

- GetChangeHandlersAndInvalidateSubProperties(...)

可以看到从1~9仅仅是 FindResource("customFreezable"); 这一个方法所作的事情,主要是从资源字典中查询想要的对象,如果该对象是 Freezable类型的,则将当前资源的 DataContent的 Visual 绑定为 Freezable的 InheritanceContext ,然后10~12,是该上下文在当前资源的 DataCobtent 触发 PropertyChanged时,去InheritanceContext 中找出关联的 CallHandle 强制刷新,触发变化事件,达到联动效果。
那么从解析源码的过程中看,开篇的两个问题就都有了答案
非可视化树中的元素不能通过
RelativeSource或者ElementName访问到可视化树中的数据,为何可以通过resource的方式访问?原因就是
FindResource方法中,如果要查询的资源是Freezable类型的,则会将当前资源的DataContent的Visual绑定到InheritanceContext,所以Freezable也就可以访问到可视化树中的数据了。Freezable类为何能够中转数据,DependencyObject不行?从代码中,编号11~12 ProvideSelfAsInheritanceContext(...)也可以看出,绑定
InheritanceContext时有一个必要条件就是该资源必须为Freezable类型的才可以,我猜测这可能跟这个类的定义有关系,Freezable类为WPF中的对象提供了不可变性和性能优化的功能,同时也为动画、资源共享和跨线程安全性等方面提供了便利。 该类是更好地管理和优化 WPF 应用程序中的对象和资源的,所以可能不想让开发者随意使用吧,所以就仅提供该类能够拥有InheritanceContext而没法使用DependencyObject。
小结
Freezable 类除了上文示例中的用法,其实它这种间接绑定的方式可以解决很多场景,比如某个元素的属性并不是依赖属性,但是你就是想使用 Binding 的方式,让它动态变化,也可以使用上文示例的方式进行绑定。
好了,源码解析的过程其实还是比较复杂的,本文中其实也省略了一些源码阅读过程中细节,若大家阅读有疑问的地方,欢迎找我解疑,建议不明白的点,优先自行进行一下源码调试。
Freezable ---探索WPF中Freezable承载数据的原理的更多相关文章
- WPF中ListBox ListView数据翻页浏览笔记(强调:是数据翻页,非翻页动画)
ListBox和ListView在应用中,常常有需求关于每页显示固定数量的数据,然后通过Timer自动或者手动翻页操作,本文介绍到的就是该动作的实现. 一.重点 对于ListBox和ListView来 ...
- WPF中RadioButton绑定数据的正确方法
RadioButton一般用于单选的时候,也就是从一组值中选择一个值. 比如性别有“男”和“女”两种取值,而对于一个员工的实例来说,性别的取值要么是男,要么是女. 这种时候一般就会用到RadioBut ...
- 正确理解WPF中的TemplatedParent
(注:Logical Tree中文称为逻辑树,Visual Tree中文称为可视化树或者视觉树,由于名称不是很统一,文中统一用英文名称代表两个概念,况且VisualTreeHelper和Logical ...
- Freezable 对象(WPF)
# Freezable 对象(WPF) # > Freezable 继承自 DependencyObject,同时添加了 Freezable 方法,用于冻结对象. --- ## 冻结对象 ## ...
- WPF中的数据验证
数据验证 WPF的Binding使得数据能够在数据源和目标之间流通,在数据流通的中间,便能够对数据做一些处理. 数据转换和数据验证便是在数据从源到目标 or 从目标到源 的时候对数据的验证和转换. V ...
- WPF中的数据模板(DataTemplate)(转)
原文地址 http://www.cnblogs.com/zhouyinhui/archive/2007/03/30/694388.html WPF中的数据模板(DataTemplate) ...
- wpf中的数据模板
wpf中的模板分为数据模板和控件模板,我们可以通过我们自己定制的数据模板来制定自己想要的数据表现形式.例如:时间的显示可以通过图片,也可以通过简单数字表现出来. 例如: (1)先在Demo这个命名空间 ...
- WPF中的数据模板(DataTemplate)
原文:WPF中的数据模板(DataTemplate) WPF中的数据模板(DataTemplate) ...
- WPF中的数据模板使用方式之一:ContentControl、ContentTemplate和TemplateSelector的使用
在WPF中,数据模板是非常强大的工具,他是一块定义如何显示绑定的对象的XAML标记.有两种类型的控件支持数据模板:(1)内容控件通过ContentTemplate属性支持数据模板:(2)列表控件通过I ...
- 在WPF中一种较好的绑定Enums数据方法
引言 在你使用wpf应用程序开发的时候,是否需要进行数据绑定到Enum数据呢?在这篇文章中,我将向你展示在WPF中处理Enum数据绑定的方法. 假设存在一个这样的Enum数据的定义,具体内容如下文代码 ...
随机推荐
- Django框架——中间件、Auth模块、ContentType
文章目录 一 什么是中间件 二 中间件有什么用 三 自定义中间件 process_request和process_response process_view process_exception pro ...
- threading 专递类对象
import threading class MyClass: def __init__(self, name): self.name = name def my_method(self): prin ...
- AI图形算法的应用之一:通过图片模板对比发现油田漏油
最近研究了一下OPENCV的图像算法,开发了一个小应用. 可以通过图像和模板进行对比,发现油田或其他作业区漏油. 直接上效果,模板如下 自己模拟了一个漏油的现场图片,如下 通过图形化算法,找到漏油点, ...
- java 创建对象的5种方式
1.创建对象的5种方式 1.通过 new 关键字 这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象.比如 Object obj = new Object(); 2.通过 ...
- [Python]对称日!
def check(year): if (year%4 == 0 and year%100 != 0) or year%400 == 0: return True else: return False ...
- Apache Hudi Timeline:支持 ACID 事务的基础
Apache Hudi 维护在给定表上执行的所有操作的Timeline(时间线),以支持以符合 ACID 的方式高效检索读取查询的数据. 在写入和表服务期间也会不断查阅时间线,这是表正常运行的关键. ...
- 如何使用Python将PDF转为Excel
PDF文件是一种静态文档格式,通常难以编辑,而Excel则是一个灵活的表格工具.如果你需要处理PDF表格中的数据,那么将其导出为Excel文件可以大大节省工作时间和精力.Excel提供的强大数据编辑和 ...
- lora训练之偷师
自stable diffusion开源之后AIGC绘画方向定制化百花齐放百家争鸣.而c站 https://civitai.com/ 也聚集了全球爱好者的各种微调训练模型分享. 其中以lora为首,应用 ...
- 题解 AGC054D
前言 因为本人尚菜,所以本篇文章没有什么数学符号,请大家放心食用. 题目分析 先吐槽一嘴,这个 o 表示 (),这个 x 表示 )(,十分形象. 好,我们先观察原序列,容易得出第一条性质: ox 的加 ...
- P2360 地下城主
题目大意 背景是逃离\(3D\)地下监狱,也就是三维样例,你可以前往所在小格的前方,后方,左方,右方,上层,下层的小格,'.'表示可走,'x'表示墙壁,'S'表示起点,'E'表示终点.每走一小格花费一 ...