WPF依赖属性

1、依赖属性的概念

  Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能。这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。本概述介绍 WPF 属性系统以及依赖项属性的功能,这包括如何在可扩展应用程序标记语言 (XAML) 中和代码中使用现有的依赖项属性。

“依赖项属性的用途在于提供一种方法来基于其他输入的值计算属性值”,这是官方的一句话,理解起来有点费劲。属性本身其实很简单,就是用来封装了字段的,本质上是两个访问器,来达到对字段值的读与写,以及从业务角度保护字段的业务有效性(因为大部分字段都有一定的业务含义,即有业务含义,就有一定范围,比如工资,是个小数的类型,但工资不可能是负数,所以在封装工资字段时,要在set 访问器中作限制)。但依赖项属性不只这么简单了,属性的值不是简单的封装字段,而是根据其他的输入值来决定本属性的值。为什么要这样做?因为在WPF 体系中,只有定义属性为依赖项属性,这个属性才支持样式设置,数据绑定,继承,动画和默认值。也就是这个属性才能具有WPF 中的一些特点。(依赖项属性和依赖属性是一个意思)


      依赖属性(DependencyProperty)是WPF中一个非常重要的概念,一个依赖属性就是一个类型为DependencyProperty的对象。
      我们普通的属性一般是以如下的形式来申明的:

public String SomeProperty
{
get{}
set{}
}

      而依赖属性需要用DependencyProperty的Register静态方法来创建,例如:

public class MyClass
{
public static DependencyProperty MyDependencyProperty = DependencyProperty.Register("MyProperty", typeof(string), typeof(MyClass));
}

      Register方法有三个重载的版本,前三个参数是必须的,第一个是属性的名称,第二个是属性的类型,第三个参数是这个依赖属性的所有者的类型(或这说当前代码所在的类)。其它重载方法的额外参数后面再解释。
      这里需要注意,依赖属性不能像普通属性那样直接赋值,依赖属性不能直接使用(我们无法直接给它赋值,例如 MyDepencyProperty = .....。这样是不可以的)。为了说明依赖属性的概念,举个例子,比如“重量”这个属性,如果我们说“重量是100斤”是没有意义的,只有说“人的重量是100斤”、“车的重量是100斤”、“桌子的重量是100斤”才有意义。同样,直接说“依赖属性A的值是什么”是没有意义的,应该说“对象obj1的依赖属性A的值是什么”、“对象obj2的依赖属性A的值是什么”才有意义。依赖属性不能单独使用,需要与其它对象配合使用。
      2、如何设置依赖属性的值
      下面来说一下,依赖属性的值是如何设置的。在WPF中,所有的对象都是依赖对象,或者说都是直接或者间接的继承自DependencyObject,DependencyObject本身直接继承自Object。DependencyObject是一个抽象类,它有两个个重要的方法:
      ◆GetValue(DependencyProperty property)                     //获取依赖属性的值
      ◆SetValue<T>(DependencyProperty property,T)            //设置依赖属性的值
      正是因为DependencyObject中的这两个方法,几乎所有的WPF对象都可以设置依赖属性的值。
      我们来创建一个Button,设置一下我们上面代码中的MyDependencyProperty依赖对象的值:      

Button Btn = new Button();
Btn.SetValue(MyClass.MyDependencyProperty,"Button的MyDependencyProperty的值");

      现在应该可以理解了,上面的MyClass仅仅是依赖属性MyDependencyProperty的一个所有者,但是所有的DependencyObject都可以使用这个依赖属性。可以这样说,依赖属性就是一个入口,每个对象都可以通过这个入口来存数据或者取数据。所以依赖属性一般都是申明成 public static,而不用每个类实例一份。
      如果MyClass继承自DependencyObject,那么它自己的实例也可以使用MyDependencyProperty这个依赖属性:

public class MyClass:DependencyObject
{
public static DependencyProperty MyDependencyProperty = DependencyProperty.Register("MyProperty", typeof(string), typeof(MyClass));
}
MyClass MyObject = new MyClass();
MyObject.SetValue(MyClass.MyDependencyProperty,"依赖属性的值");

      3、依赖属性的元数据
      元数据就指描述数据的数据。依赖属性的元数据就用来描述依赖属性的,例如描述依赖属性的默认值、依赖属性的值应该满足什么样的的验证条件、依赖属性发生变化后该做出什么样的响应等等。之前说到了如何创建依赖属性,创建需要用到DependencyProperty类的静态方法Register。这个方法有三个重载的版本如下:
      ◆Register(String, Type, Type)
      ◆Register(String, Type, Type, PropertyMetadata)
      ◆Register(String, Type, Type, PropertyMetadata, ValidateValueCallback)
      第一个方法之前已经介绍过,我们之前创建MyDependencyProperty的时候就是用的这个方法。我们现在主要关注后面的两个方法的参数PropertyMetadata和ValidateValueCallback。
      先来说Register方法的第三个可选参数,PropertyMetadata类的一个实例,这个类的构造函数有三个可选参数:
      PropertyMetadata(Object, PropertyChangedCallback, CoerceValueCallback)
      第一个参数就是指属性的默认值,第二个参数是一个委托,当依赖属性值改变时会触发,我们可以在里面做一些处理。第三个参数也是一个委托,当WPF属性系统重新计算依赖项属性值时或者在当前依赖属性所依赖的DependencyObject上显示调用CoerceValue方法时出发,在委托方法里面我们可以强制指定依赖属性的值返回。
      再来看Register方法第四个可选参数ValidateValueCallback,这个也是一个委托,每次赋值是触发,用来验证数据的有效性,在委托方法里面返回True或False表示是否有效。
      下面给出一个完整的例子以及注释:

public class MyDependencyProperty: DependencyObject
{
//申明并指定好相关的元数据
public static readonly DependencyProperty MyDependencyProperty =DependencyProperty.Register("MyDependencyProperty", typeof(string), typeof(MyDependencyProperty), new PropertyMetadata("默认值", MyChangedCallback, MyCoerceCallback), MyValidateCallback);
private static void MyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
      {
//每次依赖属性值改变时,我们把新值和旧值加到一起
            Console.WriteLine(e.OldValue + " " + e.NewValue);
      }
private static object MyCoerceCallback(DependencyObject obj, object o)
      {
//确保长度不超过10
string s = o as string;
if (s.Length > 10)
                  s = s.Substring(0, 10);
return s;
      }
private static bool MyValidateCallback(object value)
      {
//值不能是一些敏感词汇
return value != "色情词汇";
      }
}

      正是因为这些元数据,动画、资源等才能得以在依赖属性上实现,动画、资源等一旦改变相关依赖属性后,相应的效果便很快由PropertyChangedCallback反映出来。
      4、依赖属性和附加属性(Attach Property)的区别
      附加属性其实就是一个依赖属性,是依赖属性的一种应用。区别就在于,使用依赖属性的一方并不拥有该依赖属性。下面举两个例子来说明:
      Canvas是一个常见的布局控件,我们以它的两个依赖属性HeightProperty和LeftProperty为例来说明。

<Canvas x:Name="LayoutCanvas" Height="100">
<Button x:Name="Btn1" Canvas.Left="30"></Button>
</Canvas>

      注意上面的区别,在Button中指定Left必须写成Canvas.Left而不能直接写成Left,因为Left不是Button类的属性。我们看看HeightProperty和LeftProperty在Canvas中的实现有什么不同,下面是反编译Canvas的代码后得到的部分代码片段:

//Left依赖属性的申明
public static readonly DependencyProperty LeftProperty = DependencyProperty.RegisterAttached("Left", typeof(double), typeof(Canvas), new FrameworkPropertyMetadata((double) 1.0 / (double) 0.0, new PropertyChangedCallback(Canvas.OnPositioningChanged)), new ValidateValueCallback(Shape.IsDoubleFiniteOrNaN));
//Top依赖属性的申明
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), _typeofThis, new FrameworkPropertyMetadata((double) 1.0 / (double) 0.0, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(FrameworkElement.OnTransformDirty)), new ValidateValueCallback(FrameworkElement.IsWidthHeightValid));
//一个普通属性,把Height依赖属性进行了一下包装,这样每个实例都有一个普通的Height属性,但是值是通过依赖属性来进行存取的
public double Height
{
get
    {
return (double) base.GetValue(HeightProperty);
    }
set
    {
base.SetValue(HeightProperty, value);
    }
}

      上面可以看到,代码中进行了HeightProperty和LeftProperty两个依赖属性的申明,但是对HeightProperty用普通属性进行了包装(CLR Wrap),而LeftProperty没有。这样就导致Canvas的实例默认都有一个Height属性而没有Left属性。
      为什么不对LeftProperty依赖属性进行包装呢?因为Canvas是容器,它本身不需要这个属性,但是Canvas本身有高度,所以每个Canvas都需要一个Height属性。当然,如果你硬是要在Canvas的实例中去设置LeftProperty的值也是可以的,可以用Canvas的实例方法SetValue(Canvas.LeftProperty,100.0)去做,可以这样做,但是没有任何意义,不会对Canvas的显示产生任何影响,仅仅是会浪费一些存储空间而已。
      为什么不把LeftProperty定义在Button类中而要定义在Canvas类中呢?这个是设计上的原因,这些布局属性不需要子控件知道,只他们放到容器控件中,他们就可以拥有这些属性了,如何布局由容器控件去决定。
      所以,附加属性只是依赖属性的一个特殊应用,使用依赖属性的对象(对象的类)本身并不拥有相关的依赖属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值