wpf控件开发基础(3) -属性系统(2)

2/2/2010来源:ASP技巧人气:5316

属性默认值,可以保证属性的有效性.
属性验证有效性,可以对输入的属性进行校验
属性强制回调, 即不管属性有无发生变化,都要做出通知.
属性变更通知,当属性发生变化可以通知程序作出一系列的处理.
这里还没WPF什么事,我们来看看依赖属性是如何解决以上问题的.

内容概要
定义第一个最简单的依赖属性
依赖属性值基本操作
属性包装器
属性元数据(PRopertyMetadata)
属性元数据基本行为
MSDN的原话虽然生硬,但准确定性毋庸置疑.当理解后再来看别有一番体会.

一.定义第一个最简单的依赖属性

MSDN原话:Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能。这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。
我们来定义一个Age依赖属性,如下代码

public class DPCustomPeople
{
    public static readonly DependencyProperty AgeProperty =
        DependencyProperty.Register("Age", typeof(int), typeof(DPCustomPeople));

    public void DisplayAgeProperty()
    {
        Console.WriteLine("DPName:" + DPCustomPeople.AgeProperty.Name);
        Console.WriteLine("DPPropertyType:" + DPCustomPeople.AgeProperty.PropertyType);
        Console.WriteLine("DPOwnerType:" + DPCustomPeople.AgeProperty.OwnerType);
    }
}
然后调用输出结果

class Program
{
    static void Main(string[] args)
    {
        DPCustomPeople people = new DPCustomPeople();
        people.DisplayAgeProperty();
    }
}
你可能对DependencyProperty类比较陌生,DependencyProperty类提供了依赖属性的一些基本特征

注册依赖属性的方法是调用DependencyProperty的静态Register方法,其提供了多个重载方法,但以下三个步骤是必须的.注册完毕是其是一个静态属性

提供注册的名字(Name)"Age"
注册属性类型(PropertyType)typeof(int)
注册该依赖属性的所有者类型(OwnerType)typeof(DPCustomPeople)
注意:属性名字,属性类型,属性所有者类型一经注册将无法更改

以下为输出结果



二.依赖属性值基本操作(取值与赋值)

定义了Age依赖属性以后,那么我们理应可以对属性进行取值,赋值操作.DependencyProperty本身并不提供这些操作,而是由DependencyObject来负责

DependencyObject 表示一个参与依赖项属性系统的对象.

所以要求定义的类要继承自DependencyObject,那么改写DPCustomPeople

public class DPCustomPeople:System.Windows.DependencyObject
{
}
基本的取值赋值操作GetValue和SetValue方法

public void DPPropertyBasicOperator()
{
    Console.WriteLine("Age:" + this.GetValue(DPCustomPeople.AgeProperty));
    this.SetValue(DPCustomPeople.AgeProperty, 24);
    Console.WriteLine("ChangedAge:" + this.GetValue(DPCustomPeople.AgeProperty));
}
输出结果



三.属性包装器
用GetValue和SetValue方法对值操作不大美观,所以我们可以对其包装一下,定义Age属性

public int Age
{
    get { return (int)GetValue(AgeProperty); }
    set { SetValue(AgeProperty, value); }
}
注意:依赖属性包装命名规是把后面的Property去掉

public void DPPropertyBasicOperatorUsingProperty()
{
    Console.WriteLine("Age:" + this.Age);
    this.Age=24;
    Console.WriteLine("ChangedAge:" + this.Age);
}

以上的代码看起来是不是更加的简洁呢

四.属性元数据(PropertyMetadata)
MSDN原话:Windows Presentation Foundation (WPF) 属性系统包括一个元数据报告系统,该系统不局限于可以通过反射或常规公共语言运行时 (CLR) 特征报告的关于某个属性的内容。

说到属性元数据,第一个让人想到的就是.net的Attribute

public class Person
{
    [DefaultValue(200),Category("Layout")]
    public int Width { get; set; }
}
Attribute需要借助Visual Studio的力量,使得IDE对Attribute进行很友好的支持,或者依靠反射来赋值.

但离开这些技术的,通过正常途径,new出一个新的实例,加了Attribute的属性毫无效果.我们不能依赖这些Attribute来保证属性的一些基本特性(如默认值).依赖属性的属性元数据与上述描述的元数据不同.

依赖项属性的元数据

可以由定义依赖项属性的类来唯一地指定
可以在依赖项属性添加到另一个类时进行更改
可以由所有从定义基类继承依赖项属性的派生类来明确地重写
以上语言很生硬,但却说明了意图.但我们总无法第一时间领会设计者的想法.暂且先知道有这个概念的存在

五.属性元数据基本行为
属性元数据基本行为为依赖属性提供了3个功能,这也是本文刚提出来的问题.

默认属性
属性通知
属性强制回调
先来看一看一个完整PropertyMetadata的构造函数,如果没有为依赖属性设置默认的PropertyMetadata的话,内部会为依赖属性自动创建一个PropertyMetadata对象.

public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)
依赖属性借用属性元数据的概念来完成属性默认值,属性通知,强制回调等行为

1.属性默认值

public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
    new PropertyMetadata(string.Empty));
大部分人看到此处都会产生一个疑问,设置一个默认值为什么要用PropertyMetadata,为什么不直接在Register方法中直接注册呢?如下代码

