今天我们来学习WPF一个比较重要的概念:依赖属性。这里推荐大家看看周永恒大哥的文章,讲的确实很不错。我理解的没那么深入,只能发表一下自己的浅见。提到依赖属性,不得不说我们经常使用的传统的.net属性,大家都比较了解,一般拥有get和set访问器,它只是一个语法糖,在CLR层面上其实是两个方法(传统属性也叫CLR属性)和一个私有的字段,由于实例方法在内存中只有一份,所以属性不会过多增加内存负担。和CLR属性相比,依赖属性有哪些特点呢?首先我们来自定义一个具有IsTransparent的Button。

自定义依赖属性

public class MyButton:Button
{
//第一步:声明并注册依赖属性,设置默认值为false
public static readonly DependencyProperty IsTransparentProperty =
DependencyProperty.Register("IsTransparent", typeof(bool), typeof(MyButton), new FrameworkPropertyMetadata(defaultValue: false, propertyChangedCallback: new PropertyChangedCallback(IsTransparentChanged)));
//第二步:为依赖属性提供.net包装器
public bool IsTransparent
{
get { return (bool)GetValue(IsTransparentProperty); }
set {SetValue(IsTransparentProperty, value);}
}
public static void IsTransparentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Value Change
}
}

使用propdp这个Code Snippet可以快速创建一个依赖属性。

这里有一个命名约定,依赖属性的名字要以Property结尾来表明它是一个依赖属性。

声明注册的依赖属性是static readonly类型,保证了唯一性。.net包装器不是必须的。

DependencyProperty类采用了Singleton设计模式设计,由DependencyProperty.Register方法返回一个DependencyProperty实例,该方法有三个重载方法:

//
// 摘要:
// 使用指定的属性名称、属性类型和所有者类型注册依赖项属性。
//
// 参数:
// name:
// 要注册的依赖项对象的名称。在所有者类型的注册命名空间内,名称必须是唯一的。
//
// propertyType:
// 属性的类型。
//
// ownerType:
// 正注册依赖项对象的所有者类型。
//
// 返回结果:
// 一个依赖项对象标识符,应使用它在您的类中设置 public static readonly 字段的值。然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public static DependencyProperty Register(string name, Type propertyType, Type ownerType);
//
// 摘要:
// 使用指定的属性名称、属性类型、所有者类型和属性元数据注册依赖项属性。
//
// 参数:
// name:
// 要注册的依赖项对象的名称。
//
// propertyType:
// 属性的类型。
//
// ownerType:
// 正注册依赖项对象的所有者类型。
//
// typeMetadata:
// 依赖项对象的属性元数据。
//
// 返回结果:
// 一个依赖项对象标识符,应使用它在您的类中设置 public static readonly 字段的值。然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata);
//
// 摘要:
// 使用指定的属性名称、属性类型、所有者类型、属性元数据和属性的值验证回调来注册依赖项属性。
//
// 参数:
// name:
// 要注册的依赖项对象的名称。
//
// propertyType:
// 属性的类型。
//
// ownerType:
// 正注册依赖项对象的所有者类型。
//
// typeMetadata:
// 依赖项对象的属性元数据。
//
// validateValueCallback:
// 对回调的引用,除了典型的类型验证之外,该引用还应执行依赖项对象值的任何自定义验证。
//
// 返回结果:
// 一个依赖项对象标识符,应使用它在您的类中设置 public static readonly 字段的值。然后,在以后使用该标识符引用依赖项对象,用于某些操作,例如以编程方式设置其值,或者获取元数据。
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);

我们来看下参数最全的一个重载方法:

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);
  • name参数:指定以哪个CLR属性来作为该依赖属性的包装器。一般以依赖属性去掉Property后的命名作为该值。
  • propertyType参数:指定该依赖属性的注册类型。
  • ownerType参数:指定该依赖属性要注册关联的类型。
  • typeMetadata参数:指定依赖属性的属性元数据,用来告诉WPF如何处理该属性、如何处理属性值改变的回调、强制值的转换以及如何验证。常用的PropertyMetadata有三个:PropertyMetadata、UIPropertyMetadata和FrameworkPropertyMetadata,按顺序存在继承关系。
  • validateValueCallback参数:delegate类型,指定用于验证属性的回调函数。

