zoukankan      html  css  js  c++  java
  • 依赖属性和附加属性2(转载)

    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/

    文章来自-似水无痕:http://www.cnblogs.com/keylei203/
  • 相关阅读:
    不用游标 遍历记录的sql语句
    SQL Server调优的五个步骤(转)
    职业生涯:.NET牛人到底应该知道些什么?
    取数据库表中字段的描述信息
    写在自己工作六年:转载《软件工程师六年心得体会》
    SQL Server常见性能问题的优化(转)
    高性能计数器设计
    SQL Server 2005中设置Reporting Services发布web报表的匿名访问[转]
    大型网站架构演变和知识体系(转)
    Microsoft的优化SQL方法(转)
  • 原文地址:https://www.cnblogs.com/shawnzxx/p/3103169.html
Copyright © 2011-2022 走看看