public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
    string.Empty);
当然这个疑问一直伴随着我很久,解不开也是没办法,先放着.

注意点:当在PropertyMetadata给属性赋默认值时,是无法检测类型正确性的

如这样的定义,因为vs中的dp代码段默认值是0,这是值得注意的地方

public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
    new UipropertyMetadata(0));
2.属性默认值恢复操作

当属性赋值以后可以通过DependencyObject的ClearValue方法恢复默认值,如下代码

public string Name
{
    get { return (string)GetValue(NameProperty); }
    set { SetValue(NameProperty, value); }
}

public static readonly DependencyProperty NameProperty =
    DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
    new UIPropertyMetadata(string.Empty));

public void DPPropertyClearOperator()
{
    Console.WriteLine("Name:" + this.Name);
    this.Name="Terry";
    Console.WriteLine("ChangedName:" + this.Name);
    this.ClearValue(NameProperty);
    Console.WriteLine("Name:" + this.Name);
}
输出结果



注意点:区分默认赋值与默认值

默认赋值一般在构造函数中进行,但这却不是默认值(在依赖属性出现之前这的确是),特别是在派生类重写属性的时候

public class Student : DPCustomPeople
{
    public Student()
    {
        this.Name = "Sky";
    }

    public void TestSubDefaultDpValue()
    {
        Console.WriteLine("Clear Before:"+this.Name);
        this.ClearValue(Student.NameProperty);
        Console.WriteLine("Clear After:" + this.Name);
    }
}
输出结果



3.属性变更通知

这项功能是最常用的.当属性值发生变化时,会触发PropertyChangedCallback回调

public bool IsBoy
{
    get { return (bool)GetValue(IsBoyProperty); }
    set { SetValue(IsBoyProperty, value); }
}

public static readonly DependencyProperty IsBoyProperty =
    DependencyProperty.Register("IsBoy", typeof(bool), typeof(Student),
    new UIPropertyMetadata(false,new PropertyChangedCallback(IsBoyPropertyChangedCallback)));

public static void IsBoyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Student st = d as Student;
    if (st.IsBoy)
    {
        Console.WriteLine("Hello,Boy");
    }
    else
    {
        Console.WriteLine("Hello,Girl");
    }
}

public void TestPropertyChangedCallback()
{
    this.IsBoy = false;
    this.IsBoy = true;    this.IsBoy = true;
}
可以通过DependencyPropertyChangedEventArgs来查看旧值和新值
输出结果



注意点:

(1).通过上面的输出结果,你是否已经看出依赖属性的默认值是不会触发属性变更通知的

(2).手动触发属性变更通知

如果你希望默认值也能触发一次属性变更(其实有时候真的需要),你就不等不手动进行触发了

private void RaiseIsBoyPropertyChangedCallback()
{
    IsBoyPropertyChangedCallback(this,new DependencyPropertyChangedEventArgs
        (Student.IsBoyProperty, Student.IsBoyProperty.DefaultMetadata.DefaultValue, null));
}
(3).当有属性变更通知时,一定要保证属性默认值类型的正确性

我们知道值类型都有是默认值的,引用类型则没有(即可以赋值为null),一个类型是否有默认类型可以用default关键字查看.如下图



我们将上面定义的依赖属性默认值改写null,在没有PropertyChangedCallback的时候可以很好的运行,但在有属性变更通知的时候灾难发生了,程序将出现异常,说类型不匹配.

public static readonly DependencyProperty IsBoyProperty =
    DependencyProperty.Register("IsBoy", typeof(bool), typeof(Student),
    new UIPropertyMetadata(null,new PropertyChangedCallback(IsBoyPropertyChangedCallback)));
再来看看引用类型,默认值为null则相安无事

public IList LovedGirl
{
    get { return (IList)GetValue(LovedGirlProperty); }
    set { SetValue(LovedGirlProperty, value); }
}

public static readonly DependencyProperty LovedGirlProperty =
    DependencyProperty.Register("LovedGirl", typeof(IList), typeof(Student),
    new UIPropertyMetadata(null, new PropertyChangedCallback(LovedGirlChangedCallback)));

public static void LovedGirlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Student st = d as Student;
    foreach (var item in e.NewValue as IList)
    {
        Console.WriteLine(item);
    }
}

public void TestReferenceDpType()
{
    List<string> list = new List<string>();
    list.Add("girl 1");
    list.Add("girl 2");
    this.LovedGirl = list;
}
4.强制属性回调

首先默认值还是不会触发回调方法.

强制回调方法即不管属性值有无发生变化,都会进入回调方法

public int Score
{
    get { return (int)GetValue(ScoreProperty); }
    set { SetValue(ScoreProperty, value); }
}

public static readonly DependencyProperty ScoreProperty =
    DependencyProperty.Register("Score", typeof(int), typeof(Student),
    new UIPropertyMetadata(0,null,new CoerceValueCallback(ScoreCoerceValueCallback)));

public static object ScoreCoerceValueCallback(DependencyObject d, object baseValue)
{
    Console.WriteLine(baseValue);
    return baseValue;
}

public void TestCoerceValueCallback()
{
    this.Score = 0;
    this.Score = 0;
    this.Score = 0;
}