博客园  :: 首页  :: 新随笔  :: 联系 :: 管理

"依赖属性"之捅破窗户纸

Posted on 2011-08-18 08:23  codingsilence  阅读(266)  评论(1编辑  收藏  举报
 



依赖属性是WPF中的概念,很多WPF书籍用专门的一章来讲解,初学者经常迷惑,我多次给同事网友解释过。

本文的目的就是想把简单的事情讲清楚,如果你是初学者,欢迎光临。如果你早已熟悉,谢谢光临。


(一)首先我们来看看普通的属性和依赖属性的定义方式:


【普遍属性】



public class MyTextBlock
{
   
private double _fontSize = 12;
   
public double FontSize { get { return _fontSize; } set{ _fontSize = value; } }

}



【依赖属性】



public class MyTextBlock : DependencyObject

{
    public static readonly DependencyProperty FontSizeProperty =
        DependencyProperty.Register("FontSize", typeof(double), typeof(MyTextBlock), 12.0/*缺省值*/);

   
public double FontSize
    {
       
get { return (double)base.GetValue(FontSizeProperty); }
       
set { base.SetValue(FontSizeProperty, value); }
    }
}


第一点:要定义一个static readonly的类型为DependencyPropertyFontSizeProperty字段;



第二点:MyTextBlock要直接或间接继承DependencyObject

第三点:在FontSize属性的get中调用基类(DependencyObject)的GetValue,在set中调用基类的SetValue;

其中第一点,FontSizePropertystatic。这是初学者最容易迷惑的。为什么是static呢?不管有多少MyTextBlock对象,它们的缺省FontSize都是12,属性名都是“FontSize”

所以FontSizeProperty是static 。



(二) 依赖属性有什么好处呢?

首先,节省空间:在一个程序的窗口中会有很多控件,每个控件都有三五十个属性,如果这些属性都是普通属性,那么每个控件就有三五十个字段在内存中,虽然你根本没有设置过它们,但它们依然存在(占内存)

FontSize就是例子,一般你很少设置它。这样对内存是一种浪费。而依赖属性只有你设置了才会在内存中存在,不设置就不会存在。这样就节省了空间,如何节省后面会说到。

再有,依赖属性可以应用动画,双向绑定,Style,还可以从可视化树的祖先中继承值,等等很多好处。




(三) 依赖属性是如何实现这些好处的?

从上面依赖属性的定义方式可以看出,最重要的就是两个类DependencyPropertyDependencyObject,后者有两个重要的方法(GetValue和SetValue),

我们来模拟一下这两个类的实现。



1. 先从简单的来:DependencyProperty类,代码很简单,如下:




public class DependencyProperty

{
    private Type _propertyType;
   
private Type _ownerType;
   
private string _name;
   
private object _defaultValue;

   
public object DefaultValue { get { return _defaultValue; } }
   
public string Name { get { return _name; } }

   
private DependencyProperty(string name, Type propertyType, Type ownerType, object defaultValue)
    {
        _propertyType = _propertyType;
        _ownerType = ownerType;
        _name = name;
        _defaultValue = defaultValue;
    }

   
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
    {
       
if (defaultValue.GetType() != propertyType)
        {
           
throw new Exception(string.Format("the type of defaultValue is not {0}", propertyType.Name));
        }

        DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultValue);
           
       
//再做一些其它的事情,好让dp能获得一些WPF的功能
       
//可能会用到 ownerType
           
       
return dp;
    }
}



说明:在这里,DependencyProperty没做什么事,只是把Name和缺省值保存起来而已。



2. 接下来,模拟一下DependencyObject类的实现,代码不难,如下:



public class DependencyObject

{
    private IDictionary<DependencyProperty, object> _dict = new Dictionary<DependencyProperty, object>();
       
   
public void SetValue(DependencyProperty p, object val)
    {
       
if (_dict.ContainsKey(p))
        {
            _dict[p] = val;
        }
       
else
        {
            _dict.Add(p, val);
        }
    }

   
public object GetValue(DependencyProperty p)
    {
       
//如果被动画控制,返回动画计算值。(可能会用到p.Name)

       
//如果有本地值,返回本地值
        if(_dict.ContainsKey(p))
        {
           
return _dict[p];
        }

       
//如果有Style,则返回Style的值
       
       
//返回从可视化树中继承的值

       
//最后, 返回依赖属性的DefaultValue
        return p.DefaultValue;
    }
}


说明



DependencyObject用了一个字典来存储属性值。如果你从来没设置过MyTextBlock的FontSize,也就从来没调用过DependencyObject的SetValue方法,字典中也就不会有FontSize的值。从而节省了空间(要知道MyTextBlock会有几十个依赖属性)。


DependencyObject的GetValue方法,根据WPF的优先级规则,分别检查动画值,本地值(字典中的值),Style值,如果都没有就返回缺省值。也就是说SetValue方法做了很多事情,从而让依赖属性比普通属性强大了很多


3. 可以写一小段代码验证一下:如果没设置FontSize,则FontSize返回缺省值,代码很难,如下:




class Program

{
    static void Main(string[] args)
    {
       
//不设置txt的FontSize属性
        MyTextBlock txt = new MyTextBlock();
        Console.WriteLine(txt.FontSize);

       
//设置FontSize属性
        txt.FontSize = 18;
        Console.WriteLine(txt.FontSize);
    }
}



最后,上面只是对DependencyPropertyDependencyObject的猜测。


如果你觉得讲得还是罗嗦,那就也写一篇吧。