wpf-依赖属性&附加属性

依赖属性

概念

依赖属性(Dependency Property)是WPF框架中的一种特殊属性机制,允许属性没有自己的字段,而是可以通过数据绑定(Binding)从其他对象或数据源获取值。这种机制使得属性值的获取和设置更加灵活,并且能够支持诸如数据绑定、样式设置、动画等多种高级功能。

特点

  • 节省内存‌:依赖属性使用哈希表存储机制,对多个相同控件的相同属性的值只保存一份,从而节约资源。
  • ‌数据绑定‌:依赖属性支持数据绑定,可以方便地实现界面与数据源的同步更新。
  • ‌样式和模板‌:依赖属性使得样式和模板的应用更加方便,可以实现界面的统一管理和动态调整。
  • ‌动画支持‌:依赖属性可以支持动画效果,使得界面更加生动和丰富。
  • ‌元数据重写‌:依赖属性允许在注册时定义元数据,包括默认值、属性变更回调等,提供了更强大的属性控制能力。

使用场景

  • 数据绑定‌:依赖属性支持数据绑定,可以将控件的属性与数据源进行绑定,实现数据的动态更新。例如,可以将一个文本框的Text属性绑定到一个数据对象的属性上,当数据对象的属性值发生变化时,文本框的内容也会自动更新。
  • ‌样式和模板‌:依赖属性使得样式和模板的应用更加方便。例如,可以通过样式设置按钮的背景颜色、字体等属性,而无需为每个按钮单独设置这些属性。
  • ‌动画‌:依赖属性可以支持动画效果。例如,可以使用DoubleAnimation对按钮的宽度进行动画处理,实现按钮宽度的动态变化。

定义依赖属性

定义一个依赖属性通常包括以下几个步骤:

  • ‌继承DependencyObject‌:依赖属性的所有者类必须继承自DependencyObject
  • ‌注册依赖属性‌:使用DependencyProperty.Register静态方法注册依赖属性,并获取一个DependencyProperty类型的标识符。
  • ‌添加属性包装器‌:定义一个普通的C#属性作为依赖属性的包装器,通过调用GetValueSetValue方法来访问和设置依赖属性的值。

快捷键:输入propdp+连按两次tab键。

参数解析:

  1. MyProperty():获取附加属性MyProperty的值。
  2. DependencyProperty.Register():注册依赖属性。有以下参数
  • 参数1:依赖属性名称
  • 参数2:依赖属性的数据类型
  • 参数3:依赖属性所属的类名
  • 参数4:PropertyMetadata,该方法有3个参数,如下所示
参数 备注
defaultValue 依赖属性默认值
propertyChangedCallback 依赖属性值变化回调函数
coerceValueCallback 依赖属性值变化强制回调函数
  • 参数5:ValidateValueCallback:数据验证回调函数。
public delegate bool ValidateValueCallback(object value);
//属性包装器
public int MyProperty
{
  get { return (int)GetValue(MyPropertyProperty); }
  set { SetValue(MyPropertyProperty, value); }
}

//定义依赖属性 
public static readonly DependencyProperty MyPropertyProperty =
  DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0),new ValidateValueCallback(OnValidEventName));

控件与依赖属性的交互

定义依赖属性类AgeDp.cs
 public class AgeDp : Panel
 {
     public int Age
     {
         get { return (int)GetValue(AgeProperty); }
         set { SetValue(AgeProperty, value); }
     }

     // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty AgeProperty =
         DependencyProperty.Register("Age", typeof(int), typeof(AgeDp), new PropertyMetadata(0));
 }
绑定依赖属性

创建窗口文件,并绑定依赖属性Age。

 <Grid>
     <local:AgeDp
         Width="300"
         Height="100"
         Age="18"
         Background="Red" />
 </Grid>
依赖属性值改变回调

new PropertyMetadata方法的第二个参数即指定依赖属性值改变回调的回调函数。

界面上的值18是实例化完成之后再赋的值,值的整个变化过程分为三个阶段。

第一阶段:旧值0,新值20。

第二阶段:旧值20,新值21。

第三阶段:旧值21,新值18,这个18就是界面上赋的值。

 public class AgeDp : Panel
 {
     public int Age
     {
         get { return (int)GetValue(AgeProperty); }
         set { SetValue(AgeProperty, value); }
     }

     // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty AgeProperty =
         DependencyProperty.Register("Age", typeof(int), typeof(AgeDp), new PropertyMetadata(0, OnAgeChanged));

     /// <summary>
     /// 依赖属性值改变回调
     /// </summary>
     /// <param name="d"></param>
     /// <param name="e"></param>
     private static void OnAgeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
          //获取旧值和新值
          int oldVal = (int)e.OldValue;
          int newVal = (int)e.NewValue;
     }
     public AgeDp()
     {
        Age = 20;
        Age = 21;
     }
 }
