WPF 依赖属性与依赖对象

在介绍依赖属性之前,我先介绍下属性的历史

属性的历史:

早期C++的类中,只有字段及方法,暴露数据靠的是方法, 但是字段直接暴露会不安全,所以才用方法来暴露,在设置的时候加些约束,在MFC中就是这样的。但是为了访问某一个字段,总有设置及获得两个方法,太过分散,不利于管理。所以在C#中又引入了属性的概念,后来WPF又引入了依赖属性,可以节省实例对内存的开销,还可以通过binding依赖在其他对象上。

注意:字段是每个实例都要占用内存开销,而属性就如同方法(可以反编译查看,其实就是两个方法,这表示属性仅仅是一个语法糖衣)一样,不管是静态方法还是实例方法,只有一个内存开销。如果没有为属性些默认的字段,编译器自己会加一个。

依赖属性

  下面先看看什么是依赖属性,依赖属性的使用方式是什么:

  

 1   /// <summary>
 2     /// MainWindow.xaml 的交互逻辑
 3     /// </summary>
 4     public partial class MainWindow : Window
 5     {
 6         public MainWindow()
 7         {
 8             InitializeComponent();
 9         }
10
11         private void btnClick_Click(object sender, RoutedEventArgs e)
12         {
13             MyDependencyObject myDp = new MyDependencyObject();
14             myDp.SetValue(MyDependencyObject.FlagProperty, this.txt1.Text);
15             txt2.Text = (string)myDp.GetValue(MyDependencyObject.FlagProperty);
16         }
17     }
18
19     public class MyDependencyObject:DependencyObject
20     {
21         public static readonly DependencyProperty FlagProperty =
22             DependencyProperty.Register("Flag", typeof(string), typeof(MyDependencyObject));
23
24     }

从上面的例子中,我们可以知道依赖对象作为依赖属性的宿主,才能形成完整的binding目标被数据所驱动。

  其中DependencyObject是WPF相当底层的一个基类,所有的UI控件都是继承与它。它又是继承与DispatchObject.

  也就是说,所有的UI控件,在WPF中,属性都是依赖属性。

  另外从上面的例子中可以看出来,主要由三个部分构成:

  1)注册依赖属性(还有其他重载方法):

    DependencyProperty.Register(string name, Type propertyType, Type ownerType)

    第一个参数是注册的属性的名称(这个名称跟将来要包装的CLR属性的名称一样),第二个参数是这个属性的返回类型,第三个是这个属性的寄托类的类型。

    这里要注意,这个依赖属性的对象名称一般都比注册的名称多一个Property,这是一种潜规则,虽然也可以为其他的值。

另外注册的依赖属性对象都是public static readonly。

  2)依赖对象的SetValue方法。

    第一个参数是注册的依赖属性的对象名称(带Property后缀的),第二个是要设置的依赖属性的值。

  3)依赖对象的GetValue方法。

    参数就是注册的依赖属性的对象名称(带Property后缀的)。

  说到这里,我们暂时先搁下不谈,我们先看看一个一般的UI控件的属性是什么样子的:

  比如Textbox.Text属性,这个是个CLR属性,那么其跟依赖属性是什么关系了,原来在Text属性的内部,也是调用了SetValue, GetValue方法,

  并且还执行了类型转换(string类型),这样就相当于用这个包装器以实例属性的形式向外界暴露依赖属性,这样一个依赖属性才能成为数据源的path,

  我们再看看Textbox设定绑定的方法,其有一个SetBinding的方法,其实就是在内部调用的BindingOperations.SetBinding的方法,这也看出了微软希望能够

设置绑定的对象时UI对象。我们自己在构造依赖对象的时候,也可以构造一个SetBinding的方法,以方便调用。

  如果直接使用BindingOperations:

MyDependencyObject myDpo = new MyDependencyObject();
         private void DirectlyBinding()
         {
              BindingOperations.SetBinding(myDpo, MyDependencyObject.FlagProperty, new Binding("Text") { Source = txt1 });
              BindingOperations.SetBinding(txt2, TextBox.TextProperty , new Binding("Flag") { Source = myDpo });
         }

    注意第一句SetBinding是把txt1的Text CLR属性绑定到myDpo这个依赖对象的FlagProperty依赖属性上。

    第二句SetBinding是把myDpo的Flag CLR属性绑定到txt2这个依赖对象的TextProperty依赖属性上。

特别是第二个参数千万不要搞错了,是第一个参数中的依赖属性。

    另外这个myDpo对象不能放到局部变量里面,否则是达不到效果的。

    另外还有一点,如果我事先在txt2中输入了字符,在txt1中输入字符是不会更新到txt2中的,不知道为什么?

  从以上我们可以看出,CLR属性其实就是依赖属性的代言人,有没有这个代言人,依赖属性都是存在的。

为什么说依赖属性没有实现INotifyPropertyChanged接口,还可以再属性的值发生改变的时候与之关联的Binding对象依然可以得到通知呢?为什么说依赖属性天生就是合格的数据源呢?

另外注册依赖属性的时候,最后还有第四个参数,这里没有列出来,其究竟能做什么呢?

  依赖属性到底比起CLR属性有何优势,为什么能够节省内存开销,为什么能够通过Binding依赖在其他对象上(winform中不一样也可以吗)?

  我们带着这些疑问,深入挖掘下依赖属性依赖对象的秘密。

深入挖掘:

    首先从注册依赖属性看起,在注册的时候,究竟注册到哪里去了呢?

    通过深入源代码,发现最核心的地方在于一个函数(DependencyProperty的静态方法):

     private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
            FromNameKey key = new FromNameKey(name, ownerType);
            lock (Synchronized)
            {
                if (PropertyFromName.Contains(key))
                {
                    throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered, name, ownerType.Name));
                }
            }
           ....
            // Create property
            DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
         
            // Build key
            lock (Synchronized)
            {
                PropertyFromName[key] = dp;
            }
            ......
            return dp;
        }

其中:  private static Hashtable PropertyFromName = new Hashtable();

  当我们注册一个依赖属性的时候,通过FromNamekey来生成一个hashcode(通过注册的名称异或宿主得到),构造一个依赖属性,并且存到PropertyFroamName这个

哈希表里面,这其中还检查依赖属性是否独一无二。

  在依赖属性的构造函数里面:

   private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
            this._name = name;
            this._propertyType = propertyType;
            this._ownerType = ownerType;
            this._defaultMetadata = defaultMetadata;
            this._validateValueCallback = validateValueCallback;
            DependencyProperty.Flags flags;
            lock (DependencyProperty.Synchronized)
            {
                flags = (DependencyProperty.Flags)DependencyProperty.GetUniqueGlobalIndex(ownerType, name);
                DependencyProperty.RegisteredPropertyList.Add(this);
            }
            if (propertyType.IsValueType)
            {
                flags |= DependencyProperty.Flags.IsValueType;
            }
            if (propertyType == typeof(object))
            {
                flags |= DependencyProperty.Flags.IsObjectType;
            }
            if (typeof(Freezable).IsAssignableFrom(propertyType))
            {
                flags |= DependencyProperty.Flags.IsFreezableType;
            }
            if (propertyType == typeof(string))
            {
                flags |= DependencyProperty.Flags.IsStringType;
            }
            this._packedData = flags;
        }

  其中GetUniqueGlobalIndex()方法,使得我们得到了依赖属性的唯一索引号,然后把依赖属性加到RegisteredPropertyList列表里面,它是一个静态成员;

internal static ItemStructList<DependencyProperty> RegisteredPropertyList = new ItemStructList<DependencyProperty>(768);

  注册完成后,一个依赖属性实例就注册到了一个全局的Hashtable中去了(通过名称和宿主保证唯一性),而每个依赖属性又有唯一的索引号去表示,那么接下来就

