依赖属性是WPF属性系统的核心基础设施,需通过Register注册,支持绑定、动画、样式等;其值按优先级动态解析,标识符必须static readonly且以Property结尾。

依赖属性不是“带绑定的普通属性”,它是 WPF 渲染和数据流底层的基础设施。不理解它的注册机制、值解析顺序和存储模型,就容易在绑定失效、动画卡顿、样式不生效时反复试错。
DependencyProperty.Register 的参数含义与常见误配
注册依赖属性时,DependencyProperty.Register 四个核心参数必须严格匹配使用场景:
-
name:必须是纯 PascalCase 字符串(如"SearchText"),不能带空格或下划线;WPF 内部用它生成哈希键,拼错会导致绑定找不到属性 -
propertyType:必须与包装器 get/set 的返回/参数类型完全一致(比如typeof(string)不能写成typeof(String),虽然编译通过,但某些元数据操作会失败) -
ownerType:必须是当前类本身(typeof(SearchTextBox)),不能是父类或接口;否则触发器、样式查找会跳过该属性 -
typeMetadata:默认值写错最隐蔽——new PropertyMetadata("")对string安全,但对int写成new PropertyMetadata(0)没问题,而对bool?必须用new PropertyMetadata(null),否则运行时报ArgumentException: Default value is not of the same type
依赖属性包装器里为什么不能加逻辑
包装器(即 public 属性)只是 GetValue/SetValue 的薄层代理,任何额外逻辑都会破坏 WPF 的值解析链:
- 在 get 中做转换(如
return (string)GetValue(...) ?? "N/A")会让绑定源无法感知真实值变化,INotifyPropertyChanged通知也失效 - 在 set 中校验并 throw 异常,会拦截 WPF 自己的值覆盖流程(比如动画正在写入值,你的异常直接崩掉渲染线程)
- 需要校验?走
ValidateValueCallback;需要转换?用IValueConverter或绑定表达式;需要响应变更?注册PropertyChangedCallback
值优先级冲突时,谁赢谁输
依赖属性的值从来不是“写进去就固定”,它按固定优先级从高到低动态计算:
- 动画本地值(最高)→ 模板绑定 → 本地
SetValue→ 样式 Setter → 触发器 → 继承值 → 默认值(最低) - 比如你代码里
button.SetValue(Button.WidthProperty, 200.0),但同时有动画正在控制Width,那动画值永远胜出;想强制覆盖?得停掉动画,或改用BeginAnimation带FillBehavior.Stop - 继承值(如
FontFamily)只对特定属性启用,且仅沿视觉树向上找,不是所有依赖属性都支持;查 MSDN 文档看该属性的FrameworkPropertyMetadataOptions.Inherits是否为 true
为什么 DependencyProperty 必须是 static readonly
这不是编码规范,而是运行时约束:
-
static:因为DependencyProperty实例要被整个类型的所有对象共享,存进全局哈希表(PropertyFromName);实例字段会导致每次 new 控件都注册一遍,哈希冲突或内存泄漏 -
readonly:WPF 在首次访问时会缓存该字段的反射信息,后续全靠字段地址查表;如果运行时被改写,后续所有GetValue都会返回 null 或抛InvalidOperationException - 命名必须以
Property结尾(如SearchTextProperty),否则设计器、XAML 编译器无法识别,IntelliSense 不提示,propdp代码段也生成不了正确结构
真正难的不是写对语法,而是理解值从哪来、被谁改、何时变——尤其当多个样式、模板、动画、绑定同时作用在一个属性上时,调试器里看到的值,可能根本不是你 SetValue 那一刻设进去的。


















