目录

WPF-依赖属性(Dependency Property) 和 附加属性(Attached Property) 的高级使用

依赖属性(Dependency Property) 和 附加属性(Attached Property) 的高级使用.

一、依赖属性的高级用法 —— PropertyChangedCallback 和 CoerceValueCallback

1. PropertyChangedCallback —— 依赖属性值变化回调

当依赖属性的值发生变化时,你可以定义一个回调函数来响应这个变化。

比如,我们希望在某个属性值改变时执行额外的逻辑(比如:更新 UI、做数据验证、执行动画等)。

如何实现 PropertyChangedCallback
public class MyControl : Control
{
    // 定义一个依赖属性,并添加 PropertyChangedCallback
    public static readonly DependencyProperty MyProperty =
        DependencyProperty.Register(
            "MyProperty",        // 属性名
            typeof(string),      // 属性类型
            typeof(MyControl),   // 控件类型
            new PropertyMetadata("默认值", OnMyPropertyChanged) // 设置回调函数
        );

    // CLR 属性包装器
    public string MyProperty
    {
        get { return (string)GetValue(MyProperty); }
        set { SetValue(MyProperty, value); }
    }

    // PropertyChangedCallback:依赖属性变化时执行的回调函数
    private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (MyControl)d;
        string newValue = (string)e.NewValue;
        // 在这里可以添加逻辑:如更新 UI、做数据验证等
        Console.WriteLine($"MyProperty 从 {e.OldValue} 变更为 {newValue}");
    }
}

解释

  • PropertyChangedCallback:这个回调函数会在依赖属性的值发生变化时被调用。通过 e.NewValuee.OldValue 可以知道新旧值,适合用于执行某些逻辑或更新 UI。
常见应用:
  • 当某个属性值改变时,触发动画、UI 更新、数据验证等。
  • 实现控件的动态变化,比如修改控件的外观或者行为。

2. CoerceValueCallback —— 强制值回调

除了 PropertyChangedCallback,你还可以使用 CoerceValueCallback,来在属性值变更之前进行“强制修正” —— 也就是说,在属性值被设置之前,先对其进行调整或限制,保证值始终符合你的要求。

如何实现 CoerceValueCallback
public class MyControl : Control
{
    // 定义一个依赖属性,并添加 CoerceValueCallback
    public static readonly DependencyProperty MyProperty =
        DependencyProperty.Register(
            "MyProperty",        // 属性名
            typeof(int),         // 属性类型
            typeof(MyControl),   // 控件类型
            new PropertyMetadata(0, null, CoerceMyProperty) // 设置强制值回调
        );

    // CLR 属性包装器
    public int MyProperty
    {
        get { return (int)GetValue(MyProperty); }
        set { SetValue(MyProperty, value); }
    }

    // CoerceValueCallback:强制值回调
    private static object CoerceMyProperty(DependencyObject d, object baseValue)
    {
        // 假设我们希望确保 MyProperty 始终在 0 到 100 之间
        int value = (int)baseValue;
        if (value < 0) return 0;
        if (value > 100) return 100;
        return value;
    }
}

解释

  • CoerceValueCallback:用于强制属性的值在变更前进行修正或约束。在某些情况下,当属性值超出了范围或不符合预期时,可以通过这个回调函数来调整它。
常见应用:
  • 限制输入范围:确保数值属性始终在某个范围内(如年龄不超过 100,温度不低于 0)。
  • 属性的自动修正:比如对于颜色、大小等可以设置最小值和最大值。

二、附加属性的高级用法 —— 为布局系统添加行为

附加属性不仅仅是为控件添加额外的功能,它们在 布局系统 中非常有用,尤其是在实现自定义布局时。最经典的例子就是 GridCanvas 等控件,使用附加属性来控制子元素的位置和排列。

自定义附加属性来实现自定义布局

假设我们想要实现一个 自定义布局容器,并使用附加属性来控制其中元素的排列。

public static class MyLayout
{
    // 定义附加属性:自定义布局的 X 位置
    public static readonly DependencyProperty LayoutXProperty =
        DependencyProperty.RegisterAttached(
            "LayoutX",
            typeof(double),
            typeof(MyLayout),
            new PropertyMetadata(0.0)
        );

    // 定义附加属性:自定义布局的 Y 位置
    public static readonly DependencyProperty LayoutYProperty =
        DependencyProperty.RegisterAttached(
            "LayoutY",
            typeof(double),
            typeof(MyLayout),
            new PropertyMetadata(0.0)
        );

    // 设置 LayoutX 属性
    public static void SetLayoutX(UIElement element, double value)
    {
        element.SetValue(LayoutXProperty, value);
    }

    // 获取 LayoutX 属性
    public static double GetLayoutX(UIElement element)
    {
        return (double)element.GetValue(LayoutXProperty);
    }

    // 设置 LayoutY 属性
    public static void SetLayoutY(UIElement element, double value)
    {
        element.SetValue(LayoutYProperty, value);
    }

    // 获取 LayoutY 属性
    public static double GetLayoutY(UIElement element)
    {
        return (double)element.GetValue(LayoutYProperty);
    }
}

解释

  • LayoutXPropertyLayoutYProperty 是附加属性,用来控制元素在自定义布局中的 XY 位置。

  • 使用这些附加属性,可以在 XAML 中控制元素的布局行为,而不需要直接修改控件的类型或继承控件。

使用自定义布局的 XAML:

<Window xmlns:local="clr-namespace:YourNamespace"
        Title="Custom Layout Example" Height="300" Width="300">
    <Grid>
        <!-- 设置元素位置 -->
        <Button Content="按钮 1" local:MyLayout.LayoutX="50" local:MyLayout.LayoutY="50"/>
        <Button Content="按钮 2" local:MyLayout.LayoutX="150" local:MyLayout.LayoutY="100"/>
    </Grid>
</Window>

常见应用:

  • 自定义布局容器:控制元素的排列和位置。
  • CanvasGrid 控件内附加属性的使用:比如 Canvas.Left, Canvas.Top,或者 Grid.Row, Grid.Column

总结:

特性 依赖属性 附加属性

注册方式 DependencyProperty.Register DependencyProperty.RegisterAttached

用途 用于控件内部的动态属性(支持绑定、动画、样式等) 为控件添加额外的功能,常用于布局系统和外部控件扩展

支持的回调 PropertyChangedCallback, CoerceValueCallback 通常没有回调,主要用于布局和样式扩展

常见示例 绑定、动画、样式、控制属性值变化 Canvas.Left, Grid.Row, CustomLayout