翼度科技»论坛 编程开发 .net 查看内容

Freezable ---探索WPF中Freezable承载数据的原理

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
引言

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

这里有两个问题:

  • 非可视化树中的元素不能通过 RelativeSource 或者 ElementName 访问到可视化树中的数据,为何可以通过 resource 的方式访问?
  • Freezable 类为何能够中转数据,DependencyObject 不行?
那么本篇文章就来探索一下 Freezable实现了上述功能的原理是什么?
原理探索

准备

我们还是使用上一篇文章中的示例,让后为了便于剖析源码,做了部分改动。
首先,准备自定义  Freezable 类:
  1. public class CustomFreezable : Freezable
  2. {
  3.     public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(CustomFreezable));
  4.     public object Value
  5.     {
  6.         get => (object)GetValue(ValueProperty);
  7.         set => SetValue(ValueProperty, value);
  8.     }
  9.     protected override void OnChanged()
  10.     {
  11.         base.OnChanged();
  12.     }
  13.    
  14.     protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
  15.     {
  16.         base.OnPropertyChanged(e);
  17.     }
  18.     protected override Freezable CreateInstanceCore()
  19.     {
  20.         return new CustomFreezable();
  21.     }
  22. }
复制代码
然后准备界面,但是这回跟之前不一样的是所有 DataGridTextColumn 列不在 XAML 中绑定,我们放在后台绑定:
  1. <Window.Resources>
  2.     <local:VisibilityConverter x:Key="VisibilityConverter" />
  3.     <local:CustomFreezable x:Key="customFreezable" Value="{Binding IsVisibility, Converter={StaticResource VisibilityConverter}}" />
  4. </Window.Resources>
  5. <Grid>
  6.     <Grid>
  7.         <Grid.ColumnDefinitions>
  8.             <ColumnDefinition Width="1*" />
  9.             <ColumnDefinition Width="1*" />
  10.         </Grid.ColumnDefinitions>
  11.         <DataGrid
  12.             x:Name="dataGrid"
  13.             AutoGenerateColumns="False"
  14.             CanUserAddRows="False"
  15.             ItemsSource="{Binding Persons}"
  16.             SelectionMode="Single">
  17.         </DataGrid>
  18.         <CheckBox
  19.             Grid.Column="1"
  20.             Content="是否显示年龄列"
  21.             IsChecked="{Binding IsVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
  22.     </Grid>
  23. </Grid>
复制代码
然后准备 Code-Behind 代码,增加 InitDataGrid() ,手动绑定所有列。
  1. public partial class MainWindow : Window, INotifyPropertyChanged
  2. {
  3.     public event PropertyChangedEventHandler? PropertyChanged;
  4.     public void OnPropertyChanged([CallerMemberName] string propertyName = null)
  5.     {
  6.         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  7.     }
  8.     public MainWindow()
  9.     {
  10.         InitializeComponent();
  11.         Persons = new ObservableCollection<Person>() { new Person() { Age = 11, Name = "Peter" }, new Person() { Age = 19, Name = "Jack" } };
  12.         DataContext = this;
  13.         InitDataGrid();
  14.     }
  15.     private void InitDataGrid()
  16.     {
  17.         DataGridTextColumn columen1 = new DataGridTextColumn();
  18.         columen1.Header = "年龄";
  19.         columen1.Binding = new Binding("Age");
  20.         columen1.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
  21.         Binding binding = new Binding("Value");
  22.         binding.Source = FindResource("customFreezable");
  23.         BindingOperations.SetBinding(columen1, DataGridTextColumn.VisibilityProperty, binding);
  24.         dataGrid.Columns.Add(columen1);
  25.         DataGridTextColumn columen2 = new DataGridTextColumn();
  26.         columen2.Header = "姓名";
  27.         columen2.Binding = new Binding("Name");
  28.         columen2.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
  29.         dataGrid.Columns.Add(columen2);
  30.     }
  31.     private bool isVisibility = true;
  32.     public bool IsVisibility
  33.     {
  34.         get => isVisibility;
  35.         set
  36.         {
  37.             isVisibility = value;
  38.             OnPropertyChanged(nameof(IsVisibility));
  39.         }
  40.     }
  41.     private ObservableCollection<Person> persons;
  42.     public ObservableCollection<Person> Persons
  43.     {
  44.         get { return persons; }
  45.         set { persons = value; OnPropertyChanged(); }
  46.     }
  47. }
复制代码
源码剖析

在源码剖析之前,如果大家还不会如何使用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 的方式,让它动态变化,也可以使用上文示例的方式进行绑定。
好了,源码解析的过程其实还是比较复杂的,本文中其实也省略了一些源码阅读过程中细节,若大家阅读有疑问的地方,欢迎找我解疑,建议不明白的点,优先自行进行一下源码调试。
有错误之处,还请大家指正。

来源:https://www.cnblogs.com/pandefu/Undeclared/17946312
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具