是如何使用依赖对象的SetValue和GetValue借助这个依赖属性实例保存及读取值了。

这里自然就要去跟踪依赖对象的GetValue方法了:

    public object GetValue(DependencyProperty dp)
          {
                base.VerifyAccess();
                if (dp == null)
               {
                      throw new ArgumentNullException("dp");
                }
              return this.GetValueEntry(this.LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value;
          }

   通过依赖属性的实例我们就可以得到这个实例对应的值,关键在于最后return的那句话,其中用到了dp.GlobalIndex.

其中DependencyObject有一个数组EffectiveValueEntry[] _effectiveValueEntry,这个变量里面保存了值和索引(就是GlobalIndex),通过dp.GlobalIndex我们就可以取得相应的EffectiveValueEntry,其Value就是我们要找的值。如果数组没有包含这个值,就会返回依赖属性的默认值,是由DefaultMetadata提供。

SetValue方法的奥秘,跟GetValue类似,我们肯定也是把值存进EffectiveValueEntry数组里面。

    说道这里,我举一个例子来说明,假设有一个依赖对象类A申明了10个依赖对象,A中注册了5个依赖属性,另外一个依赖对象类B申明了8个依赖对象,B中注册了4个依赖属性。

    那么在AB都注册完后,DependencyProperty的静态成员PropertyFromName和RegisterPropertyList就有5+4=9个成员。

  在A申明的10个依赖对象中,每个依赖对象都会有EffectiveValueEntry这个集合,那么也就是说每个对象都有存取5个值的能力,也就是说每个对象都可以开5个房间存取值。

  B申明的8个依赖对象也是一样,每个对象都有存取4个值的能力,存取值的索引每个都是一样的,也就是说B1对象和B2对象针对同样的依赖属性其索引是一样的。

  假设A的第一个对象A1设置了3个依赖属性的值,那么这个对象A1的EffectiveValueEntry只有3个成员,其他没有设置依赖属性的值的对象,他们的EffectiveValueEntry是没有成员的。

  在以前如果使用属性,那么总共需要内存开销(假设每个字段消耗1个字节),10*5+8*4 = 82个字节,那么现在因为只设置了一个对象的3个属性值,那么其实就只消耗了3个字节,所以这也就说明了为什么依赖属性可以节省空间的原因了,以时间来换取空间。

总结:

  到目前为止,我们知道了依赖对象与依赖属性是息息相关的,也知道了为什么能够节省空间的原因,所有的UI控件的属性都是依赖属性,所以说,如果我们自己要写控件,依赖属性也是必不可少要写的方面,知道了依赖属性的深层次原理,下次写起来就不会那么费劲了,另外注册的时候最后还有一个参数这里没有解释,相信也不是很难,这里就略过了。例子就不用附带了。

时间: 2024-10-15 16:04:42

WPF 依赖属性与依赖对象的相关文章

WPF系列 —— 控件添加依赖属性

依赖属性的概念,用途 ,如何新建与使用.本文用做一个自定义TimePicker控件来演示WPF的依赖属性的简单应用. 先上TimePicker的一个效果图. 概念 和 用途:依赖属性是对传统.net 属性的一种封装,使一个传统.net属性支持 WPF 中的 数据绑定.动画.样式 等功能. 新建:任意代码代码文件中 ,输入 propdp 再双击tab键.生成如下的代码块. MyProperty: 依赖属性的名称: ownerclass: 当前依赖属性绑定的所有类; new PropertyMeta

WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)

一. 摘要 首先圣殿骑士非常高兴这个系列能得到大家的关注和支持.这个系列从七月份開始到如今才第七篇,上一篇公布是在8月2日,掐指一算有二十多天没有继续更新了,最主要原因一来是想把它写好,二来是由于近期几个月在筹备"云计算之旅"系列,所以一再推迟了公布进度. 之前一直都没有想过要录制视频.基本的原因还是怕自己知识有限,从而误导他人,所曾经几次浪曦和51CTO邀请录制视频,我都以工作忙.公司内部培训须要时间和自己有待提高等理由委婉的拒绝了,说实在的.自己也知道自己还有非常多地方有待提高.还

