一文读懂WPF系列之依赖属性与附加属性

依赖属性与附加属性

依赖属性

对比C#属性

首先传统CLR的属性是什么,它们由自动生成的 getter 和 setter 方法管理。例如:

javascript 复制代码
public class Person
{
    public string Name { get; set; }
}

优点:简单、直接,适用于不需要高级功能的场景。

缺点:它的值是存储在对象的字段中,无法进行数据绑定、样式、动画等功能

WPF依赖属性(Dependency Properties)

那么在WPF中 属性的绑定赋值有很多复杂使用 比如数据绑定 样式绑定等高级功能,所以要有适配WPF技术中的 高级属性使用 也就是 依赖属性

  1. 定义与核心机制
    依赖属性是 WPF 中基于 DependencyObject 类实现的一种特殊属性系统。它通过全局哈希表管理属性值,支持动态来源(如绑定、动画、样式等),并允许属性值继承和优先级控制。例如,Button 的 Background 属性可通过多种方式赋值(如直接设置、绑定数据或应用样式)
  2. 核心特点
    内存优化高效存储:依赖属性通过哈希表存储值,相同控件的相同属性仅保存一份默认值,减少内存消耗。
    动态绑定支持:支持与数据源绑定,实现 UI 与业务逻辑的实时同步。
    值继承与优先级:子控件可继承父控件的属性值(如 FontSize),且不同来源的值按优先级生效(如本地设置 > 样式 > 继承值)。
    变更通知:通过 PropertyChangedCallback 回调响应属性值变化,常用于触发界面更新

优先级计算与值决策​​

既然依赖属性 应对高级使用 多种动态绑定支持 显示值在控件上最终呈现一个值 那肯定有优先级

优先级从高到低依次为:

  1. 动画值(如正在执行的动画效果)。
  2. 本地值(通过 SetValue 或XAML直接设置的值)。
  3. 绑定值(数据绑定的结果)。
  4. 样式与模板(通过 Style 或 ControlTemplate 设置的值)。
  5. 继承值(从父元素继承的值,如 FontSize)。
  6. 默认值(通过 PropertyMetadata 定义的默认值)

回调与验证机制​​

依赖属性支持通过回调函数实现动态控制和验证:

  1. 属性变更回调(PropertyChangedCallback):在值变化时触发,用于更新UI或执行逻辑(如重绘控件)。
  2. 强制值回调(CoerceValueCallback):强制调整值(例如限制数值范围),优先级高于动画。
  3. 验证回调(ValidateValueCallback):验证值的合法性,若无效则抛出异常

WPF 自带的依赖属性

WPF 中绝大多数控件的属性都是依赖属性,它们通过 DependencyProperty 实现,支持动态绑定、动画、样式等特性。以下是常见的​​内置依赖属性​​分类及示例:

  1. 布局与外观
    Width/Height:控件的尺寸(如 Button 的宽高)
    Background/Foreground:背景色和前景色(如 TextBox 的背景色)
    FontSize/FontFamily:字体样式(支持继承父容器的字体设置)
  2. 行为与交互
    IsEnabled:控件是否可用(如禁用按钮)
    Visibility:控件的可见性(支持 Collapsed、Hidden 等状态)
    Command:绑定命令(如 Button 的点击事件绑定 MVVM 命令)
  3. 数据绑定与模板
    ItemsSource:列表控件的数据源(如 ListBox 的绑定集合)
    Content:内容属性(如 Label 的显示文本)
    特点:这些属性通过全局哈希表存储,仅保存非默认值,减少内存占用

自定义依赖属性

这个会结合 自定义控件文章 来讲解

这里就先给个 示例

主要步骤就是 定义控件 注册依赖属性DependencyProperty 包装clr 定义样式 和 布局使用

javascript 复制代码
public class NumericBox : Control
{
    // 注册依赖属性(含验证)
    public static readonly DependencyProperty ValueProperty = 
        DependencyProperty.Register(
            "Value", 
            typeof(double), 
            typeof(NumericBox),
            new FrameworkPropertyMetadata(0.0, 
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                null, 
                CoerceValue
            ),
            ValidateValue
        );

    // CLR 包装器
    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    // 强制值回调(限制范围)
    private static object CoerceValue(DependencyObject d, object value)
    {
        return Math.Max(0, (double)value); // 确保值 ≥ 0
    }

    // 验证回调(拒绝非法值)
    private static bool ValidateValue(object value)
    {
        return value is double && (double)value <= 100; // 最大值 100
    }
}

  <!-- Window.Resources中自定义样式 -->
  <Style TargetType="{x:Type local:NumericBox}">
      <Setter Property="Template">
          <Setter.Value>
              <ControlTemplate TargetType="{x:Type local:NumericBox}">
                  <!-- 模板内容(如文本框、增减按钮等) -->
                  <TextBlock Text="{Binding Path=Value, RelativeSource={RelativeSource AncestorType={x:Type local:NumericBox}}}"/>
              </ControlTemplate>
          </Setter.Value>
      </Setter>
  </Style>
  
<!-- 使用的布局地方local 使用 -->
<local:NumericBox Width="120" Margin="5,0" Value="{Binding dataItem.Id} />

