WPF学习:5.依赖属性

  前一章主要介绍了类型扩展和标记转换,这一章主要介绍WPF中一个重要的特性-依赖属性。按照惯例,先贴上示例代码:https://files.cnblogs.com/keylei203/5.BindingDP.zip

一个新的属性系统

  依赖属性的设计思想就是侧重于属性超过方法和事件,能用属性解决的问题解决不使用方法和事件,以往的属性功能太单一,仅仅是提供一个类型的值,WPF提供了一个新的属性类型即依赖属性和与之配套的服务,让它能做方法和事件所能做的事情。

  依赖属性和一般的CLR属性大部分相似,那么这种新的属性系统的优势在哪里呢,下面让我们看看依赖属性和CLR属性的区别。

依赖属性和CLR属性的区别

  CLR属性仅仅是一个private变量的封装,它使用get/set方法获取和设置属性值,因此CLR属性只能给你一个获取和设置属性的调用块,功能非常简单明了的。随着对象的属性越来越多,再加上从对象派生出去的子对象,子对象再生出的“孙子对象”......最终对象运行实例中会有大量的私有成员,每私有成员都要分配内存,占用一定的资源。反过来想,通常我们在使用一个控件/对象时,往往只用到了几个熟悉,大部分属性都是采用默认值,这对于WPF是一种极大的性能损耗。我们再回想一下静态(static)方法或成员,他们的调用不依赖于实例,它是class级别的,不管这个类有多少个实例,静态成员在内存中只占一份,这正是我们想要的。

  依赖属性的功能是比较强大的,依赖属性的思想是基于其它内置类型输入完成属性的计算,这些其它的内置类型包含了样式,主题,系统属性,动画等等。你可以说依赖属性时为了处理WPF的很多内置特性而生的。

  DependencyObject是一个用来建立依赖属性的类,我们可以使用它向属性系统注册一个对象依赖属性,保证我们在任何时候调用它,甚至可以以一般的CLR属性形式封装依赖属性,使用GetValueSetValue去获取和设置属性值。

依赖属性的优势

  事实上,依赖属性相比与CLR属性是非常有优势的,在动手建立一个依赖属性之前,先简单的说明下这些优势。

  1.属性继承:通过属性继承,数中的子对象会沿袭父对象的属性。这里也解释了为什么上级控件对下级控件的自动布局,因为下级控件自动继承了上级控件的相关属性。

  2.数据验证:“变化通知”,属性值发生更改时,数据验证会被自动触发。

  3.动画支持:WPF动画具有在一定范围能变换的能力,定义一个依赖属性,能够支持动画。

  4.样式支持:依赖属性也支持样式。

  5.模板支持:模板定义了元素的整体结构,依赖属性支持模板。

  6.数据绑定:每一个依赖属性在值发生变化时会自动调用INotifyPropertyChanged

  7.回调:可以定义一个回调函数,当属性改变时,回调被调用。

  8.元数据重载:使用PropertyMetaData定义依赖函数的行为。元数据重载可以避免重写定义整个属性。

  9.设计器支持:对WPF设计器的集成支持主要体现在自定义一个空间,如定义一个按钮并为其添加了一个依赖属性,那么在WPF的属性窗口中会显示该项,而不是显示普通属性。

  总之,所有这些特性只在依赖属性中被支持,动画,样式,模板,属性继承等等。加入你在CLR属性中使用这些特征,编译器会报错。