WPF入门教程系列十一——依赖属性(一)

一.依赖属性基本介绍 本篇开始学习WPF的另一个重要内容依赖属性. 大家都知道WPF带来了很多新的特性,其中一个就是引入了一种新的属性机制--依赖属性.依赖属性出现的目的是用来实现WPF中的样式.自动绑定及实现动画等特性.依赖属性的出现是WPF这种特殊的呈现原理派生出来的,与.NET普通属性不同的是,依赖属性的值是依靠多个提供程序来判断的,并且其具有内建的传递变更通知的能力. 依赖属性基本应用在了WPF的所有需要设置属性的元素.依赖属性根据多个提供对象来决定它的值 (可以是动画.父类元素.绑定.

WPF,Silverlight与XAML读书笔记第七 - WPF新概念之二依赖属性

说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. WPF引入了一种新的属性类型 – 依赖属性.依赖属性用于整个WPF平台,用来实现样式化,自动属性绑定,动画等.详细说即使用属性替代方法和事件处理对象的行为,通过属性驱动来加强系统的行为.如将属性绑定到数据源来驱动用户界面的显示. 依赖属性可以发挥作用的场合如:将一个属性绑定到另一个对象的某属性,要求当被绑定的属性改变时,依赖于那个属性的属性会自动改变(

Windbg调试WPF的依赖属性

?? 我们用wndbg调试时,很多时候需要查看某个控件的依赖属性值. 比如:我们查看DataGridColumnHeader的Content依赖属性   1.我们用到的windbg的命令有:!do, !da -details, .formats  2.利用!do查看依赖对象的成员变量, 找到具体依赖属性的地址 0:000> !do 00000000039a71d8 Name:        System.Windows.Controls.Primitives.DataGridColumnHead

WPF依赖属性相关博客导航

1.一站式WPF--依赖属性(DependencyProperty)一(什么是依赖属性,依赖属性的由来) 2.一站式WPF--依赖属性(DependencyProperty)二(涉及依赖属性的使用) WPF依赖属性相关博客导航

说说WPF的依赖属性

首先,我们先来大概了解一下依赖属性 什么是依赖属性:依赖属性自己没有值,通过依赖别人(如Binding)来获得值. 依赖属性为什么会出现:控件常用字段有限,包装太多属性会占用过高内存,造成浪费.所以用依赖属性,用不着就不用,用得着就用. 怎么声明依赖属性:用public static readonly三个修饰符修饰. 怎么声明实例:使用DependencyProperty.Register方法生成.此方法有三个参数跟四个参数. 怎么操作依赖属性的值:利用依赖对象(Dependency Objec

依赖属性

依赖属性基础   依赖属性是具有借用其他对象的数据的能力,具有依赖属性的对象为依赖对象.WPF所有UI都是依赖对象. 只有依赖属性才能做为Bingding的源或目标. DependencyObject具有GetValue()和SetValue()两个方法. 自定义一个依赖对象 public class Student3: DependencyObject//必须继承DependencyObject才能成为一个依赖对象 { // 定义依赖属性标准格式 public static readonly 

XAML(2) - 依赖属性

3.依赖属性 在用WPF编程时,常常会遇到"依赖属性"这个术语.WPF元素是带有方法,属性和事件的类.WPF元素的几乎每个属性都是依赖属性, 这是什么意思?依赖属性可以依赖其他输入,例如主题和用户喜好.依赖属性与数据绑定,动画,资源和样式一起使用 前面我们说过WPF的体系结构,只有派生自DependencyObject基类的类才能包含依赖属性. (由于DependencyObject类位于层次结构的最高层,所以每个WPF元素都派生自这个基类) 下面写一个类自定义依赖属性 class C