依赖属性验证回调

DependencyProperty.Register方法的第五个参数即指定依赖属性值改变验证回调的回调函数。

先调用验证回调,验证通过后,才调用属性变化回调。但是如果新旧值相同时,不触发属性变化回调。

public class AgeDp : Panel
{
    public int Age
    {
        get { return (int)GetValue(AgeProperty); }
        set { SetValue(AgeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AgeProperty =
        DependencyProperty.Register("Age", typeof(int), typeof(AgeDp), new PropertyMetadata(0, OnAgeChanged), new ValidateValueCallback(OnValidAge));

    /// <summary>
    /// 依赖属性值修改验证回调
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private static bool OnValidAge(object value)
    {
        int age = (int)value;
        return age > 120 ? false : true;
    }

    /// <summary>
    /// 依赖属性值改变回调
    /// </summary>
    /// <param name="d"></param>
    /// <param name="e"></param>
    private static void OnAgeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //获取旧值和新值
        int oldVal = (int)e.OldValue;
        int newVal = (int)e.NewValue;
    }

    public AgeDp()
    {
        Age = 20;
        Age = 21;
    }
}

当前端绑定数据年龄值超过120时,会导致验证失败,程序抛出异常。

依赖属性强制回调

new PropertyMetadata方法的第三个参数即指定依赖属性值改变强制回调的回调函数。

先走验证回调,再走强制回调,最后走属性变化回调。

界面上的值18是实例化完成之后再赋的值,值的整个变化过程分为四个阶段。

第一阶段:旧值0,新值20。

第二阶段:旧值20,新值21。

第三阶段:旧值21,新值120,虽然构造函数赋值是121,但121经过验证回调判断不符合逻辑,强制赋值为120。

第四阶段:旧值120,新值18。

    public class AgeDp : Panel
    {
        public int Age
        {
            get { return (int)GetValue(AgeProperty); }
            set { SetValue(AgeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AgeProperty =
            DependencyProperty.Register("Age", typeof(int), typeof(AgeDp), new PropertyMetadata(0, OnAgeChanged, OnAgeCoerceValueChanged), new ValidateValueCallback(OnValidAge));

        /// <summary>
        /// 依赖属性值强制回调
        /// </summary>
        /// <param name="d"></param>
        /// <param name="baseValue"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private static object OnAgeCoerceValueChanged(DependencyObject d, object baseValue)
        {
            int age = (int)baseValue;
            return age > 120 ? 120 : age;
        }

        /// <summary>
        /// 依赖属性值修改验证回调
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private static bool OnValidAge(object value)
        {
            //int age = (int)value;
            //return age > 120 ? false : true;
            return true;
        }

        /// <summary>
        /// 依赖属性值改变回调
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnAgeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //获取旧值和新值
            int oldVal = (int)e.OldValue;
            int newVal = (int)e.NewValue;
        }

        public AgeDp()
        {
            Age = 20;
            Age = 21;
            Age = 121;
        }
    }
<Grid>
    <local:AgeDp
        Width="300"
        Height="100"
        Age="18"
        Background="Red" />
</Grid>

依赖属性继承

概念

依赖属性继承是指依赖属性的值可以在元素树中从父元素传播到子元素。这种机制允许子元素从最近的父元素获取特定属性的值。

WPF 属性系统默认不启用属性值继承,除非在依赖属性元数据中专门启用,否则值继承处于非活动状态。 即使启用了属性值继承,子元素也仅会在缺少更高优先级值的情况下继承属性值。

属性继承机制
  1. ‌依赖属性‌:
    • 属性继承主要适用于依赖属性。依赖属性是WPF中的一种特殊属性,它们可以在运行时动态地获取和设置值。
    • 依赖属性具有元数据,这些元数据可以指定属性是否支持继承。
  2. ‌属性值继承‌:
    • 当一个依赖属性在元数据中启用了继承选项时,其子元素将能够继承该属性的值。
    • 继承的值具有较低的优先级,如果子元素具有更高优先级的值(如本地设置值、样式、模板或数据绑定),则这些值将覆盖继承的值。
  3. ‌元素树‌:
    • 在WPF中,元素以树形结构组织,称为元素树。属性继承沿着元素树从父元素传播到子元素。
    • 继承可以跨代进行,即一个父元素的属性值可以传播到其多级子元素。
属性继承案例
<Canvas x:Name="canvas1" Grid.Column="0" Margin="20" Background="Orange" AllowDrop="True">
    <StackPanel Name="stackPanel1" Margin="20" Background="Green">
        <Label Name="label1" Margin="20" Height="40" Width="40" Background="Blue"/>
    </StackPanel>
</Canvas>

在上面的XAML代码中,Canvas元素具有一个AllowDrop属性,其值设置为True。由于AllowDrop是一个支持继承的依赖属性,因此StackPanelLabel子元素将继承这个值,它们的AllowDrop属性也将被设置为True

实现自定义依赖属性继承

下面将演示子元素如何继承父元素的依赖属性CustomName。

  1. 定义父元素依赖属性类NameDp.cs,创建了一个名为CustomName的自定义依赖属性,并在注册时启用了继承选项。
public class NameDp : Panel
{
    public string CustomName
    {
        get { return (string)GetValue(CustomNameProperty); }
        set { SetValue(CustomNameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for NameProperty.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CustomNameProperty =
        DependencyProperty.Register("CustomName", typeof(string), typeof(NameDp), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.Inherits));
}
  1. 定义子元素控件类DpInherit.cs,使用DependencyProperty.AddOwner方法允许子类共享父类同一个依赖属性。
public class DpInherit:Control
{
    public string CustomName
    {
        get { return (string)GetValue(CustomNameProperty); }
        set { SetValue(CustomNameProperty, value); }
    }

    // Using a DependencyProperty as the backing store for CustomName.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CustomNameProperty =
       NameDp.CustomNameProperty.AddOwner(typeof(DpInherit),new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.Inherits));
}
  1. 控件引入依赖属性类及继承类,添加按钮测试。
 <StackPanel>
     <local:NameDp
         Width="300"
         Height="100"
         Background="Red"
         CustomName="张三">
         <local:DpInherit x:Name="testDpInherit" />
     </local:NameDp>
     <Button
         Width="120"
         Click="Button_Click"
         Content="测试依赖属性继承" />
 </StackPanel>
  1. 按钮后台事件中从继承控件类中获取父类的依赖属性值。
 private void Button_Click(object sender, RoutedEventArgs e)
 {
     string CustomName = testDpInherit.CustomName;
 }

附加属性

概念

附加属性是一种特殊的依赖属性,不同之处在于附加属性被应用到的类并非定义附加属性的那个类,它可以附加在不同的元素上,而不是作为那个元素的一部分,它允许为派生自DependencyObject的任何XAML元素设置额外的属性/值对,即使该元素未在其对象模型中定义这些额外的属性。附加属性是一种XAML概念,通常不具有常规的属性包装器,而是作为依赖属性实现。它们支持依赖属性的概念,例如来自元数据的默认值等。‌

特点

  1. 特殊的依赖属性‌:附加属性是一种没有常规属性包装器的依赖属性的专用形式。
  2. ‌全局访问‌:附加属性可以供全局访问,这意味着它们可以在应用程序的任何地方使用。
  3. ‌绑定支持‌:由于附加属性是依赖属性,因此支持数据绑定,这使得它们在UI开发中非常有用。

使用场景

当控件自带的属性不满足我们的要求时,可以使用附加属性,常用于UI元素的自定义和布局控制。例如,DockPanel的Dock属性就是一个附加属性,它允许子元素指定如何在父容器中停靠。此外,Grid面板的RowDefinition和ColumnDefinition也是附加属性,用于定义网格的行和列。

快速生成附加属性

快捷键:输入propa+连按两次tab键。

参数解析:

  1. GetMyProperty():获取附加属性MyProperty的值。
  2. SetMyProperty():设置附加属性MyProperty的值。
  3. DependencyProperty.RegisterAttached():注册附加属性。有以下参数
  • 参数1:附加属性名称
  • 参数2:附加属性的数据类型
  • 参数3:附加属性所属的类名
  • 参数4:PropertyMetadata,该方法有3个参数,如下所示
参数 备注
defaultValue 附加属性默认值
propertyChangedCallback 附加属性值变化回调函数
coerceValueCallback 附加属性值变化强制回调函数
  • 参数5:ValidateValueCallback:数据验证回调函数。
public delegate bool ValidateValueCallback(object value);
public static int GetMyProperty(DependencyObject obj)
{
   return (int)obj.GetValue(MyPropertyProperty);
}

public static void SetMyProperty(DependencyObject obj, int value)
{
   obj.SetValue(MyPropertyProperty, value);
}

// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.RegisterAttached("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));

控件与附加属性的交互

定义附加属性类PasswordEx.cs

使用代码片段propa+两次Tab键可快速注册一个附加属性。

这里修改下附加属性的名称、方法名、数据类型。

  public static string GetCustomPwd(DependencyObject obj)
  {
      return (string)obj.GetValue(CustomPwdProperty);
  }

  public static void SetCustomPwd(DependencyObject obj, string value)
  {
      obj.SetValue(CustomPwdProperty, value);
  }

  // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
  public static readonly DependencyProperty CustomPwdProperty =
      DependencyProperty.RegisterAttached("CustomPwd", typeof(string), typeof(PasswordEx), new PropertyMetadata(""));
控件绑定附加属性

创建窗口文件,引入PasswordBox控件,并绑定附加属性CustomPwd。

 <Grid>
     <PasswordBox
         x:Name="MyPassword"
         Width="100"
         Height="30"
         local:PasswordEx.CustomPwd="" />
 </Grid>
附加属性值改变时设置控件属性值

添加属性变化的回调函数OnCustomPwdValueChanged。

  public static string GetCustomPwd(DependencyObject obj)
  {
      return (string)obj.GetValue(CustomPwdProperty);
  }

  public static void SetCustomPwd(DependencyObject obj, string value)
  {
      obj.SetValue(CustomPwdProperty, value);
  }

  // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
  public static readonly DependencyProperty CustomPwdProperty =
      DependencyProperty.RegisterAttached("CustomPwd", typeof(string), typeof(PasswordEx), new PropertyMetadata("",OnCustomPwdValueChanged));

    /// <summary>
    /// 附加属性值发生改变的回调事件
    /// </summary>
    /// <param name="d"></param>
    /// <param name="e"></param>
    private static void OnCustomPwdValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        PasswordBox passwordBox = d as PasswordBox;
        if (passwordBox != null)
        {
            passwordBox.Password = (string)e.NewValue;
        }
    }
 <Grid>
     <PasswordBox
         x:Name="MyPassword"
         Width="100"
         Height="30"
         local:PasswordEx.CustomPwd="123" />
 </Grid>
控件值改变时设置附加属性值

当IsDefine设置为True时,则订阅PasswordBox的PasswordChanged事件,反之则取消订阅。

当PasswordBox的值发生改变时执行OnPasswordChange事件,将输入的值通过方法SetCustomPwd赋值给附加属性。

 public static string GetCustomPwd(DependencyObject obj)
 {
     return (string)obj.GetValue(CustomPwdProperty);
 }

 public static void SetCustomPwd(DependencyObject obj, string value)
 {
     obj.SetValue(CustomPwdProperty, value);
 }

 // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
 public static readonly DependencyProperty CustomPwdProperty =
     DependencyProperty.RegisterAttached("CustomPwd", typeof(string), typeof(PasswordEx), new PropertyMetadata("", OnCustomPwdValueChanged));

 /// <summary>
 /// 附加属性值发生改变的回调事件
 /// </summary>
 /// <param name="d"></param>
 /// <param name="e"></param>
 private static void OnCustomPwdValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 {
     PasswordBox passwordBox = d as PasswordBox;
     if (passwordBox != null)
     {
         passwordBox.Password = (string)e.NewValue;
     }
 }



 public static bool GetIsDefine(DependencyObject obj)
 {
     return (bool)obj.GetValue(IsDefineProperty);
 }

 public static void SetIsDefine(DependencyObject obj, bool value)
 {
     obj.SetValue(IsDefineProperty, value);
 }

 // Using a DependencyProperty as the backing store for IsDefine.  This enables animation, styling, binding, etc...
 public static readonly DependencyProperty IsDefineProperty =
     DependencyProperty.RegisterAttached("IsDefine", typeof(bool), typeof(bool), new PropertyMetadata(false,OnIsDefineChanged));

 private static void OnIsDefineChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 {
     PasswordBox passwordBox = d as PasswordBox;
     if (passwordBox != null)
     {
         if ((bool)e.NewValue)
         {
             passwordBox.PasswordChanged += OnPasswordChange;
         }
         else
         {
             passwordBox.PasswordChanged -= OnPasswordChange;
         }
     }
 }

 /// <summary>
 /// 将Password的值赋值给附加属性CustomPwd
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private static void OnPasswordChange(object sender, RoutedEventArgs e)
 {
     PasswordBox passwordBox = sender as PasswordBox;
     SetCustomPwd(passwordBox, passwordBox.Password);
 }
<Grid>
<PasswordBox
  x:Name="MyPassword"
      Width="100"
      Height="30"
      local:PasswordEx.CustomPwd="{Binding CustomPwd, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
          local:PasswordEx.IsDefine="True" />
</Grid>
posted @   相遇就是有缘  阅读(372)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
  1. 1 我记得 赵雷
  2. 2 北京东路的日子 汪源
  3. 3 把回忆拼好给你 王贰浪
把回忆拼好给你 - 王贰浪
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

Not available

点击右上角即可分享
微信分享提示