WPF学习(5)依赖属性
今天我们来学习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。优先级从高到低如下排列:
- 本地值(LocalValue):通过调用SetValue方法设值,表现为在XAML页面直接赋值或者通过过程式代码赋值
- 样式触发器
- 模板触发器
- 样式设置程序
- 主题样式触发器:主题样式就是WPF系统内置的一些样式
- 主题样式设置程序
- 属性值继承:子类从父类继承过来的依赖属性值
- 默认值:在注册时设置的初始值
如果属性值是一个表达式的话,会转换成具体的值。
如果属性值是一个动画的话,它可以改变或者替代当前的属性值。
如果注册时给出了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)依赖属性的更多相关文章
- WPF学习笔记——依赖属性(Dependency Property)
1.什么是依赖属性 依赖属性是一种可以自己没有值,并且通过Binding从数据源获得值(依赖在别人身上)的属性,拥有依赖属性的对象被称为"依赖对象". 依赖项属性通过调用 Regi ...
- WPF 精修篇 依赖属性
原文:WPF 精修篇 依赖属性 依赖属性使用场景 1. 希望可在样式中设置属性. 2. 希望属性支持数据绑定. 3. 希望可使用动态资源引用设置属性. 4. 希望从元素树中的父元素自动继承属性值. 5 ...
- WPF中的依赖属性
1. WPF中的依赖属性 依赖属性是专门基于WPF创建的.在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使用方法与普通的属性是相同的. 1.1 依赖属性提供的属性功能 资源 数据绑定 样式 ...
- WPF教程:依赖属性
一.什么是依赖属性 依赖属性就是一种自己可以没有值,并且可以通过绑定从其他数据源获取值.依赖属性可支持WPF中的样式设置.数据绑定.继承.动画及默认值. 将所有的属性都设置为依赖属性并不总是正确的解决 ...
- [No000012D]WPF(5/7)依赖属性
介绍 WPF带来了很多传统 Windows 应用程序没有的新特性和选择.我们已经讨论了一些 WPF 的特性,是时候更进一步介绍其他特性了.当你读完这个系列之前的文章,我希望你已经或多或少地了解了 WP ...
- (原创)2. WPF中的依赖属性之二
1 依赖属性 1.1 依赖属性最终值的选用 WPF属性系统对依赖属性操作的基本步骤如下: 第一,确定Base Value,对同一个属性的赋值可能发生在很多地方.还用Button的宽度来进行举例,可能在 ...
- 【转】【WPF】关于依赖属性的ValidateValueCallback,PropertyChangedCallback和CoerceValueCallback的执行顺序
三个回调对应依赖属性的验证过程,改变过程和强制转换过程. class Dobj : DependencyObject { //依赖属性包装 public int MyProperty { get { ...
- WPF usercontrol 自定义依赖属性
1.依赖属性不同意一般属性,一般属性主要定义在对象中,而依赖属性是存在一个特殊的依赖属性表中.2.当我们触发改变值时,需要通过SetValue这种方式进行触发. UserControl1.xaml: ...
- WPF 主动触发依赖属性的 PropertyChanged
需求背景 需要显示 ViewModel 中的 Message/DpMessage,显示内容根据其某些属性来确定.代码结构抽象如下: // Model public class Message : IN ...
- WPF快速入门系列(2)——深入解析依赖属性
一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列.尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF.今天继续分享WPF中一个最 ...
随机推荐
- android2.2应用开发之IccCard(sim卡或USIM卡)
tyle="margin:20px 0px 0px; font-size:14px; line-height:26px; font-family:Arial; color:rgb(51,51 ...
- 开放搜索服务OpenSearch
开放搜索服务系统架构:从系统.平台到开放服务 搜索是各类网站和数据类APP的标配功能.目前开发者一般基于开源搜索系统,例如ElasticSearch.Solr.Sphinx等自己搭建搜索服务,系统定制 ...
- HDOJ 5147 Sequence II 树阵
树阵: 每个号码的前面维修比其数数少,和大量的这后一种数比他的数字 再枚举每一个位置组合一下 Sequence II Time Limit: 5000/2500 MS (Java/Others) ...
- CSS预处理器——Sass、LESS和Stylus实践
CSS(Cascading Style Sheet)被译为级联样式表,做为一名前端从业人员来说,这个专业名词并不陌生,在行业中通常称之为“风格样式表(Style Sheet)”,它主要是用来进行网页风 ...
- Blend4精选案例图解教程(四):请给我路径指引
原文:Blend4精选案例图解教程(四):请给我路径指引 路径在界面设计中,可以起到很好的辅助作用,我常常使用它来对元素进行规则排列和非规则排列控制. 本次教程将演示,Blend中路径的常规用法. 1 ...
- Makefile学习(一)[第二版]
简单介绍 1)make:利用 make 工具能够自己主动完毕编译工作.这些工作包含:假设仅改动了某几个源文件,则仅仅又一次编译这几个源文件[make通过比对对应的.c文件与.o文件的时间];假设某个头 ...
- SQLSERVER2014的内存优化表
SQL Server 2014中的内存引擎(代号为Hekaton)将OLTP提升到了新的高度. 现在,存储引擎已整合进当前的数据库管理系统,而使用先进内存技术来支持大规模OLTP工作负载. 就算如此, ...
- UVa 572 Oil Deposits(DFS)
Oil Deposits The GeoSurvComp geologic survey company is responsible for detecting underground oil ...
- 【转】HLSL基础
原文地址http://blog.csdn.net/chpdirect1984/article/details/1911622 目录 前言 1.HLSL入门 1.1什么是着色器 1.2什么是HLSL 1 ...
- 交换A与B值的四种方法
在网上看到了这样一道面试题,"int A=5,int B=2,怎样交换A与B的值",或许这是一道简单到不能再简单的题,但能作为一道面试题,肯定有其独特之处 大多数人会通过定义第三个 ...