如何定义一个依赖属性

  在实际的编码中,让我们来看看如何定义一个依赖属性。(技巧:vs2008中,只要键入propdp,再连敲二次Tab键,vs就会自动添加一个依赖属性的代码模板)

 1 public static readonly DependencyProperty MyCustomProperty = 
 2 DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1));
 3 public string MyCustom
 4 {
 5     get
 6     {
 7         return this.GetValue(MyCustomProperty) as string;
 8     }
 9     set
10     {
11         this.SetValue(MyCustomProperty, value);
12     }
13 }

  看了上面的代码,一定很奇怪为什么依赖属性时静态的,因为依赖属性时是class级别的,因此你可以说类A有一个属性B,因此属性B对于所有A的对象只有单独的一份,这里也说明了为什么不能直接用txt.Left=xxx来直接赋值,而必须用txt.SetValue(Cabvas.Left,xxx)来处理,因为static成员实例是无法调用的。

  这里定义了一个MyCustom的string类型依赖属性,和普通属性的区别是:必须使用DependencyProperty.Register来注册该属性,而且“属性命名”要以Property为后缀。加入你不遵守这些,在你的程序中可能产生一些功能异常。

  这里需要注意的地方是不能在封装的属性中写你的逻辑代码,因为它不会每次都被调用,它只会调用你内置的GetValueSetValue方法,使用回调callbacks处理这样的工作,

 为属性定义元数据

  定义了一个简单的依赖属性之后,接下来让我们进一步深入,使用PropertyMetaData来给依赖属性增加元数据:

 1 static FrameworkPropertyMetadata propertymetadata = new FrameworkPropertyMetadata("Comes as Default",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | 
 4 FrameworkPropertyMetadataOptions.Journal,new 
 5 PropertyChangedCallback(MyCustom_PropertyChanged),
 6 new CoerceValueCallback(MyCustom_CoerceValue),
 7 false, UpdateSourceTrigger.PropertyChanged);
 8 
 9 public static readonly DependencyProperty MyCustomProperty = 
10 DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1),
11 propertymetadata, new ValidateValueCallback(MyCustom_Validate));
12 
13 private static void MyCustom_PropertyChanged(DependencyObject dobj, 
14 DependencyPropertyChangedEventArgs e)
15 {
16   //To be called whenever the DP is changed.
17   MessageBox.Show(string.Format(
18      "Property changed is fired : OldValue {0} NewValue : {1}", e.OldValue, e.NewValue));
19 }
20 
21 private static object MyCustom_CoerceValue(DependencyObject dobj, object Value)
22 {
23 //called whenever dependency property value is reevaluated. The return value is the
24 //latest value set to the dependency property
25 MessageBox.Show(string.Format("CoerceValue is fired : Value {0}", Value));
26 return Value;
27 }
28 
29 private static bool MyCustom_Validate(object Value)
30 {
31 //Custom validation block which takes in the value of DP
32 //Returns true / false based on success / failure of the validation
33 MessageBox.Show(string.Format("DataValidation is Fired : Value {0}", Value));
34 return true;
35 }
36 
37 public string MyCustom
38 {
39 get
40 {
41 return this.GetValue(MyCustomProperty) as string;
42 }
43 set
44 {
45 this.SetValue(MyCustomProperty, value);
46 }
47 }

  上面显示有些复杂,定义一个FrameworkMetaData,它用来指定依赖属性的默认值,加入没有设定依赖属性的值得话,属性将一直保持默认值,FrameworkPropertyMetDataOption能够评估依赖属性的各种原数据,下面我们一一列举它的选项:

  AffectsMeasure:在对象放置的时候,布局元素调用AffectsMeasure

  AffectsArrange:为布局元素调用AffectsArrange

  AffectsParentMeasure:为父控件调用AffectsMeasure。

  AffectsParentArrange:为父控件调用AffectsArrange

  AffectsRender:当属性值发生改变时,重新渲染控件。

  NotDataBindable:取消数据绑定。

  BindsTwoWayByDefault:默认是单向数据绑定,使用这个可以使变为双向数据绑定。

  Inherits:确保子控件从基类中继承。

  当使用超过一个选项时,你可以使用“|”分隔符隔开。

  PropertyChangedCallback在属性值已经发生改变后调用,CoerceValue在属性值发生改变之前调用,这就意味着CoerceValue调用之后,其返回值会指定给属性。ValidationCoerceValue前调用,这个事件确保传递给属性的值时合法有效的,所以你需要返回一个布尔型,true或者false,如果为false时,运行环境会生成一个错误。因此在你运行了这段代码之后,MessageBox的起点顺序是:

  1.ValidateCallback:需要写入检查合法性的逻辑代码,True接受值,false抛出错误。

  2.CoerceValue:属性的改变依赖与传递的依赖属性。你可以使用CoerceValue方法调用CoerceValueCallback。

  3.PropertyChanged:属性完全改变后调用的方法,你能够得从DependencyPropertyChangedEventArgs中得到OldValueNewValue。

