WPF依赖属性学习
概述WPF 依赖属性(Dependency Property)是 WPF 框架的核心基础设施之一,它扩展了传统 .NET 属性的能力,为 WPF 提供数据绑定、动画、样式、继承值、属性值变更通知等高级功能。
为什么需要设计依赖属性?
因为依赖属性做到了CLR属性没做到的一些事情。
列举几个场景:
1、数据驱动 UI 的动态性需要“可计算的值
在 WPF 里,绑定的值、样式 Setter 的值、触发器的值、动画帧的值,都是事后才知道的,甚至可以在运行时不断切换来源。
CLR 属性:值写死在一个私有字段里,谁最后 set 就留谁。
依赖属性:属性系统先查看“当前这一帧到底是谁最有发言权”,再给出最终值——也就是“值是从外部来的,我只是按优先级算一算”的依赖计算。
2、大规模对象树的内存压力要求“默认值共享
WPF 的控件树随随便便成千上万实例,如果每个 Button 都把 FontSize = 11 存一份 double,内存就爆炸了。
依赖属性把“默认值”压缩到一个静态全局哈希表里,没显式设置的实例,直接查表用同一份值。
3、样式 / 动画 / 绑定 / 继承 / 触发器 / 资源多路输入需要统一的“优先级规则
同一个 Background,可以是:本地值(红),主题样式(蓝),动画(绿),触发器(黄)……
传统属性里谁最后 set 谁赢,根本无法表达这种“多源头分时复用”的复杂策略。
依赖属性为此内置了一套显式的优先级表(动画>本地值>触发器>样式…),系统每次重新评估就行,无需控件开发者自己写状态机。
4、跨父子树的“属性值继承
典型例子:FontSize 设到 Window 上,所有子孙 TextBlock 直接复用该值,但中途随时可以用样式或本地值覆盖。
传统字段存储实现:父级改一次就要递归遍历整棵树;
依赖属性:子元素在取值时惰性向上询问,逻辑/性能都优雅。
学习依赖属性
在创建自定义的时候,创建一个依赖属性的示例如下所示:
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(int), typeof(RatingControl),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, CoerceValue));首先来看看命名,一个CLR属性是Value,依赖属性是ValueProperty,这是一种命名约定,可以很容易将这两个东西关联起来。
依赖属性都是通过DependencyProperty.Register方法注册:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
nameof(Value), // 属性名 Value
typeof(int), // 属性类型
typeof(RatingControl),// 所属类型
new FrameworkPropertyMetadata(
0, // 默认值
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, // 默认为双向绑定
OnValueChanged, // 值发生变更时的回调
CoerceValue) // 强制值回调
);CoerceValue是强制回调:
private static object CoerceValue(DependencyObject d, object baseValue)
{
var ctl = (RatingControl)d;
int v = Math.Max(0, (int)baseValue); // 下限
v = Math.Min(v, ctl.Max); // 上限
return v;
}OnValueChanged是变更回调:
private static void OnValueChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (RatingControl)d;
ctl.UpdateVisualStates();
}作用:值真正改变后通知控件更新 UI。
e 中包含旧值 e.OldValue 与新值 e.NewValue,可进一步比较差异。
生命周期小结(一个赋值的全过程)
代码 / 绑定 / 动画尝试改变 Value。
WPF 调用 CoerceValue 让控件有机会矫正值。
如果矫正后的值与当前存储值相同,流程结束;否则进入下一步。
触发 OnValueChanged → 更新UI。
因为是 BindsTwoWayByDefault,若存在绑定的源(ViewModel),其对应属性也会被同步。
现在大概了解了依赖属性的设计,你可能也听说过“附加属性”与“继承属性”。
其实官方并没有“继承属性”这个称谓,继承属性只是将依赖属性设置成可继承罢了。
要想更好地理解依赖属性的概念,一个很好的方式就是去看WPF的源码,看看在源码中是如何使用的,现在就让我们一起去源码中找找看吧!!
先来看看普通的依赖属性定义:
目前我们接触到了DependencyProperty与DependencyPropertyKey。
DependencyPropertyKey表示只读依赖属性。
这里官方源码将按钮是否按下这个属性设置为了只读依赖属性,为什么官方是这样做的呢?
想象一下一个按钮的 IsPressed 属性。这个属性应该是 true 还是 false,不应该由应用程序的逻辑直接决定(比如,你不应该写 myButton.IsPressed = true; 来“按下”一个按钮)。它的状态应该完全由用户的交互行为(鼠标按下、触摸、键盘空格键等)来驱动。
如果你把它做成一个普通的可以随意读写的属性:
public bool IsPressed { get; set; }那么任何代码都可以修改它,这会破坏按钮的内在逻辑和行为一致性。
如果你把它做成一个普通的只读属性:
private bool _isPressed;
public bool IsPressed { get { return _isPressed; } }虽然外部代码不能修改了,但这样做有几个缺点:
不支持 WPF 高级功能:它不再是一个依赖属性,因此无法享受数据绑定、样式、动画、属性值继承等 WPF 的核心特性。比如,你无法在 XAML 中写一个 Trigger 来在 IsPressed 为 true 时改变按钮的背景色。
缺少变更通知:如果 _isPressed 的值改变了,WPF 的其他部分(比如 UI 渲染系统)不会自动知道。你需要手动实现 INotifyPropertyChanged 接口,这额外增加了复杂性。
为了解决上述问题,WPF 引入了“只读依赖属性” (Read-Only Dependency Property) 。这种属性拥有两全其美的优势:
对外是只读的:保护了属性的完整性,防止外部代码随意篡改。
内部是可读写的:属性的“所有者”可以在特定逻辑下修改其值。
拥有依赖属性的全部特性:支持数据绑定、样式、动画、触发器等。
再来看看附加依赖属性:
Grid.Row是一个很经典的附加依赖属性。
注册附加依赖属性使用的是DependencyProperty.RegisterAttached方法。
附加属性必须提供静态的Get和 Set 方法:
在WPF中一个很经典的可继承依赖属性的例子就是FontSize,让我们来看看它的定义:
使用了FrameworkPropertyMetadataOptions.Inherits。
这个枚举类有以下几个选项:
名称说明None无标志。AffectsMeasure此属性影响测量(Measure)过程。当此属性值改变时,元素需要重新计算其所需大小。AffectsArrange此属性影响布局(Arrange)过程。当此属性值改变时,元素需要重新定位并确定其最终大小。AffectsParentMeasure此属性影响父级的测量过程。当此属性值改变时,其父元素需要重新进行测量。AffectsParentArrange此属性影响父级的布局过程。当此属性值改变时,其父元素需要重新进行布局。AffectsRender此属性影响渲染。当此属性值改变时,元素可能需要部分或完全重绘。Inherits此属性的值可以被子元素继承。OverridesInheritanceBehavior此属性会导致继承和资源查找过程,忽略在查找路径上任何元素 (FE) 设置的 InheritanceBehavior 值。NotDataBindable此属性不支持数据绑定。BindsTwoWayByDefault对此属性的数据绑定默认为双向(Two-Way)模式。Journal在通过 URI 进行日志记录/导航时,此属性的值应该被保存和恢复。SubPropertiesDoNotAffectRender此属性的子属性不会影响渲染。例如,若属性 X 有子属性 Y,则修改 X.Y 不会触发渲染更新。现在只是差不多了解了WPF中的依赖属性的一些概念与使用,要想真正明白依赖属性的设计与实现,还得多研究研究源码。
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除
页:
[1]