当前位置: 首页 > news >正文

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),          // 属性名 Valuetypeof(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

这个枚举类有以下几个选项:

名称十六进制值十进制值说明
None0x0000无标志。
AffectsMeasure0x0011此属性影响测量(Measure)过程。当此属性值改变时,元素需要重新计算其所需大小。
AffectsArrange0x0022此属性影响布局(Arrange)过程。当此属性值改变时,元素需要重新定位并确定其最终大小。
AffectsParentMeasure0x0044此属性影响父级的测量过程。当此属性值改变时,其父元素需要重新进行测量。
AffectsParentArrange0x0088此属性影响父级的布局过程。当此属性值改变时,其父元素需要重新进行布局。
AffectsRender0x01016此属性影响渲染。当此属性值改变时,元素可能需要部分或完全重绘。
Inherits0x02032此属性的值可以被子元素继承。
OverridesInheritanceBehavior0x04064此属性会导致继承和资源查找过程,忽略在查找路径上任何元素 (FE) 设置的 InheritanceBehavior 值。
NotDataBindable0x080128此属性不支持数据绑定。
BindsTwoWayByDefault0x100256对此属性的数据绑定默认为双向(Two-Way)模式。
Journal0x4001024在通过 URI 进行日志记录/导航时,此属性的值应该被保存和恢复。
SubPropertiesDoNotAffectRender0x8002048此属性的子属性不会影响渲染。例如,若属性 X 有子属性 Y,则修改 X.Y 不会触发渲染更新。

现在只是差不多了解了WPF中的依赖属性的一些概念与使用,要想真正明白依赖属性的设计与实现,还得多研究研究源码。

http://www.dtcms.com/a/398021.html

相关文章:

  • 云原生-高级阶段-利用rsync备份全网服务器数据
  • wordpress建购物网站抚顺营销型网站建设
  • Pythoner 的Flask项目实践-添加Shapefile面数据并展示功能Mapboxgl底图
  • Flutter混合Android开发Release 打包失败GeneratedPluginRegistrant.java,Plugin不存在
  • docker 安装TDengine 并创建新用户
  • 网站推广实施方案珠海网站制作软件
  • 为世界添彩 - WebGL 中的颜色与着色器变量
  • 初识MYSQL —— mysql的安装
  • c回顾 01
  • 【LeetCode 每日一题】3484. 设计电子表格——(解法一)二维数组
  • python+django/flask+springboot实践性教学系统 实训任务发布 学生作业提交 教师评阅管理系统
  • 洞悉未来,智驭不确定性:蒙特卡洛模拟决策模型实践
  • 长宁哪里有做网站优化比较好利润在100万到300万之间税率2021
  • 沈阳网站设计外包广西建设网官网桂建云
  • vscode 插件怎么实现编辑器行号处添加图标标记
  • Git 从零到一:以 Gitee 为例的实战与可视化指南
  • React 标准 SPA 项目 入门学习记录
  • HAProxy 完整指南:简介、负载均衡原理与安装配置
  • 领码课堂 | React 核心组件与高级性能优化全实战指南
  • 涡轮丝杆升降机的丝杆材质有哪些?
  • 前端笔记:vue中 Map、Set之间的使用和区别
  • 中美关系最新消息视频重庆seo优化公司
  • 【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制
  • 英雄联盟视频网站源码做产品设计之前怎么查资料国外网站
  • Vue3-接入飞书H5应用
  • 四川省建设厅网站川北医学院广告网站怎么建设
  • 七彩喜智慧养老:科技向善,让晚年生活绽放“喜”悦之光
  • 模型驱动的 AI Agent架构:亚马逊云科技的Strands框架技术深度解析
  • 【数据结构】——外部排序(K路归并)
  • 【观成科技】活跃黑产团伙“黑猫”攻击武器加密通信分析