集合依赖属性的注意点

  当你想要将依赖属性组成一个集合时可以使用CollectionType,这在工程中时非常普遍的,一般而言,当你建立了一个依赖属性并且给它定义了一个初值之后,你每一个创建一个实例的时候属性值都可能不再是初值,当你先要建立一个集合依赖属性并且拥有它们自己的默认值之后,你需要指定为集合类型制定值,而不是定义元数据。

 1 public static readonly DependencyPropertyKey ObserverPropertyKey = 
 2 DependencyProperty.RegisterReadOnly("Observer", typeof(ObservableCollection<Button>), 
 3 typeof(MyCustomUC),new FrameworkPropertyMetadata(new ObservableCollection<Button>()));
 4 public static readonly DependencyProperty ObserverProperty = 
 5      ObserverPropertyKey.DependencyProperty;
 6 public ObservableCollection<Button> Observer
 7 {
 8 get
 9 {
10 return (ObservableCollection<Button>)GetValue(ObserverProperty);
11 }
12 }

  在上面的代码中,我们声明了一个DependencyPropertyKey,使用RegisterReadonly方法,ObservableCollection是一个Button的集合。现在假如你要使用这个集合,你会发现当你在用户控件中创建对象的时候,他们中每一个都使用了相同的依赖属性。根据定义,每一个依赖属性根据它的类型分配内存,假如对象2建立了一个依赖属性的实例,它对重写对象1,它会像一个Singleton类一样执行,为了避免这种局面,每当一个新的对象被创建时,你都需要重置这个集合,由于属性时readonly,你需要使用SetValue建立新的实例。

1 public MyCustomUC()
2 {
3             InitializeComponent();
4             SetValue(ObserverPropertyKey, new ObservableCollection<Button>());
5 }

属性继承

  依赖属性支持属性继承。根据定义,在你建立完一个依赖属性之后,使用AddOwner方法,它的所有子控件能够很容易地对继承一个依赖属性。

  每一个依赖属性都有AddOwner方法,它建立了一个与其他依赖属性联系的方法。

 1 public class A :DependencyObject
 2 {
 3 public static readonly DependencyProperty HeightProperty = 
 4    DependencyProperty.Register("Height", typeof(int), typeof(A),
 5 new FrameworkPropertyMetadata(0,
 6 FrameworkPropertyMetadataOptions.Inherits));
 7 
 8 public int Height
 9 {
10 get
11 {
12 return (int)GetValue(HeightProperty);
13 }
14 set
15 {
16 SetValue(HeightProperty, value);
17 }
18 }
19 
20 public B BObject { get; set; }
21 }
22 
23 public class B : DependencyObject
24 {
25 public static readonly DependencyProperty HeightProperty;
26 
27 static B()
28 {
29 HeightProperty = A.HeightProperty.AddOwner(typeof(B), 
30 new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
31 }
32 
33 public int Height
34 {
35 get
36 {
37 return (int)GetValue(HeightProperty);
38 }
39 set
40 {
41 SetValue(HeightProperty, value);
42 }
43 }
44 }

  从上面的代码中可以看出,类B使用AddOwner继承了依赖属性Height。因此当A声明时,假如你指定了A的Height,这个值会自动传递到对象B。

  对象一般的对象也一样,当你一个Window的前景,属性会自动继承到它所有的子元素,这些子控件的前景都变成一样。

更新

附加属性

  附加属性时另外一个有趣的概念。附加属性能够对使一个对象之外的属性附加到对象上,似乎有些困惑,还是看代码吧。

  假如你定义了一个DockPanel,为DockPanel注册一个附加属性:

1 public static readonly DependencyProperty DockProperty = 
2 DependencyProperty.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel),
3  new FrameworkPropertyMetadata(Dock.Left, 
4 new PropertyChangedCallback(DockPanel.OnDockChanged)),
5  new ValidateValueCallback(DockPanel.IsValidDock));

  给DockPanel注册了附加属性之后,任何DockPanel子元素都能得到Dock属性。

 1 public static readonly DependencyProperty IsValuePassedProperty = 
 2 DependencyProperty.RegisterAttached("IsValuePassed", typeof(bool), typeof(Window1),
 3 new FrameworkPropertyMetadata(new PropertyChangedCallback(IsValuePassed_Changed)));
 4 
 5 public static void SetIsValuePassed(DependencyObject obj, bool value)
 6 {
 7 obj.SetValue(IsValuePassedProperty, value);
 8 }
 9 
10 public static bool GetIsValuePassed(DependencyObject obj)
11 {
12 return (bool)obj.GetValue(IsValuePassedProperty);
13 }

 似水无痕:http://www.cnblogs.com/keylei203/

posted @ 2013-03-13 22:21  似水无痕keylei  阅读(2042)  评论(0编辑  收藏  举报