(原创)3.1 附加属性原理

来源:互联网 发布:3dmax软件简介 编辑:程序博客网 时间:2024/05/24 04:39

附加依赖属性:(Ramoras Pattern)之依赖属性
1 附加属性

1.1 Register和RegisterAttach的区别

1.1.1 当给依赖属性赋值的时候,是如何触发PropertyMetaData中的PropertyChanged 事件

1.1.1.1 获取依赖属性的元数据GetMetadata(DependencyObject dp)

        public void SetValue(DependencyProperty dp, object value)        {            base.VerifyAccess();            PropertyMetadata metadata = this.SetupPropertyChange(dp);//@1             this.SetValueCommon(dp, value, metadata, false, false, OperationType.Unknown, false);        }
        private PropertyMetadata SetupPropertyChange(DependencyProperty dp)        {            if (dp == null)            {                throw new ArgumentNullException("dp");            }            if (dp.ReadOnly)            {                throw new InvalidOperationException(MS.Internal.WindowsBase.SR.Get("ReadOnlyChangeNotAllowed", new object[] { dp.Name }));            }            return dp.GetMetadata(this.DependencyObjectType);//@2获取当前的依赖属性的元数据        }
        public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType)        {            if (dependencyObjectType != null)            {                int num2;                object obj2;                int index = this._metadataMap.Count - 1;                if (index < 0)                {                    return this._defaultMetadata;//元数据的表没有数据,就直接返回缺省数据                }                if (index == 0)//@3 如果元数据表中,只有一项                {                    this._metadataMap.GetKeyValuePair(index, out num2, out obj2);//从元数据表中获取当前的元数据和对应的DependencyObject的ID号                    while (dependencyObjectType.Id > num2)//如果当前的DependecyObject的Id号和从表中查询得到的ID号不相同,                    {                        dependencyObjectType = dependencyObjectType.BaseType;//就直接当前依赖属性的父类赋值给当前的依赖属性                    }                    if (num2 == dependencyObjectType.Id)                    {                        return (PropertyMetadata) obj2;//返回在继承体系中,最近的PropertyMetaData                    }                }                else if (dependencyObjectType.Id != 0)//@4 如果有多项PropertyMetaData。同样的方法,获取元数据,实际上就是比@3中多了一个循环;                {                    do                    {                        this._metadataMap.GetKeyValuePair(index, out num2, out obj2);//比上一步                        index--;                        while ((dependencyObjectType.Id < num2) && (index >= 0))                        {                            this._metadataMap.GetKeyValuePair(index, out num2, out obj2);                            index--;                        }                        while (dependencyObjectType.Id > num2)                        {                            dependencyObjectType = dependencyObjectType.BaseType;                        }                        if (num2 == dependencyObjectType.Id)                        {                            return (PropertyMetadata) obj2;                        }                    }                    while (index >= 0);                }            }            return this._defaultMetadata;        }

可以看到,在为依赖属性设置值的时候,首先要获取依赖属性的元数据,获取元数据的过程,首先是要获取当前属于DependencyObject的元数据,如果没有的话,就直接获取_defautMetaData;

这里就要注意:实际上是否触发元数据的回调PropertyChanged就是要看,GetMetaData获取的是当前类型对应的PropertyMetaData,还是当前继承体系中,最近的父类的对应的PropertyMetaData,再者,还是选取当前依赖属性的_defaultMetaData;

1.1.1.2 Register 和 RegisterAttached在实现上的区别

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, System.Windows.ValidateValueCallback validateValueCallback){RegisterParameterValidation(name, propertyType, ownerType);PropertyMetadata defaultMetadata = null;//注意defaultMetadata,实际上依赖属性是把当前用户注册的元数据的缺省值初始化为一个新的元数据,作为RegisterCommon的参数;if ((typeMetadata != null) && typeMetadata.DefaultValueWasSet()) { defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue); }DependencyProperty property = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback); if (typeMetadata != null) { property.OverrideMetadata(ownerType, typeMetadata); } return property; }public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, System.Windows.ValidateValueCallback validateValueCallback){    RegisterParameterValidation(name, propertyType, ownerType);    return RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);//附加属性是直接用用户提供的元数据,作为当前依赖属性的元数据;}


所以,可以看出,依赖属性和附加依赖属性实际上是一样的,两个只有用户注册依赖属性时,处理传入的元数据上有一点差别。所以,依赖属性和附加依赖属性的区别是,依赖属性只能继承,附加依赖属性只能被其它非继承类进行伪继承是错误的。

我们完全可以伪继承其它无关类的依赖属性,如下例中
    //Create by Rendw, 2013-8-15 代码片段<1>    public class Person : DependencyObject    {        public static readonly DependencyProperty MoodProperyty = DependencyProperty.Register("Mood",            typeof(string),            typeof(Person),            new PropertyMetadata("happy", new PropertyChangedCallback(MoodChanged)));        private static void MoodChanged(object sender, DependencyPropertyChangedEventArgs e)        {            Trace.WriteLine("the mood is changed, new Value is " + e.NewValue.ToString());        }        public string Mood        {            get            {                return GetValue(MoodProperyty) as string;            }            set            {                SetValue(MoodProperyty, value);            }        }    }    public class Patient : Person    {    }    //Test1 :实验在值发生变化的时候,是如何触发PropertyMetaData中的PropertyChanged 事件的过程    //Test2: RegisterCommon的理解    //Test3:SetValueCommon的理解    public class Feeling : DependencyObject    {        //注册Dependency Property        public static readonly DependencyProperty FeelProperty = DependencyProperty.Register("Feel",    typeof(string),    typeof(Feeling),    new PropertyMetadata("feel-happy", new PropertyChangedCallback(FeelChanged)));        private static void FeelChanged(object sender, DependencyPropertyChangedEventArgs e)        {            Trace.WriteLine("the feel is changed, new Value is " + e.NewValue.ToString());        }        //注册Attached Property        public static readonly DependencyProperty SenseProperty = DependencyProperty.RegisterAttached("Sense",            typeof(string),            typeof(Feeling),            new PropertyMetadata("sense-happy", new PropertyChangedCallback(SenseChanged)));        private static void SenseChanged(object sender, DependencyPropertyChangedEventArgs e)        {            Trace.WriteLine("the sense is changed, new value is " + e.NewValue.ToString());        }
        public static string GetSense(DependencyObject dO)        {            return dO.GetValue(SenseProperty) as string;        }        public static void SetSense(DependencyObject dO, string val)        {            dO.SetValue(SenseProperty, val);        }    }    public class Annimal : DependencyObject    {        public string Mood        {            get            {                return GetValue(Person.MoodProperyty) as string;            }            set            {                SetValue(Person.MoodProperyty, value);            }        }        public string Feel        {            get            {                return GetValue(Feeling.FeelProperty) as string;            }            set            {                SetValue(Feeling.FeelProperty, value);            }        }        public string Sense        {            get            {                return GetValue(Feeling.SenseProperty) as string;            }            set            {                SetValue(Feeling.SenseProperty, value);            }        }    }

在上边的例子中,MoodProperty是在Person中注册的依赖属性,但是,可以附加到Annimal类中,但是,我们有了上边的介绍,很容易就知道了在Annimal类中的Mood属性和Sense属性在设定值的时候,有如何区别
            //Create by Rendw 2013-8-15            Annimal animal = new Annimal();            animal.Mood = "angry";            animal.Sense = "sense-angry";

将会输出
the sense is changed, new value is sense-angry
而没有输出
the mood is changed, new Value is angry


1.2 xaml解析器对附加依赖属性Binding的解析

在上述的"代码片段<1>"中,添加类AnnimalButton,

public class AnnimalButton : Button    {        private string _rush;        public string Rush        {            get            {                return _rush;            }            set            {                if (value != _rush)                {                    _rush = value;                }            }        }        //附加依赖属性的clr包装        public string Sense        {            get            {                return GetValue(Feeling.SenseProperty) as string;            }            set            {                SetValue(Feeling.SenseProperty, value);            }        }      }

那么,我就在xaml中,如下的进行绑定

<loc:AnnimalButton Grid.Row="2"                            Content="annimal button"                            Click="AnnimalButton_Click"                           loc:Feeling.Sense ="{Binding Sense, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type loc:MainWindow}}}"                           />
在MainWindow类中,添加

        //Create by Rendw                                                                                                                                                                                                            private string _sense = "data binding sense";        public string Sense        {            get            {                return _sense;            }            set            {                if (value != _sense)                {                    _sense = value;                    Notify("Sense");                }            }        }

实际上在xaml中,解析器遇到附加依赖属性,就会去定义附加依赖属性的类中,查找是否有两个静态函数GetXXX和SetXXX两个静态函数,其中,XXX是属性名称,这时约定的,编程人员必须遵守。

甚至,我们可以在这两个函数中,进行其它的设定,比如Behavior的事件绑定,这就著名的(Ramoras Pattern)设计模式,这里不详细描述。

这里有个极容易犯的错误,也是作者自己一开始认识上的误区。此错误是在class AnnimalButton类中定义的Clr属性Sense,此属性来包装附加依赖属性。代码如下

//附加依赖属性的clr包装 Created by Rendw 2013-8-15        public string Sense        {            get            {                return GetValue(Feeling.SenseProperty) as string;            }            set            {                SetValue(Feeling.SenseProperty, value);            }        }  

从表面上来看,在Feeling中定义的public static string GetSense(DependencyObject dO);和public static void SetSense(DependencyObject dO, string val);两个静态函数和class AnnimalButton中定义的Clr属性Sense执行的是相同的代码,也可以进行绑定。于是,写下如下的代码

 <loc:AnnimalButton Grid.Row="2"                            Content="annimal button"                            Click="AnnimalButton_Click"                           Sense ="{Binding Sense, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type loc:MainWindow}}}"                           />

运行,阿欧,在xaml解析中,抛出异常“只能在 DependencyObject 的 DependencyProperty 上设置“Binding””。这个异常告诉我们,如果要对Clr属性进行绑定,那么,这个Clr必须是包装了当前类或者继承体系中父类的依赖属性;我们再来看

<loc:AnnimalButton Grid.Row="2"                            Content="annimal button"                            Click="AnnimalButton_Click"                           Sense ="rendawei"//直接赋值是没有问题的                           />

运行,正常运行。

所以,可以看出,在xaml中对依赖属性或者附加依赖属性进行Binding的时候,是有规则的,对于本类中的Clr包装的依赖属性,必须当前的依赖属性要属于本类或者父类;而附加属性的格式,是完全不检查SetXXX和GetXXX两个静态函数内,是否是对一个依赖属性进行操作,只检查格式是否正确。

如果,我们在编程中,想要把其它类中的附加依赖属性进行Clr包装,那么这里就用到了AddOwner的函数,这个应用很有用处,在字体的设置,颜色的设置中都是利用AddOwner的特性。要了解更多可以参考<<3.2 AddOwner和OverrideMetadata的区别>>.