LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

WPF依赖属性学习

freeflydom
2025年9月5日 8:49 本文热度 263

概述

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

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

转自https://www.cnblogs.com/mingupupu/p/19074134


该文章在 2025/9/5 8:49:29 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved