WPF - 依赖属性
简述
在 WPF 应用程序中,依赖属性是扩展 CLR 属性的特定类型的属性。它利用了 WPF 属性系统中可用的特定功能。
定义依赖属性的类必须继承自 DependencyObject 类。 XAML 中使用的许多 UI 控件类都派生自 DependencyObject 类,它们支持依赖属性,例如Button 类支持 IsMouseOver 依赖属性。
以下 XAML 代码创建一个带有一些属性的按钮。
csharp
<Window x:Class = "WPFDependencyProperty.MainWindow"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local = "clr-namespace:WPFDependencyProperty"
Title = "MainWindow" Height = "350" Width = "604">
<Grid>
<Button Height = "40" Width = "175" Margin = "10" Content = "Dependency Property">
<Button.Style>
<Style TargetType = "{x:Type Button}">
<Style.Triggers>
<Trigger Property = "IsMouseOver" Value = "True">
<Setter Property = "Foreground" Value = "Red" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
XAML 中的 x:Type 标记扩展具有与 C# 中的 typeof() 类似的功能。它用于指定采用对象类型的属性时,例如
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows; // WPF基础命名空间(包含DependencyObject等)
using System.Windows.Controls; // 用户控件相关类(UserControl)
using System.Windows.Data; // 数据绑定支持
using System.Windows.Documents; // 富文本支持
using System.Windows.Input; // 输入处理(键盘、鼠标)
using System.Windows.Media; // 图形和渲染(画笔、画刷)
using System.Windows.Media.Imaging; // 图像处理
using System.Windows.Navigation; // 导航支持
using System.Windows.Shapes; // 基本形状(矩形、椭圆等)
namespace WpfApplication3
{
/// <summary>
/// 自定义用户控件类,继承自UserControl
/// 演示依赖属性的实现和使用模式
/// </summary>
public partial class UserControl1 : UserControl // partial类:XAML文件将定义另一部分
{
/// <summary>
/// 构造函数:初始化控件
/// </summary>
public UserControl1()
{
// 调用InitializeComponent()方法:
// 1. 加载关联的XAML文件(UserControl1.xaml)
// 2. 实例化XAML中定义的UI元素
// 3. 注册事件处理程序
// 4. 初始化x:Name标识的控件(如tbTest)
InitializeComponent();
}
/// <summary>
/// 依赖属性标识符(静态只读字段)
/// 命名惯例:属性名 + "Property"后缀
/// </summary>
public static readonly DependencyProperty SetTextProperty =
// 使用DependencyProperty.Register方法注册依赖属性
DependencyProperty.Register(
"SetText", // 属性名称(字符串)
typeof(string), // 属性类型(System.String)
typeof(UserControl1), // 所有者类型(声明该属性的类)
new PropertyMetadata( // 属性元数据配置
"", // 默认值(空字符串)
new PropertyChangedCallback(OnSetTextChanged) // 属性变更回调方法
)
);
/// <summary>
/// CLR属性包装器(公共接口)
/// 提供对依赖属性的类型安全访问
/// </summary>
public string SetText
{
// 通过GetValue获取依赖属性当前值
// DependencyProperty.GetValue()是WPF属性系统的核心方法
get { return (string)GetValue(SetTextProperty); }
// 通过SetValue设置依赖属性值
// 注意:WPF数据绑定会直接调用此方法,绕过setter中的代码
set { SetValue(SetTextProperty, value); }
}
/// <summary>
/// 静态属性变更回调方法
/// 符合DependencyPropertyChangedCallback委托签名
/// </summary>
/// <param name="d">发生属性变更的DependencyObject实例(这里是UserControl1实例)</param>
/// <param name="e">包含新旧值等信息的变更参数</param>
private static void OnSetTextChanged(
DependencyObject d, // 依赖对象(控件实例)
DependencyPropertyChangedEventArgs e) // 变更事件参数
{
// 将通用DependencyObject转换为具体类型UserControl1
// 这是必要的,因为静态方法没有this引用
UserControl1 userControl = d as UserControl1;
// 空值检查:确保转换成功
if (userControl != null)
{
// 调用实例方法处理实际变更逻辑
userControl.OnSetTextChanged(e);
}
}
/// <summary>
/// 实例级属性变更处理方法
/// 执行实际的UI更新逻辑
/// </summary>
/// <param name="e">包含新旧值的变更参数</param>
private void OnSetTextChanged(DependencyPropertyChangedEventArgs e)
{
// 获取新值并转换为字符串:
// 1. e.NewValue 是Object类型,包含变更后的值
// 2. 使用?.操作符避免空引用异常
// 3. 使用??提供空值回退(空字符串)
string newValue = e.NewValue?.ToString() ?? string.Empty;
// 更新UI元素:
// tbTest 是在XAML中定义的TextBlock控件(通过x:Name="tbTest")
tbTest.Text = newValue;
}
}
}
代码执行流程详解:
-
初始化阶段:
用户控件加载 构造函数 InitializeComponent XAML解析器 内存 new UserControl1() 调用 加载UserControl1.xaml 创建UI元素树 返回控件实例 用户控件加载 构造函数 InitializeComponent XAML解析器 内存
-
属性变更流程:
外部设置属性 CLR包装器 依赖属性系统 静态回调 实例方法 TextBlock 界面 SetText = "Hello" SetValue(SetTextProperty, "Hello") OnSetTextChanged(d, e) userControl.OnSetTextChanged(e) tbTest.Text = "Hello" 更新显示文本 外部设置属性 CLR包装器 依赖属性系统 静态回调 实例方法 TextBlock 界面
关键设计要点解析:
-
依赖属性声明:
DependencyProperty.Register
是WPF属性系统的核心注册方法- 元数据中的
PropertyChangedCallback
指定值变更时的通知机制 - 静态字段命名
SetTextProperty
是WPF的标准约定
-
回调模式设计:
csharp// 静态方法(框架要求) private static void OnSetTextChanged(DependencyObject d, ...) { // 转换对象类型 var control = (UserControl1)d; // 调用实例方法 control.InstanceOnTextChanged(...); } // 实例方法(业务逻辑) private void InstanceOnTextChanged(...) { // 实际UI更新代码 }
这种模式是WPF的标准实践,兼顾了框架要求(静态方法)和面向对象设计(实例方法)
-
XAML依赖:
tbTest
必须在XAML中正确定义:
xaml<UserControl ...> <Grid> <TextBlock x:Name="tbTest" /> </Grid> </UserControl>
- 如果名称不匹配,运行时会抛出
NullReferenceException
-
空值处理:
csharpe.NewValue?.ToString() ?? string.Empty;
使用空条件运算符和空合并运算符确保:
- 当
e.NewValue
为null时不引发异常 - 当
ToString()
返回null时使用空字符串代替
- 当
此实现完整展示了WPF自定义控件的标准模式,是理解依赖属性工作机制的经典范例。