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的源码,看看在源码中是如何使用的,现在就让我们一起去源码中找找看吧!!

先来看看普通的依赖属性定义:

目前我们接触到了DependencyPropertyDependencyPropertyKey

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
方法。

附加属性必须提供静态的GetSet方法:

在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中的依赖属性的一些概念与使用,要想真正明白依赖属性的设计与实现,还得多研究研究源码。

展开阅读全文

更新时间:2025-09-07

标签:科技   属性   绑定   样式   触发器   元素   动画   源码   数据   按钮   控件

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020- All Rights Reserved. Powered By 71396.com 闽ICP备11008920号
闽公网安备35020302034903号

Top