我们来具体说下最为复杂的FrameworkPropertyMetadata,它有这样一些属性:

还有两个方法:

  • Merge方法:当子类调用DependencyProperty实例的OverrideMetadata方法时调用
  • OnApply方法:当此元数据已经应用到一个属性时(这表明正在密封元数据)调用

工作原理简单剖析

前面我们在声明依赖属性的时候用的是Static类型,当把值直接存在该dp里面时,所有的拥有该dp的do的该dp的值都是一样的,这是不合实际的。那dp的值Set到哪里了呢?

原来在dp的内部维护了一个全局的map,key是由上面的name参数和ownerType参数各自的HashCode取异或得到的(保证唯一性)。这样还是没有解决问题,同一个类的不同实例的相同依赖属性的值在内存中还是只有一份。dp是依赖do的。在do中引入EffectiveValueEntry数组用来存储修改过的依赖属性值,在dp内部维护一个PropertyIndex,通过它去找该依赖属性修改值。

    internal struct EffectiveValueEntry
{
internal int PropertyIndex { get; set; }
internal object Value { get; set; }
}

然后,我们可以在DependencyProperty.Register的第四个PropertyMetadata类型参数中设置默认值。这样,当依赖属性修改后,我们去EffectiveValueEntry数组中去取值;当依赖属性未修改时,我们去取它的默认值。这自然节省了内存的占用。

变更通知

无论什么时候,只有属性值改变了,WPF就会自动根据属性的metadata触发一系列操作。这些动作例如有重新呈现适当的元素、更新当前布局等,它们是由metadata属性来决定的。内建的变更通知最有趣的特性之一是属性触发器,它可以在属性值改变时执行自定义操作而不用更改任何过程式代码。这个很好理解,可以直接在XAML页面使用属性触发器,而不用在过程式代码中写事件处理程序。我们来看个例子:

<local:MyButton Content="Hello,WPF" x:Name="btn" IsTransparent="True" Width="" Height="" Click="MyButton_Click">
<local:MyButton.Style>
<Style TargetType="{x:Type local:MyButton}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Foreground" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
</local:MyButton.Style>
</local:MyButton>

当触发MouseEnter时,IsMouseOver为true,Button的前景色变蓝;当触发MouseLeave时,IsMouseOver为false,Button的前景色恢复黑色。还有数据触发器(DataTrigger)和事件触发器(EventTrigger)我们将在将style时再详细说明。

属性值继承

属性值继承并不是指传统的面向对象的类继承,而是指属性值沿着元素树自顶向下传递。举个例子说明:

<Window x:Class="DependencyPropertyDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" FontSize="20">
<Grid>
<StackPanel>
<TextBlock Text="WPF" FontSize="50"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="WF" Width="100px"/>
<TextBlock Text="SliverLight" />
</StackPanel>
<StatusBar>WPF is Working!</StatusBar>
</StackPanel>
</Grid>
</Window>

效果如下:

我们在Window设置了FontSize="20",Window下第一个TextBlock的FontSize值,我们进行了显式地设置,重载了继承的值20。其余的TextBlock的FontSize都变成了20。然而,并不是所有的子元素都会继承这个FontSize值,例如StatusBar,虽然StatusBar具有FontSize属性。属性值的继承行为由以下两个因素决定:

1.并不是每个依赖属性都参与属性继承(从其内部来讲,依赖属性会通过传递FrameWorkMatadataOptions.Inherits给DependencyProperty.Register方法注册来完成继承)

2.有其它优先级更高的源来设置这些属性值。

有一些控件例如StatusBar、Menu、Tooltip等,其内部会将字体属性设置为当前系统设置。而且,它们会阻止继承沿着元素树继续向下传递。属性值继承在其它地方的应用:属性值继承并不只是发生在逻辑树或者可视树的子元素,也发生在元素的触发器或任何属性(不只是Content和Children属性),只要继承自Freezable类就行。

