Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能,这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。
上面是官方对依赖项属性的说辞。比较晦涩抽象。再来看看MSDN上给出的定义:
Represents a property that can be set through methods such as, styling, data binding, animation, and inheritance.表示可以通过方法设置的属性,样式,数据绑定、动画和继承。
简单了许多,但还是不够直白。我们来给依赖属性一个大白话的描述:也就是说它是一个可以通过多种方式进行设置且能进行改变是进行通知的属性。
微软引入依赖属性的初衷是什么呢?
我们先看看普通的属性和依赖属性的区别:
普通属性:
1 private string sampleProperty; 2 public string SampleProperty 3 { 4 get 5 { 6 return sampleProperty; 7 } 8 set 9 { 10 if (value != null) 11 { 12 sampleProperty = value; 13 } 14 else 15 { 16 sampleProperty = "Knights Warrior!"; 17 } 18 } 19 }
依赖属性:(使用Visula Studio时,依赖属性可以通过输入propdp+两次Tab键生成代码段)
1 public class SampleDPClass : DependencyObject 2 { 3 //声明一个静态只读的DependencyProperty字段 4 public static readonly DependencyProperty SampleProperty; 5 6 static SampleDPClass() 7 { 8 //注册我们定义的依赖属性Sample 9 SampleProperty = DependencyProperty.Register("Sample", typeof(string), typeof(SampleDPClass), 10 new PropertyMetadata("Knights Warrior!", OnValueChanged)); 11 } 12 13 private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 14 { 15 //当值改变时,我们可以在此做一些逻辑处理 16 } 17 18 //属性包装器,通过它来读取和设置我们刚才注册的依赖属性 19 public string Sample 20 { 21 get { return (string)GetValue(SampleProperty); } 22 set { SetValue(SampleProperty, value); } 23 } 24 }
PS:不使用代码段时,依赖属性可以通过下面的简单四步编写:
第一步: 让所在类型继承自 DependencyObject基类,在WPF中,我们仔细观察框架的类图结构,你会发现几乎所有的 WPF 控件都间接继承自DependencyObject类型。
第二步:使用 public static 声明一个 DependencyProperty的变量,该变量才是真正的依赖属性 ,看源码就知道这里其实用了简单的单例模式的原理进行了封装(构造函数私有),只暴露Register方法给外部调用。
第三步:在静态构造函数中完成依赖属性的元数据注册,并获取对象引用,看代码就知道是把刚才声明的依赖属性放入到一个类似于容器的地方,没有讲实现原理之前,请容许我先这么陈述。
第四步:在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。
对于普通属性,我们是熟悉的,我们知道,普通属性不能对该属性进行改变通知,值限制,值验证,且属性的设置方式单一,最关键一点,普通属性继承自一层层父类,它们中大多数值都为默认值,并未修改和使用,更耗费内存。
自此,我们可以总结出依赖属性存在的原因或者说自身的优势:
1、新功能的引入:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。
2、节约内存:在WinForm等项目开发中,你会发现UI控件的属性通常都是赋予的初始值,为每一个属性存储一个字段将是对内存的巨大浪费。WPF依赖属性解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。
3、支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合Expression、Style、Animation等可以给我们带来很强的开发体验。
下面是几个依赖属性的小示例:
小示例1:继承关系:
1 public class MyCustomButton : Button 2 { 3 static MyCustomButton() 4 { 5 //通过MyStackPanel依赖属性MinDateProperty的AddOwner方式实现继承,注意FrameworkPropertyMetadataOptions的值为Inherits 6 MinDateProperty = MyStackPanel.MinDateProperty.AddOwner(typeof(MyCustomButton), 7 new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits)); 8 } 9 10 public static readonly DependencyProperty MinDateProperty; 11 12 public DateTime MinDate 13 { 14 get { return (DateTime)GetValue(MinDateProperty); } 15 set { SetValue(MinDateProperty, value); } 16 } 17 } 18 19 20 public class MyStackPanel : StackPanel 21 { 22 static MyStackPanel() 23 { 24 //我们在MyStackPanel里面注册了MinDate,注意FrameworkPropertyMetadataOptions的值为Inherits 25 MinDateProperty = DependencyProperty.Register("MinDate", 26 typeof(DateTime), 27 typeof(MyStackPanel), 28 new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits)); 29 } 30 31 public static readonly DependencyProperty MinDateProperty; 32 33 public DateTime MinDate 34 { 35 get { return (DateTime)GetValue(MinDateProperty); } 36 set { SetValue(MinDateProperty, value); } 37 } 38 }
1 <Window x:Class="Custom_Inherited_DPs.Window1" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:Custom_Inherited_DPs" 5 xmlns:sys="clr-namespace:System;assembly=mscorlib" 6 WindowStartupLocation="CenterScreen" 7 Title="使用自动以依赖属性继承" Height="300" Width="300"> 8 <Grid> 9 <local:MyStackPanel x:Name="myStackPanel" MinDate="{x:Static sys:DateTime.Now}"> 10 <!-- myStackPanel的依赖属性 --> 11 <ContentPresenter Content="{Binding Path=MinDate, ElementName=myStackPanel}"/> 12 <!-- 继承自myStackPanel的依赖属性 --> 13 <local:MyCustomButton 14 Content="{Binding RelativeSource={x:Static RelativeSource.Self}, 15 Path=MinDate}" 16 Height="20"/> 17 </local:MyStackPanel> 18 </Grid> 19 </Window>
效果图:
示例2:只读依赖属性:
1 using System; 2 using System.Windows; 3 using System.Windows.Threading; 4 5 namespace ReadOnlyDP 6 { 7 /// <summary> 8 /// MainWindow.xaml 的交互逻辑 9 /// </summary> 10 public partial class MainWindow : Window 11 { 12 public MainWindow() 13 { 14 InitializeComponent(); 15 DispatcherTimer timer = new DispatcherTimer(); 16 timer.Tick += new EventHandler(dispatcherTimer_Tick); 17 timer.Interval = new TimeSpan(0, 0, 1); 18 timer.Start(); 19 } 20 private void dispatcherTimer_Tick(object sender, EventArgs e) 21 { 22 int newValue = Counter == int.MaxValue ? 0 : Counter + 1; 23 SetValue(counterKey, newValue); 24 } 25 public int Counter 26 { 27 get { return (int)GetValue(counterKey.DependencyProperty); } 28 } 29 private static readonly DependencyPropertyKey counterKey = 30 DependencyProperty.RegisterReadOnly("Counter", typeof(int), typeof(MainWindow), 31 new PropertyMetadata(0)); 32 } 33 }
1 <Window x:Class="ReadOnlyDP.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 x:Name="winThis" 5 Title="MainWindow" 6 Width="525" 7 Height="350"> 8 <Grid> 9 <Viewbox> 10 <TextBlock Text="{Binding ElementName=winThis, Path=Counter}" /> 11 </Viewbox> 12 </Grid> 13 </Window>