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

上篇说明了属性存在的一系列问题.

  1. 属性默认值,可以保证属性的有效性.
  2. 属性验证有效性,可以对输入的属性进行校验
  3. 属性强制回调, 即不管属性有无发生变化,都要做出通知.
  4. 属性变更通知,当属性发生变化可以通知程序作出一系列的处理.

这里还没WPF什么事,我们来看看依赖属性是如何解决以上问题的.

内容概要

  • 定义第一个最简单的依赖属性
  • 依赖属性值基本操作
  • 属性包装器
  • 属性元数据(PropertyMetadata)
  • 属性元数据基本行为

MSDN的原话虽然生硬,但准确定性毋庸置疑.当理解后再来看别有一番体会.

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

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

  1. public class DPCustomPeople
  2. {
  3. public static readonly DependencyProperty AgeProperty =
  4. DependencyProperty.Register("Age", typeof(int), typeof(DPCustomPeople));
  5.  
  6. public void DisplayAgeProperty()
  7. {
  8. Console.WriteLine("DPName:" + DPCustomPeople.AgeProperty.Name);
  9. Console.WriteLine("DPPropertyType:" + DPCustomPeople.AgeProperty.PropertyType);
  10. Console.WriteLine("DPOwnerType:" + DPCustomPeople.AgeProperty.OwnerType);
  11. }
  12. }

然后调用输出结果

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. DPCustomPeople people = new DPCustomPeople();
  6. people.DisplayAgeProperty();
  7. }
  8. }

你可能对DependencyProperty类比较陌生,DependencyProperty类提供了依赖属性的一些基本特征

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

  1. 提供注册的名字(Name)"Age"

  2. 注册属性类型(PropertyType)typeof(int)
  3. 注册该依赖属性的所有者类型(OwnerType)typeof(DPCustomPeople)

注意:属性名字,属性类型,属性所有者类型一经注册将无法更改

以下为输出结果

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

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

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

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

  1. public class DPCustomPeople:System.Windows.DependencyObject
  2. {
  3. }

基本的取值赋值操作GetValue和SetValue方法

  1. public void DPPropertyBasicOperator()
  2. {
  3. Console.WriteLine("Age:" + this.GetValue(DPCustomPeople.AgeProperty));
  4. this.SetValue(DPCustomPeople.AgeProperty, 24);
  5. Console.WriteLine("ChangedAge:" + this.GetValue(DPCustomPeople.AgeProperty));
  6. }

输出结果

三.属性包装器

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

  1. public int Age
  2. {
  3. get { return (int)GetValue(AgeProperty); }
  4. set { SetValue(AgeProperty, value); }
  5. }

注意:依赖属性包装命名规是把后面的Property去掉

  1. public void DPPropertyBasicOperatorUsingProperty()
  2. {
  3. Console.WriteLine("Age:" + this.Age);
  4. this.Age=24;
  5. Console.WriteLine("ChangedAge:" + this.Age);
  6. }

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

四.属性元数据(PropertyMetadata)

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

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

  1. public class Person
  2. {
  3. [DefaultValue(200),Category("Layout")]
  4. public int Width { get; set; }
  5. }

Attribute需要借助Visual Studio的力量,使得IDE对Attribute进行很友好的支持,或者依靠反射来赋值.

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

依赖项属性的元数据

  1. 可以由定义依赖项属性的类来唯一地指定

  2. 可以在依赖项属性添加到另一个类时进行更改
  3. 可以由所有从定义基类继承依赖项属性的派生类来明确地重写

以上语言很生硬,但却说明了意图.但我们总无法第一时间领会设计者的想法.暂且先知道有这个概念的存在

五.属性元数据基本行为

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

  1. 默认属性

  2. 属性通知
  3. 属性强制回调

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

  1. public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)

依赖属性借用属性元数据的概念来完成属性默认值,属性通知,强制回调等行为

1.属性默认值

  1. public static readonly DependencyProperty NameProperty =
  2. DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
  3. new PropertyMetadata(string.Empty));

大部分人看到此处都会产生一个疑问,设置一个默认值为什么要用PropertyMetadata,为什么不直接在Register方法中直接注册呢?如下代码

  1. public static readonly DependencyProperty NameProperty =
  2. DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
  3. string.Empty);

当然这个疑问一直伴随着我很久,解不开也是没办法,先放着.

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

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

  1. public static readonly DependencyProperty NameProperty =
  2. DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
  3. new UIPropertyMetadata(0));