对多个提供程序的支持

WPF有很多强大的机制可以独立地去尝试设置依赖属性的值。

基础值(BaseValue)的来源很多,通过优先级来判断BaseValue。优先级从高到低如下排列:

  1. 本地值(LocalValue):通过调用SetValue方法设值,表现为在XAML页面直接赋值或者通过过程式代码赋值
  2. 样式触发器
  3. 模板触发器
  4. 样式设置程序
  5. 主题样式触发器:主题样式就是WPF系统内置的一些样式
  6. 主题样式设置程序
  7. 属性值继承:子类从父类继承过来的依赖属性值
  8. 默认值:在注册时设置的初始值

如果属性值是一个表达式的话,会转换成具体的值。

如果属性值是一个动画的话,它可以改变或者替代当前的属性值。

如果注册时给出了CoerceValueCallBack,会调用该回调函数,返回一个基于自定义逻辑的值。例如ProgressBar当Value小于Minimum时,Value等于Minimum;当Value大于Maximum时,Value等于Maximum。

如果注册时给出了ValidateValueCallBack,就会将值传入来判断是否有效。

当无法判断属性值的来源时,可以使用DependencyPropertyHelper.GetValueSource方法来获取一个ValueSource结构:

  • BaseValueSource:它是一个枚举值,反应上面的基础值的来源。
  • IsExpression:判断是否是一个表达式。
  • IsAnimated:判断是否是执行动画。
  • IsCoerced:判断是否是强制值转换。

我们来看下在属性值继承的那个例子,来看下为什么TextBlock会继承Window的FontSize属性而StatusBar不会。

ValueSource vs = DependencyPropertyHelper.GetValueSource(this.tb1, TextBlock.FontSizeProperty);
MessageBox.Show((vs.BaseValueSource == BaseValueSource.Local).ToString());//true,在XAML中直接赋值 ValueSource vs1 = DependencyPropertyHelper.GetValueSource(this.tb2, TextBlock.FontSizeProperty);
MessageBox.Show((vs1.BaseValueSource == BaseValueSource.Inherited).ToString());//true,继承自Window ValueSource vs2 = DependencyPropertyHelper.GetValueSource(this.sb1, StatusBar.FontSizeProperty);
MessageBox.Show((vs2.BaseValueSource == BaseValueSource.DefaultStyle).ToString());//true,系统内置样式

另外,我们可以使用DependencyObject.ClearValue()方法来清除某依赖属性的Local Value,让该依赖属性重新确认BaseValue,还以上面为例:

this.tb1.ClearValue(TextBlock.FontSizeProperty);
ValueSource vs = DependencyPropertyHelper.GetValueSource(this.tb1, TextBlock.FontSizeProperty);
MessageBox.Show((vs.BaseValueSource == BaseValueSource.Inherited).ToString());//true,恢复继承自Window

依赖属性的共享

当某一个类需要和其它类共享某一依赖属性,而这些类并不一定要有继承关系,我们就可以用DependentyProperty的AddOwner方法来实现共享该依赖属性。在WPF的类库实现中,TextBlock和Control的FontFamilyProperty属性就共享了TextElement的FontFamilyProperty属性。

public class TextBlock
{
public static readonly DependencyProperty FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock), new UIPropertyMetadata(null));
}

当我们在自定义元素时,可以这种方式方便地实现依赖属性的重用。在WPF内部大量使用了这种方式。其实,不仅依赖属性可以共享,在后面要说的路由事件也同样可以使用RoutedEvent的AddOwner方法共享。

附加属性

附加属性也是依赖属性,是依赖属性的一种特殊形式,可以被有效地添加到任何对象中。先来看下附加属性的声明注册:

public class MyTooltip
{
public static bool GetAttached(DependencyObject obj)
{
return (bool)obj.GetValue(AttachedProperty);
} public static void SetAttached(DependencyObject obj, bool value)
{
obj.SetValue(AttachedProperty, value);
} // Using a DependencyProperty as the backing store for Attached. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AttachedProperty =
DependencyProperty.RegisterAttached("Attached", typeof(bool), typeof(MyButton), new UIPropertyMetadata(false, new PropertyChangedCallback(OnPropertyChanged))); private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
Slider slider = sender as Slider;
if (slider != null)
{
Button btn = new Button();
btn.Content = "Ok";
TextBlock tb = new TextBlock(new Run("This is a Tooltip!"));
StackPanel sp = new StackPanel();
sp.Children.Add(tb);
sp.Children.Add(btn);
slider.ToolTip = sp;
}
}
}
}

这里我们用propa这个Code Snippet快速构建了一个AttachedProperty附加属性,通过DependencyProperty.RegisterAttached方法来声明注册,该方法签名和声明注册依赖属性的方法签名一样,最大的不同是依赖属性宿主是依赖对象,而附加属性宿主任意,还有不同的就是依赖属性通过CLR属性进行了封装,而附加属性则通过静态方法封装。

我们这样来使用上面的代码:

<Slider local:MyTooltip.Attached="True" Minimum="" Maximum=""/>

效果如下所示:

假如有这样一种场景,在属性值继承那里的例子中,我们要求最里面的StackPanel中的所有TextBlock采用Script MT字体。

很明显,在TextBlock上直接设置不是民智之举,因为可能有很多。这时想到属性值继承,在StackPanel上设置。然而不幸的是,StackPanel没有FontFamily属性(也不需要)。这时,附加属性粉墨登场了。

<Window x:Class="DependencyPropertyDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" FontSize="20">
<Grid>
<StackPanel>
<TextBlock x:Name="tb1" Text="WPF" FontSize="50"/>
<StackPanel Orientation="Horizontal" TextElement.FontFamily="Script MT">
<TextBlock x:Name="tb2" Text="WF" Width="100px"/>
<TextBlock Text="SliverLight" />
</StackPanel>
<StatusBar x:Name="sb1">WPF is Working!
<Button Content="Pass"></Button>
</StatusBar>
</StackPanel>
</Grid>
</Window>

前面说过TextBlock和Control的一些文本相关的属性都是通过TextElement共享的方式获得的。这里将TextElement的FontFamily属性附加到StackPanel上,然后TextBlock进行了属性值的继承。当XAML的解析器或编译器遇到这样的语法时,会先去要求附加属性的提供者(TextElement)提供SetFontFamily这样的静态方法来设置相应的属性值。过程式代码这么实现:

TextElement.SetFontFamily(this.sp1, new FontFamily("Script MT"));

效果图如下:

现在是不是觉得附加属性也并不神奇了。说到底,依赖属性是附加在DependencyObject上,而附加属性是附加在任意对象上,本质上是一样的。

总结

依赖属性和依赖对象在WPF中举足轻重,重点是要了解依赖属性内部的工作机制。

