WPF 依赖项属性深入解析
依赖项属性(Dependency Property)是 WPF 对传统 .NET Framework 属性的扩展,它专属于 WPF 框架,但使用体验与传统属性相似。几乎所有 WPF 元素提供的属性都是依赖项属性。
1. 定义依赖项属性
依赖项属性必须定义在继承自 DependencyObject 的类上,WPF 中大多数 UI 元素都继承自该类。由于属性信息需要全局共享(例如多个元素共享同一属性的默认值),依赖项属性对象被定义为关联类的静态字段。例如,FrameworkElement 类定义了 Margin 属性,所有继承该类的元素共享此属性,定义方式如下:
public class FrameworkElement : UIElement, ...
{
public static readonly DependencyProperty MarginProperty;
...
}
按照惯例,依赖项属性的命名是在普通属性名后追加 "Property" 后缀,以作区分。
2. 注册依赖项属性
依赖项属性在使用前必须注册,且注册过程需在属性被使用前完成,因此通常在静态构造函数中执行(readonly 关键字确保字段只能在构造中被赋值)。WPF 禁止直接实例化 DependencyProperty 类(其没有公共构造函数),只能通过 DependencyProperty.Register() 方法创建。注册时需提供 FrameworkPropertyMetadata 对象,用于指定依赖项属性支持的服务(如数据绑定、动画等)。
3. 使用依赖项属性
注册后,可通过传统的属性包装器来使用,内部调用 DependencyObject 基类的 SetValue 和 GetValue 方法:
public Thickness Margin
{
set { SetValue(MarginProperty, value); }
get { return (Thickness)GetValue(MarginProperty); }
}
注意:属性包装器中应仅包含 SetValue 和 GetValue 调用,不要添加数据验证或事件处理等额外逻辑,否则可能被 WPF 属性系统绕过。
清除属性值需使用 ClearValue 方法:myElement.ClearValue(FrameworkElement.MarginProperty)
4. 属性值检索优先级
依赖项属性依赖多个值提供者,每个提供者具有不同优先级。WPF 按以下顺序(从低到高)确定最终值:
- 默认值(由
FrameworkPropertyMetadata设置) - 继承值(若设置
Inherits标志,且父元素提供了值) - 主题样式值
- 项目样式值
- 本地值(代码或 XAML 直接设置)
通过直接设置本地值可覆盖所有较低层级。
5. 共享依赖项属性
多个类可共享同一依赖项属性。例如 TextBlock 和 Control 的 FontFamily 属性都指向 TextElement.FontFamilyProperty。该属性在 TextElement 的静态构造函数中注册,其他类通过 AddOwner 方法重用它:
TextBlock.FontFamily = TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));
6. 属性验证与强制回调
不应将验证代码放入属性包装器的 set 中,因为 SetValue 可直接绕过包装器。WPF 提供两种回调机制:
- ValidateValueCallback:接受或拒绝新值,在注册时作为参数传入。必须是静态方法,仅接收对象值参数,无法访问设置属性的对象实例。
- CoerceValueCallback:将新值修改为更合适的值,通过
FrameworkPropertyMetadata设置。可访问设置属性的对象,用于处理属性间的关联约束。
当设置依赖项属性时,执行流程为:
CoerceValueCallback修改或拒绝值(返回DependencyProperty.UnsetValue)ValidateValueCallback验证值合法性- 以上均通过后,触发
PropertyChangedCallback通知其他类
7. 验证回调示例
注册时通过 ValidateValueCallback 验证属性值:
MarginProperty = DependencyProperty.Register(
"Margin",
typeof(Thickness),
typeof(FrameworkElement),
metadata,
new ValidateValueCallback(FrameworkElement.IsMarginValid)
);
private static bool IsMarginValid(object value)
{
// 验证逻辑
return true; // 或 false 拒绝值
}
注意:验证回调是静态的,不能访问对象实例的其他属性。
8. 强制回调示例
通过 FrameworkPropertyMetadata 设置 CoerceValueCallback,处理关联属性约束:
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.CoerceValueCallback = new CoerceValueCallback(CoerceMaximum);
DependencyProperty.Register("Maximum", typeof(double), typeof(RangeBase), metadata);
private static object CoerceMaximum(DependencyObject obj, object value)
{
RangeBase range = (RangeBase)obj;
double proposed = (double)value;
return proposed;
}
强制回调可用于处理属性间相互依赖的情况,例如确保 Maximum 不小于 Minimum。