2.属性默认值恢复操作

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

  1. public string Name
  2. {
  3. get { return (string)GetValue(NameProperty); }
  4. set { SetValue(NameProperty, value); }
  5. }
  6.  
  7. public static readonly DependencyProperty NameProperty =
  8. DependencyProperty.Register("Name", typeof(string), typeof(DPCustomPeople),
  9. new UIPropertyMetadata(string.Empty));
  10.  
  11. public void DPPropertyClearOperator()
  12. {
  13. Console.WriteLine("Name:" + this.Name);
  14. this.Name="Terry";
  15. Console.WriteLine("ChangedName:" + this.Name);
  16. this.ClearValue(NameProperty);
  17. Console.WriteLine("Name:" + this.Name);
  18. }

输出结果

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

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

  1. public class Student : DPCustomPeople
  2. {
  3. public Student()
  4. {
  5. this.Name = "Sky";
  6. }
  7.  
  8. public void TestSubDefaultDpValue()
  9. {
  10. Console.WriteLine("Clear Before:"+this.Name);
  11. this.ClearValue(Student.NameProperty);
  12. Console.WriteLine("Clear After:" + this.Name);
  13. }
  14. }

输出结果

3.属性变更通知

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

  1. public bool IsBoy
  2. {
  3. get { return (bool)GetValue(IsBoyProperty); }
  4. set { SetValue(IsBoyProperty, value); }
  5. }
  6.  
  7. public static readonly DependencyProperty IsBoyProperty =
  8. DependencyProperty.Register("IsBoy", typeof(bool), typeof(Student),
  9. new UIPropertyMetadata(false,new PropertyChangedCallback(IsBoyPropertyChangedCallback)));
  10.  
  11. public static void IsBoyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
  12. {
  13. Student st = d as Student;
  14. if (st.IsBoy)
  15. {
  16. Console.WriteLine("Hello,Boy");
  17. }
  18. else
  19. {
  20. Console.WriteLine("Hello,Girl");
  21. }
  22. }
  23.  
  24. public void TestPropertyChangedCallback()
  25. {
  26. this.IsBoy = false;
  27. this.IsBoy = true;
    this.IsBoy = true;
  28. }

可以通过DependencyPropertyChangedEventArgs来查看旧值和新值
输出结果

注意点:

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

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

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

  1. private void RaiseIsBoyPropertyChangedCallback()
  2. {
  3. IsBoyPropertyChangedCallback(this,new DependencyPropertyChangedEventArgs
  4. (Student.IsBoyProperty, Student.IsBoyProperty.DefaultMetadata.DefaultValue, null));
  5. }

(3).当有属性变更通知时,一定要保证属性默认值类型的正确性

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

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

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

再来看看引用类型,默认值为null则相安无事

  1. public IList LovedGirl
  2. {
  3. get { return (IList)GetValue(LovedGirlProperty); }
  4. set { SetValue(LovedGirlProperty, value); }
  5. }
  6.  
  7. public static readonly DependencyProperty LovedGirlProperty =
  8. DependencyProperty.Register("LovedGirl", typeof(IList), typeof(Student),
  9. new UIPropertyMetadata(null, new PropertyChangedCallback(LovedGirlChangedCallback)));
  10.  
  11. public static void LovedGirlChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
  12. {
  13. Student st = d as Student;
  14. foreach (var item in e.NewValue as IList)
  15. {
  16. Console.WriteLine(item);
  17. }
  18. }
  19.  
  20. public void TestReferenceDpType()
  21. {
  22. List<string> list = new List<string>();
  23. list.Add("girl 1");
  24. list.Add("girl 2");
  25. this.LovedGirl = list;
  26. }

4.强制属性回调

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

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

  1. public int Score
  2. {
  3. get { return (int)GetValue(ScoreProperty); }
  4. set { SetValue(ScoreProperty, value); }
  5. }
  6.  
  7. public static readonly DependencyProperty ScoreProperty =
  8. DependencyProperty.Register("Score", typeof(int), typeof(Student),
  9. new UIPropertyMetadata(0,null,new CoerceValueCallback(ScoreCoerceValueCallback)));
  10.  
  11. public static object ScoreCoerceValueCallback(DependencyObject d, object baseValue)
  12. {
  13. Console.WriteLine(baseValue);
  14. return baseValue;
  15. }
  16.  
  17. public void TestCoerceValueCallback()
  18. {
  19. this.Score = 0;
  20. this.Score = 0;
  21. this.Score = 0;
  22. }

好了,本篇主要介绍了依赖属性的注册方法以及依赖属性的属性元数据用法.下篇继续

Demo下载