WPF学习(5)依赖属性的更多相关文章

  1. WPF学习笔记——依赖属性(Dependency Property)

    1.什么是依赖属性 依赖属性是一种可以自己没有值,并且通过Binding从数据源获得值(依赖在别人身上)的属性,拥有依赖属性的对象被称为"依赖对象". 依赖项属性通过调用 Regi ...

  2. WPF 精修篇 依赖属性

    原文:WPF 精修篇 依赖属性 依赖属性使用场景 1. 希望可在样式中设置属性. 2. 希望属性支持数据绑定. 3. 希望可使用动态资源引用设置属性. 4. 希望从元素树中的父元素自动继承属性值. 5 ...

  3. WPF中的依赖属性

    1. WPF中的依赖属性 依赖属性是专门基于WPF创建的.在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使用方法与普通的属性是相同的. 1.1 依赖属性提供的属性功能 资源 数据绑定 样式 ...

  4. WPF教程:依赖属性

    一.什么是依赖属性 依赖属性就是一种自己可以没有值,并且可以通过绑定从其他数据源获取值.依赖属性可支持WPF中的样式设置.数据绑定.继承.动画及默认值. 将所有的属性都设置为依赖属性并不总是正确的解决 ...

  5. [No000012D]WPF(5/7)依赖属性

    介绍 WPF带来了很多传统 Windows 应用程序没有的新特性和选择.我们已经讨论了一些 WPF 的特性,是时候更进一步介绍其他特性了.当你读完这个系列之前的文章,我希望你已经或多或少地了解了 WP ...

  6. (原创)2. WPF中的依赖属性之二

    1 依赖属性 1.1 依赖属性最终值的选用 WPF属性系统对依赖属性操作的基本步骤如下: 第一,确定Base Value,对同一个属性的赋值可能发生在很多地方.还用Button的宽度来进行举例,可能在 ...

  7. 【转】【WPF】关于依赖属性的ValidateValueCallback,PropertyChangedCallback和CoerceValueCallback的执行顺序

    三个回调对应依赖属性的验证过程,改变过程和强制转换过程. class Dobj : DependencyObject { //依赖属性包装 public int MyProperty { get { ...

  8. WPF usercontrol 自定义依赖属性

    1.依赖属性不同意一般属性,一般属性主要定义在对象中,而依赖属性是存在一个特殊的依赖属性表中.2.当我们触发改变值时,需要通过SetValue这种方式进行触发. UserControl1.xaml: ...

  9. WPF 主动触发依赖属性的 PropertyChanged

    需求背景 需要显示 ViewModel 中的 Message/DpMessage,显示内容根据其某些属性来确定.代码结构抽象如下: // Model public class Message : IN ...

  10. WPF快速入门系列(2)——深入解析依赖属性

    一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列.尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF.今天继续分享WPF中一个最 ...

随机推荐

  1. android2.2应用开发之IccCard(sim卡或USIM卡)

    tyle="margin:20px 0px 0px; font-size:14px; line-height:26px; font-family:Arial; color:rgb(51,51 ...

  2. 开放搜索服务OpenSearch

    开放搜索服务系统架构:从系统.平台到开放服务 搜索是各类网站和数据类APP的标配功能.目前开发者一般基于开源搜索系统,例如ElasticSearch.Solr.Sphinx等自己搭建搜索服务,系统定制 ...

  3. HDOJ 5147 Sequence II 树阵

    树阵: 每个号码的前面维修比其数数少,和大量的这后一种数比他的数字 再枚举每一个位置组合一下 Sequence II Time Limit: 5000/2500 MS (Java/Others)    ...

  4. CSS预处理器——Sass、LESS和Stylus实践

    CSS(Cascading Style Sheet)被译为级联样式表,做为一名前端从业人员来说,这个专业名词并不陌生,在行业中通常称之为“风格样式表(Style Sheet)”,它主要是用来进行网页风 ...

  5. Blend4精选案例图解教程(四):请给我路径指引

    原文:Blend4精选案例图解教程(四):请给我路径指引 路径在界面设计中,可以起到很好的辅助作用,我常常使用它来对元素进行规则排列和非规则排列控制. 本次教程将演示,Blend中路径的常规用法. 1 ...

  6. Makefile学习(一)[第二版]

    简单介绍 1)make:利用 make 工具能够自己主动完毕编译工作.这些工作包含:假设仅改动了某几个源文件,则仅仅又一次编译这几个源文件[make通过比对对应的.c文件与.o文件的时间];假设某个头 ...

  7. SQLSERVER2014的内存优化表

    SQL Server 2014中的内存引擎(代号为Hekaton)将OLTP提升到了新的高度. 现在,存储引擎已整合进当前的数据库管理系统,而使用先进内存技术来支持大规模OLTP工作负载. 就算如此, ...

  8. UVa 572 Oil Deposits(DFS)

     Oil Deposits  The GeoSurvComp geologic survey company is responsible for detecting underground oil ...

  9. 【转】HLSL基础

    原文地址http://blog.csdn.net/chpdirect1984/article/details/1911622 目录 前言 1.HLSL入门 1.1什么是着色器 1.2什么是HLSL 1 ...

  10. 交换A与B值的四种方法

    在网上看到了这样一道面试题,"int A=5,int B=2,怎样交换A与B的值",或许这是一道简单到不能再简单的题,但能作为一道面试题,肯定有其独特之处 大多数人会通过定义第三个 ...