附加属性

本质与定义​​

  1. 附加属性(Attached Properties)是一种特殊类型的依赖属性,允许将属性"附加"到其他非宿主类的对象上。例如,Grid.Row 属性由 Grid 类定义,但可附加到任何子控件上,用于指定其在网格中的行位置。
  2. 实现方式:通过 DependencyProperty.RegisterAttached 静态方法注册,并定义 Get 和 Set 静态方法作为访问器。
  3. 核心特点:
    跨控件扩展:无需修改目标控件源码即可添加新属性(如为 TextBox 添加水印提示)。
    全局性:可在任何继承自 DependencyObject 的对象上使用。

与依赖属性的区别​​

​​宿主差异​​:依赖属性定义在宿主类内部(如自定义控件的属性),而附加属性定义在外部类并附加到其他控件。

​​注册方法​​:依赖属性使用 Register,附加属性使用 RegisterAttached。

​​访问方式​​:附加属性通过静态 Get/Set 方法访问,依赖属性通过 CLR 包装器直接访问

附加属性的典型应用场景

  1. 布局控制
    网格布局:Grid.Row 和 Grid.Column 属性由 Grid 类定义,附加到子元素以指定位置。
    画布定位:Canvas.Left 和 Canvas.Top 控制子元素在画布中的坐标。
  2. 行为扩展
    验证逻辑:为 TextBox 添加输入验证或水印提示。
    动态样式:通过附加属性触发控件样式的动态变化(如高亮选中项)。
  3. 服务类场景
    跨组件通信:定义服务类,通过附加属性传递全局状态(如用户权限或主题颜色)

自定义附加属性

也会结合自定义控件文章来讲解

这里就先给个 示例

主要步骤就是 定义控件 注册依赖属性DependencyProperty 包装clr 定义样式 和 布局使用

javascript 复制代码
public static class GridHelper
{
    public static readonly DependencyProperty ShowBorderProperty = 
        DependencyProperty.RegisterAttached("ShowBorder", typeof(bool), typeof(GridHelper), 
            new PropertyMetadata(false, OnShowBorderChanged));

    public static bool GetShowBorder(DependencyObject obj) => (bool)obj.GetValue(ShowBorderProperty);
    public static void SetShowBorder(DependencyObject obj, bool value) => obj.SetValue(ShowBorderProperty, value);

    private static void OnShowBorderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Grid grid && (bool)e.NewValue)
        {
            // 动态为每个子元素添加边框
            foreach (var child in grid.Children.OfType<FrameworkElement>())
            {
                var border = new Border { BorderBrush = Brushes.Gray, BorderThickness = new Thickness(1) };
                Grid.SetRow(border, Grid.GetRow(child));
                Grid.SetColumn(border, Grid.GetColumn(child));
                grid.Children.Add(border);
            }
        }
    }
}

<Grid local:GridHelper.ShowBorder="True">
    <Grid.RowDefinitions>...</Grid.RowDefinitions>
    <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>
    <Button Grid.Row="0" Grid.Column="0" Content="按钮" />
</Grid>

注意事项

  1. 命名规范
    附加属性名称以 Property 结尾(如 ShowBorderProperty)。
    Get/Set 方法需与属性名严格对应(如 GetShowBorder)。
  2. 性能优化
    避免在回调函数中频繁操作 UI 元素,优先使用数据绑定。
    合理设置默认值以减少初始化开销。
  3. 调试技巧
    命名空间检查:确保 XAML 中正确引入附加属性所在的命名空间。
    模板绑定:在自定义控件模板中使用 TemplateBinding 关联附加属性

属性对比

小结

  1. WPF依赖属性的访问机制通过全局存储、优先级计算和动态回调实现了高效灵活的特性。其核心优势在于支持数据绑定、动画、样式继承等复杂场景,同时通过优化存储和计算逻辑保证了性能
  2. 附加属性是 WPF 实现灵活 UI 扩展的核心机制,其核心价值在于解耦功能与控件,支持跨组件通信、动态布局等复杂场景。通过合理设计,开发者可以显著提升代码复用性和可维护性
相关推荐
FuckPatience3 天前
WPF 具有跨线程功能的UI元素
wpf
诗仙&李白3 天前
HEFrame.WpfUI :一个现代化的 开源 WPF UI库
ui·开源·wpf
He BianGu4 天前
【笔记】在WPF中Binding里的详细功能介绍
笔记·wpf
He BianGu4 天前
【笔记】在WPF中 BulletDecorator 的功能、使用方式并对比 HeaderedContentControl 与常见 Panel 布局的区别
笔记·wpf
123梦野4 天前
WPF——效果和可视化对象
wpf
He BianGu5 天前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
时光追逐者5 天前
一款专门为 WPF 打造的开源 Office 风格用户界面控件库
ui·开源·c#·.net·wpf
He BianGu5 天前
【笔记】介绍 WPF XAML 中 Binding 的 StringFormat详细功能
笔记·wpf
Rotion_深6 天前
C# WPF使用线程池运行Action方法
c#·wpf·线程池
攻城狮CSU6 天前
WPF 深入系列.2.布局系统.尺寸属性
wpf