wpf控件开发基础(3) -属性系统(2)的更多相关文章

  1. wpf控件开发基础(4) -属性系统(3)

    原文:wpf控件开发基础(4) -属性系统(3) 知识回顾 接上篇,上篇我们真正接触到了依赖属性的用法,以及依赖属性的属性元数据的用法,并且也实实在在地解决了之前第二篇提到的一系列问题.来回顾一下 属 ...

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

    原文:wpf控件开发基础(2) -属性系统(1) 距离上篇写的时间有1年多了.wpf太大,写的东西实在太多,我将依然围绕着自定义控件来展开与其相关的技术点. 也欢迎大家参与讨论.这篇我们将要讨论的是W ...

  3. wpf控件开发基础

    wpf控件开发基础(3) -属性系统(2) http://www.cnblogs.com/Clingingboy/archive/2010/02/01/1661370.html 这个有必要看看 wpf ...

  4. wpf控件开发基础(5) -依赖属性实践

    原文:wpf控件开发基础(5) -依赖属性实践 知识回顾 接上篇,回顾这三篇讲了什么东西 首先说明了属性的现存问题,然后介绍了依赖属性的基本用法及其解决方案,由于依赖属性以静态属性的方式存在,进而又介 ...

  5. asp.net控件开发基础(1)(转)原文更多内容

    asp.net本身提供了很多控件,提供给我们这些比较懒惰的人使用,我认为控件的作用就在此,因为我们不想重复工作,所以要创建它,这个本身便是一个需求的关系,所以学习控件开发很有意思. wrox网站上有本 ...

  6. WPF控件开发(2) 自动完成(AutoComplete)-1

    自动完成功能使用范围很广,多以TextBox或ComboBox的形式出现,在输入的同时给予候选词,候选词一般有两种方式获取. 一种类似Baidu,Google,Bing之类的搜索引擎所用的直接给予前十 ...

  7. 跟我一起学WPF(2):WPF控件基础

    WPF控件简介 通过上一篇XAML语言的介绍,我们知道,XAML是一个树形结构,同样,WPF控件作为构成整个XAML树的一部分,也是一个树形结构.我们看一个简单的例子. <Button.Cont ...

  8. 浅谈Winform控件开发(一):使用GDI+美化基础窗口

    写在前面: 本系列随笔将作为我对于winform控件开发的心得总结,方便对一些读者在GDI+.winform等技术方面进行一个入门级的讲解,抛砖引玉. 别问为什么不用WPF,为什么不用QT.问就是懒, ...

  9. C# Winform开发以及控件开发的需要注意的,被人问怕了,都是基础常识

    我是搞控件开发的,经常被人问,所以把一些问题记录了下来!如果有人再问,直接把地址丢给他看. 一. 经常会有人抱怨Winform界面闪烁,下面有几个方法可以尽可能的避免出现闪烁 1.控件的使用尽量以纯色 ...

随机推荐

  1. CloudFoundry hm9000原理及排错

    hm9000跟hm_next(healthmanager)功能类似.在cloudfoundry集群中担任至关重要的角色 - 尝试启动缺失情况下的实例,停止异常实例 - 获知和报告应用执行的实际实例个数 ...

  2. 介绍linux设备驱动编程

    目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer):       主要利用C库函数和Linux API进行应用 ...

  3. 【例题5-7 UVA - 136】Ugly Numbers

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 每个丑数x,都能生成3个丑数2x,3x,5x 则我们以1作为起点. 生成丑数. 每次取出set里面最小的那个数. 然后用它去生成其他 ...

  4. 【BZOJ 4516】生成魔咒

    [链接]h在这里写链接 [题意]     [Description]         给你n(n<=10^9)个数字,把它们依次,一个一个地添加在空串S的后面.         要求每添加一次之 ...

  5. Spring 使用Cache(转)

    从3.1开始Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事物管理的支持.Spring Cache是作用在方法上的,其核心思想是:当我们在调用一个缓存方法时会把该方法参数 ...

  6. JScript使用正则表达式的经验

    作者:朱金灿 来源:http://blog.csdn.net/clever101 在JScript使用正则表达式时有不少元字符在试图对其进行匹配时需要进行特殊的处理.要匹配这些特殊字符,必须首先将这些 ...

  7. EXCEL 学习笔记

    上一次学院培训学生干部,讲了这个,发现自己EXCEL还是弱爆了.分享一些上次学到的东西. 1. 字符串拼接: 2.排名快速生成 RAND()随机函数 RANK(num,ref,[order]) 第一列 ...

  8. Fragment之一:基本原理 分类: H1_ANDROID 2013-11-18 14:15 1642人阅读 评论(0) 收藏

    1.低版本API对Fragment的支持 Fragment必须被加载进Acitivity中,才能呈现.而在低于3.0版本的API中,由于不存在Fragment,因此必须使用support包: (1)对 ...

  9. js进阶js中支持正则的四个常用字符串函数(search march replace split)

    js进阶js中支持正则的四个常用字符串函数(search march replace split) 一.总结 代码中详细四个函数的用法 search march replace split 二.js进 ...

  10. Android动态修改图片颜色的实现方式分析

    版权声明:本文为博主原创文章,未经博主允许不得转载. 1.修改色相.饱和度.亮度 参看:http://blog.csdn.NET/sjf0115/article/details/7267063 2.使 ...