读 MAUI 源代码 理解可绑定对象和可绑定属性的存储机制
和 UWP 与 WPF 不同的是在 MAUI 里面,使用可绑定对象 BindableObject 替换了依赖对象的概念,我阅读了 MAUI 的源代码发现其实只是命名变更了,里面的机制和设计思想都是差不多的。在 MAUI 里面提供 BindableObject 用来支持可绑定属性机制和附加属性机制,本文将告诉大家在 MAUI 里面是如何在可绑定对象里面提供可绑定属性和附加属性的存储的机制
在 WPF 里面,依赖属性的提出的一部分原因是为了省内存。在 MAUI 里面,我猜测省内存是可绑定对象提出的一个原因。由于一个界面控件,例如按钮等,有着非常庞大数量的属性,假设每个控件里面的所有属性都是需要独立的对象不能共用,那么在复杂界面上,将会因为大量的控件的大量属性占用大量的内存。可绑定对象里面可以实现在属性没有被赋值时,将可以使用默认值,而对于大部分控件来说,很多不常用的属性都是使用默认值即可。可绑定对象需要解决的是让可绑定属性可以代替普通的 CLR 属性,对可绑定属性进行赋值时,可以值和可绑定对象关联,从而可以读取出来。既然名字叫可绑定对象,那自然也要实现绑定的支持,绑定的支持的核心就是通知,需要支持在属性值变更的时候进行通知。接下来将通过阅读源代码了解在 MAUI 里是如何实现
打开 MAUI 的 BindableObject 的源代码,可以看到在 BindableObject 里有 _properties
字段,定义如下
public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
{
readonly Dictionary<BindableProperty, BindablePropertyContext> _properties = new Dictionary<BindableProperty, BindablePropertyContext>(4);
}
没错,这就是在 MAUI 里面的可绑定对象的存储核心实现。在 MAUI 的可绑定对象里面通过 _properties
字典存放可绑定属性的值内容,字典的 Key 是 BindableProperty 可绑定属性,字典的 Value 是 BindablePropertyContext 可绑定属性上下文,初始化字典默认占用 4 个空间,默认初始化空间是为了优化而已,没有什么特别用途。通过此字典定义可以了解到存储的核心实现就是将可绑定属性和对应的值存入到对象的字典里,例如给某个可绑定对象的某个叫 Xxx 的可绑定属性进行赋值,那将会对 _properties
字典更新 Xxx 属性的值内容
在 MAUI 的实现是,在可绑定对象里面,使用 SetValueCore 方法进行属性更新赋值,我删掉了不关键的逻辑的代码如下
internal void SetValueCore(BindableProperty property, object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes)
{
// 获取或创建可绑定属性上下文信息
BindablePropertyContext context = GetOrCreateContext(property);
SetValueActual(property, context, value, currentlyApplying, attributes, silent);
}
BindablePropertyContext GetOrCreateContext(BindableProperty property) => GetContext(property) ?? CreateAndAddContext(property);
internal BindablePropertyContext GetContext(BindableProperty property) => _properties.TryGetValue(property, out var result) ? result : null;
BindablePropertyContext CreateAndAddContext(BindableProperty property)
{
var context = new BindablePropertyContext { ... };
_properties.Add(property, context);
return context;
}
void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
{
// 触发对象变更前事件
context.Value = value;
// 触发对象已变更事件
}
可以看到赋值的第一步就是调用 GetOrCreateContext 方法,尝试去拿到上下文信息,如果拿不到就创建。这里的用到的 BindablePropertyContext 上下文信息是存储可绑定属性的关键,在 BindablePropertyContext 里面存放了很多字段,定义如下
public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
{
internal class BindablePropertyContext
{
public BindableContextAttributes Attributes;
public BindingBase Binding;
public Queue<SetValueArgs> DelayedSetters;
public BindableProperty Property;
public object Value;
public bool StyleValueSet;
public object StyleValue;
}
}
可以看到 BindablePropertyContext 是一个内部类型,也不对外开放。在 BindablePropertyContext 里面重要的就是 Value
字段,表示存储的实际值内容。其次为了更好的支持绑定,也添加了 Binding
字段
在获取到 BindablePropertyContext 上下文之后,即可进行赋值,赋值是调用 SetValueActual 方法进行赋值,赋值前后分别触发事件用来通知。触发通知事件最重要的功能是让绑定可以有刷新的时机。如此即可完成赋值过程
通知事件是分别触发可绑定的对象的通知事件和对应的可绑定属性的通知事件,如下面代码
void SetValueActual(BindableProperty property, BindablePropertyContext context, object value, bool currentlyApplying, SetValueFlags attributes, bool silent = false)
{
// 触发对象变更前事件
property.PropertyChanging?.Invoke(this, original, value);
OnPropertyChanging(property.PropertyName);
context.Value = value;
// 触发对象已变更事件
OnPropertyChanged(property.PropertyName);
property.PropertyChanged?.Invoke(this, original, value);
}
通过以上代码可以看到,可绑定对象给可绑定属性赋值的时候,就是先获取或创建可绑定属性上下文,将赋值的参数值给到 可绑定属性上下文 的 Value 字段。如此完成赋值过程
由于赋值的参数值被放入到 可绑定属性上下文 的 Value 字段,而 可绑定属性上下文 又放入到 _properties
字典里,相当于间接将 赋值的参数值 放入到 _properties
字典里。自然在获取值过程里,也需要从字典里面读取。在 MAUI 里面读取可绑定属性是通过 GetValue 方法实现,代码如下
public object GetValue(BindableProperty property)
{
if (property == null)
throw new ArgumentNullException(nameof(property));
var context = property.DefaultValueCreator != null ? GetOrCreateContext(property) : GetContext(property);
return context == null ? property.DefaultValue : context.Value;
}
以上代码的判断 BindableProperty 的 DefaultValueCreator 属性逻辑是 MAUI 特有的逻辑,和 WPF 与 UWP 不相同,咱下文再聊。回到获取属性的方法上,是通过先获取对象的可绑定上下文信息,如果能获取到可绑定上下文,证明此可绑定对象的这个可绑定属性曾经被赋值过,需要用赋值更新的内容。如果拿到的可绑定属性上下文是空,那就使用可绑定属性定义的默认值即可
在 MAUI 里面,通过 BindableProperty 的 DefaultValueCreator 属性简化了可绑定属性的定义,和让可绑定属性更加强大。使用 MAUI 的可绑定属性和可绑定对象对比 WPF 的依赖属性和依赖对象的实现,可以看到 MAUI 的实现实在简洁很多。在 MAUI 里的 BindableProperty 的 DefaultValueCreator 属性是一个委托,定义如下
public sealed class BindableProperty
{
public delegate object CreateDefaultValueDelegate(BindableObject bindable);
internal CreateDefaultValueDelegate DefaultValueCreator { get; }
}
可以看到 BindableProperty 的 DefaultValueCreator 属性的委托是支持给传入的可绑定对象进行处理,对可绑定对象返回特定的默认值。这里值得说明的是,通过委托是可以特例给可绑定对象不同的默认值的,但不代表着一定是不同的可绑定对象都一定需要不同的默认值对象。这里只是一个委托,让委托返回相同的对象是完全可以的。这个委托更多的是使用在判断可绑定对象类型,根据可绑定类型对象或者状态,返回不同的默认值。或者是返回一个需要运行时动态计算值,而不是一个可以写固定在代码里面的参数
例如对于 FontSize 的可绑定属性的定义里,就采用让不同的控件返回不同的默认字体大小,定义如下
public static readonly BindableProperty FontSizeProperty =
BindableProperty.Create("FontSize", typeof(double), typeof(IFontElement), 0d,
propertyChanged: OnFontSizeChanged,
defaultValueCreator: FontSizeDefaultValueCreator);
static object FontSizeDefaultValueCreator(BindableObject bindable)
=> ((IFontElement)bindable).FontSizeDefaultValueCreator();
也就是说对于不同的可绑定对象,获取到的默认的字体大小是根据对应的可绑定对象的 FontSizeDefaultValueCreator 方法实现决定,不同的可绑定对象可以有不同的实现,从而实现了让默认值关联上具体的可绑定对象类型。这个创新的设计,可以省掉在 WPF 里面的大量默认依赖属性值重写的逻辑代码,省掉了这部分代码,也可以大量减少的机制,从而减少更多的代码
例如 Span 和 Editor 控件对字体大小默认值有不同的实现
public class Span : GestureElement, IFontElement
{
double IFontElement.FontSizeDefaultValueCreator() =>
double.NaN;
}
public partial class Button : View, IFontElement
{
double IFontElement.FontSizeDefaultValueCreator() =>
this.GetDefaultFontSize();
}
同样,对于某些可绑定属性来说,需要给每个可绑定对象的对象不同的默认值对象,例如 Grid 里面的 RowDefinitions 属性。大家都知道,在 Grid 里面的 RowDefinitions 是一个集合,如果集合也是一个共享的默认值,那自然会存在默认值污染。如果默认值是一个空值,那么将会让 Grid 逻辑里面存在大量的判断空逻辑,或者需要其他额外的初始化逻辑。在 MAUI 里面,通过 DefaultValueCreator 委托,实现了每个 Grid 对象使用独立的默认值对象,代码如下
public class Grid : Layout, IGridLayout
{
public static readonly BindableProperty RowDefinitionsProperty = BindableProperty.Create("RowDefinitions",
typeof(RowDefinitionCollection), typeof(Grid), null, validateValue: (bindable, value) => value != null,
propertyChanged: UpdateSizeChangedHandlers, defaultValueCreator: bindable =>
{
// 每个 Grid 对象使用独立的,新创建的默认值对象
var rowDef = new RowDefinitionCollection();
rowDef.ItemSizeChanged += ((Grid)bindable).DefinitionsChanged;
return rowDef;
});
}
在 MAUI 里面除了可绑定属性之外,还有一个特殊的属性类型,附加属性。附加属性可以定义在任意的类型里面,通过附加属性,给某个现有的类型附加上属性。功能上和 WPF 或 UWP 的附加属性功能是相同的。可绑定属性和附加属性都是相同的 BindableProperty 类型,只是在创建的时候,调用的静态创建方法不同而已。对于可绑定属性来说,调用的是 BindableProperty.Create
方法创建。对于附加属性来说,调用 BindableProperty.CreateAttached
创建。在 MAUI 里面,通过阅读代码,我认为分开两个方法更多的是为了兼容 WPF 或 UWP 的写法,没有非常本质的差别,参数也差不多,如下面代码
internal static BindableProperty Create(string propertyName, Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
CreateDefaultValueDelegate defaultValueCreator = null)
{
return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging,
defaultValueCreator: defaultValueCreator);
}
internal static BindableProperty CreateAttached(string propertyName, Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode, ValidateValueDelegate validateValue,
BindingPropertyChangedDelegate propertyChanged, BindingPropertyChangingDelegate propertyChanging, CoerceValueDelegate coerceValue, BindablePropertyBindingChanging bindingChanging,
bool isReadOnly, CreateDefaultValueDelegate defaultValueCreator = null)
{
return new BindableProperty(propertyName, returnType, declaringType, defaultValue, defaultBindingMode, validateValue, propertyChanged, propertyChanging, coerceValue, bindingChanging, isReadOnly,
defaultValueCreator);
}
如此可以看到可绑定属性和附加属性从参数上是似乎相同的。由于附加属性也是一个可绑定属性类型,同理可以了解到附加属性的存储也和可绑定对象的可绑定属性的存储是相同的。如此也能解答一个问题,在 MAUI 的附加属性,附加到对象上,附加属性的参数值是如何跟随对象的生命周期的问题。由于附加属性也是一个可绑定属性,同样将参数值存在可绑定对象的 _properties
字典里面,在对象会 GC 回收时,自然 _properties
字段也被回收,那放在字典里面的参数值也自然被减去引用,当参数值的没有被引用时,也就自然被回收
在 MAUI 里面,可绑定对象基类型的意义就是提供了可绑定属性的机制,存储可绑定属性的方式就是通过 _properties
字典存放。通过字典存放的内容是被赋值更改的属性,没有赋值更改的属性是没有被放入到字典里面,获取在字典里面没有存放的属性时,将会通过对应的可绑定属性获取到默认值。默认值的获取有两个方式,一个是可绑定属性的固定的默认值属性,另一个是通过可绑定属性的默认值创建委托创建默认值。在 MAUI 里的可绑定属性的默认值创建委托是一个创新,可以写出让不同的可绑定对象使用不同的默认值的功能,也可以写出根据不同的可绑定对象类型返回不同的默认值,通过委托的方式灵活实现复杂的功能
更多的 MAUI 相关博客,还请参阅我的 博客导航
读 MAUI 源代码 理解可绑定对象和可绑定属性的存储机制的更多相关文章
- 背水一战 Windows 10 (22) - 绑定: 通过 Binding 绑定对象, 通过 x:Bind 绑定对象, 通过 Binding 绑定集合, 通过 x:Bind 绑定集合
[源码下载] 背水一战 Windows 10 (22) - 绑定: 通过 Binding 绑定对象, 通过 x:Bind 绑定对象, 通过 Binding 绑定集合, 通过 x:Bind 绑定集合 作 ...
- 绑定: 通过 Binding 绑定对象, 通过 x:Bind 绑定对象, 通过 Binding 绑定集合, 通过 x:Bind 绑定集合
背水一战 Windows 10 之 绑定 通过 Binding 绑定对象 通过 x:Bind 绑定对象 通过 Binding 绑定集合 通过 x:Bind 绑定集合 示例1.演示如何通过 Bindin ...
- 读Flask源代码学习Python--config原理
读Flask源代码学习Python--config原理 个人学习笔记,水平有限.如果理解错误的地方,请大家指出来,谢谢!第一次写文章,发现好累--!. 起因 莫名其妙在第一份工作中使用了从来没有接 ...
- 带搜索功能,支持绑定对象到节点的TreeView辅助类
特点: 1.支持数叶子节点与对象绑定 2.支持xml导入,且数据类相关的xml可自定义,只和泛型的实现有关 3.支持节点搜索功能,可在树结构上要求只显示部分节点 4.用C#编写,但与平台关联性低,可移 ...
- 1.面向过程编程 2.面向对象编程 3.类和对象 4.python 创建类和对象 如何使用对象 5.属性的查找顺序 6.初始化函数 7.绑定方法 与非绑定方法
1.面向过程编程 面向过程:一种编程思想在编写代码时 要时刻想着过程这个两个字过程指的是什么? 解决问题的步骤 流程,即第一步干什么 第二步干什么,其目的是将一个复杂的问题,拆分为若干的小的问题,按照 ...
- 深入理解Javascript window对象
首先看我们的源代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> ...
- 类的封装,property特性,类与对象的绑定方法和非绑定方法,
类的封装 就是把数据或者方法封装起来 为什么要封装 封装数据的主要原因是:保护隐私 封装方法的主要原因是:隔离复杂度(快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了,比如你 ...
- 理解Java中对象基础Object类
一.Object简述 源码注释:Object类是所有类层级关系的Root节点,作为所有类的超类,包括数组也实现了该类的方法,注意这里说的很明确,指类层面. 所以在Java中有一句常说的话,一切皆对象, ...
- 深入理解javascript对象系列第二篇——属性操作
× 目录 [1]查询 [2]设置 [3]删除[4]继承 前面的话 对于对象来说,属性操作是绕不开的话题.类似于“增删改查”的基本操作,属性操作分为属性查询.属性设置.属性删除,还包括属性继承.本文是对 ...
- Firemonkey绑定对象列表
在实现Firemonkey绑定对象列表的过程中,我遇到的一些现有教程当中没有提到的细节,分享一下. 1.追加对象 用Navigator插入记录,位置总是在当前记录之前插入,没有在最后追加一个对象的方法 ...
随机推荐
- Linux快速入门(五)Linux系统管理
top top命令相当于任务管理器.在top命令中,可以使用M,将进程列表按内存使用排序,使用P将进程列表按照CPU的使用情况排序,输入q退出. (1)第一行是任务队列信息,显示系统时间.运行时间.当 ...
- Python简单程序设计(计算程序设计(公式)篇)
如题: 解题方式如下:
- WPF多数类概念性注册加自动扫描
在java中springboot的配置应用了自动扫描 @ComponentScan(value = {"com.example", "com.fox"}) 而对 ...
- python结巴分词及词频统计
1 def get_words(txt): 2 seg_list = jieba.cut(txt) 3 c = Counter() 4 for x in seg_list: 5 if len(x) & ...
- 取消掉远程桌面mstsc顶部(侧面)连接栏
在进行mstsc远程桌面连接电脑或者虚拟机的时候,总是会出现一个连接栏.虽然点左边的图钉可以自动隐藏,但是每次鼠标滑到上面的时候,还是会冒出来,这个就有点闹心了. 查了下相关资料,解决了,特写下相关教 ...
- 详解数仓对象设计中序列SEQUENCE原理与应用
本文分享自华为云社区<GaussDB(DWS)对象设计之序列SEQUENCE原理与使用方法介绍>,作者:VV一笑. 1. 前言 适用版本:8.2.1及以上版本 序列SEQUENCE用来生成 ...
- #交互#CF1375F Integer Game
题目 有三堆石子初始石子数分别为\(a,b,c\),可以选择先手还是后手操作, 每次操作形如先手选择一个正整数 \(k\) ,后手自由选择一堆石子加上 \(k\) , 但是不能和上一次操作选择的石堆相 ...
- 掌握 C++ 中 static 关键字的多种使用场景
static是什么 在最开始C中引入了static关键字可以用于修饰变量和函数,后来由于C++引入了class的概念,现在static可以修饰的对象分为以下5种: 成员变量,成员函数,普通函数,局部变 ...
- C#实现的下拉多选框,下拉多选树,多级节点
今天给大家上个硬货,下拉多选框,同时也是下拉多选树,支持父节点跟子节点!该控件是基于Telerik控件封装实现的,所以大家在使用的过程中需要引用Telerik.WinControls.dll.Tele ...
- 三七互娱《斗罗大陆:魂师对决》上线,Network Kit助力玩家即刻畅玩
三七游戏旗下的年度旗舰大作<斗罗大陆:魂师对决>现已开启全平台公测.8月1日,三七互娱技术副总监出席了HMS Core.Sparkle游戏应用创新沙龙,展示了在HMS Core Netwo ...