依懒
属性是WPF推出的概念,使用它的好处很多,这一点在Silverlight里也得到了继承,由于这个概念比较抽象,它不同于.NET中
一般的属性类型,所以我个人整理了一些关于依赖
属性的
文章希望对大家有用。
WPF揭秘:
依赖
属性(Dependency Properties)
WPF引入了一种新的
属性类型,称作“依赖
属性”,它可以用在外观风格、
自动化数据绑定以及动画等方面。我们在第一次遇到这个概念的时候,可能会有一些迷惑,因为它把拥有简单字段、
属性、
方法和事件的.NET类型弄得
有点复杂。但是,当我们理解了它可以用来
解决什么样的问题时,就会非常喜欢它。
依赖
属性依赖多个能够在任意时刻及时确定
属性值的提供器(provider),这些提供器可以是
一个不断改变
属性值的动画,也可以是
一个可以将
属性值传递到子元素的父元素。它的最大的特点无疑是它能够提供变更
通知(change notification)的能力。
为
属性加入这种能力,可以使得我们在XAML中直接使用WPF的各种丰富
功能。WPF“说明性友好”设计的关键就是为了
支持对
属性的大量使用(这就是说,XAML就像XML,可以为元素设置各种各样的
属性,“说明性友好”的目的就是追求有效地用利
属性),比如Button就拥有96个公共
属性!XAML可以简单地设置
属性而无需任何程序
代码,但是如果在依赖
属性中没有额外的引擎,那么即使在没有编写
代码的情况下想要获得
属性值都是困难的。
理解依赖
属性的细节通常只对
自定义控件开发者来说比较重要。有时候,我们可能仅需要知道风格类和动画类的依赖
属性。在开发一段时间WPF应用后,我们可能发现,我们甚至希望所有的
属性都是依赖
属性!
依赖的
属性的实现
实际上,依赖
属性就是连接了一些WPF基础结构的一般.NET
属性。它全部通过WPF API完成,对任何.NET语言(除了XAML)来说是完全透明的。
例:标准依赖
属性的实现(Button的IsDefault)
public class Button : ButtonBase
{
// 依赖
属性
public static readonly DependencyProperty IsDefaultProperty;
static Button()
{
//
注册属性
Button.IsDefaultProperty = DependencyProperty.Register("IsDefault",
typeof(bool),typeof(Button),
new FrameworkProperty
Metadata(
false,
new PropertyChangedCall
BACk(OnIsDefaultChang
E))
);
// ……
}
// .NET
属性包装器(可选的)
public bool IsDefault
{
get
{ return (bool)GetValue(Button.IsDefaultProperty
); }
set
{ SETVALue(Button.IsDefaultProperty,
value); }
}
//
属性变更回调(可选的)
private static void OnIsDefaultChanged(
DependencyOb
ject o,DependencyPropertyChangedEventArgs
E) { }
// ……
}
静态的IsDefaultProperty字段是真正的依赖
属性,它是Sy
stem.Window
s.DependencyProperty类型的。所有的依赖
属性习惯上以Property为后缀,且其访问权限是pulbic和static。依赖
属性一般由DependencyProperty.Register
方法创建,它需要
一个名称(IsDefault)、
一个属性类型(bool)和声明拥有这个
属性的类型(Button)。
通过该
方法的不同重载,我们可以有选择地传递处理
自定义属性元数据,也可以编写那些用于处理
属性值变更、强制转换(coerce)和验证(vali
date)的回调
方法。Button在其静态构造
方法中
调用了Register
方法的一种重载,并向其提供了依赖
属性的
默认值(
false),并且为处理变更
通知(change notification)附加了
一个委托。
IsDefault
属性最终通过
调用继承自依赖
属性基类Sy
stem.Window
s.DependencyOb
ject的GetValue和
SETVALue
方法实现了对IsDefaultProperty的访问器。GetValue返回由
SETVALue最后一次设置的值,或是当
SETVALue从未被
调用时,返回
属性注册时提供的
默认值。IsDefault
属性有时被称作“
属性包装器”(property wrapper),它不是必须的,如Button的使用者可以直接
调用公共
方法GetValue和
SETVALue来
获取以来
属性的值。尽管如此,定义
一个.NET
属性仍旧十分有用,因为它不但令我们以编程方式读/写字段更加自然,而且还令XAML可以直接设置
属性。(意思是,如果在元素对象的类型中定义了.NET
属性,则我们在XAML中操作元素的
属性,就如同在元素对象上操作那个
相应的.NET
属性;相反地,如果
我们没有定义.NET
属性,那么在XAML中也无法简单通过操作元素的
属性来调整元素对象的状态,
因此最好为依赖
属性定义对应的
属性包装器。)
在XAML在使用依赖
属性的时候
需要注意,尽管XAML编译器在编译时需要
属性包装器,但是在运行时,WPF并不使用
属性包装器,而是直接
调用底层GetValue和
SETVALue
方法。因此,在依赖
属性对应的包装器中,除了
调用GetValue和
SETVALue之外,不要包含任何逻辑。所有的WPF内建
属性包装器都遵循这条规则,因此在我们编写新的包含依赖
属性的类型时,也应当这么做。
像上例那样表示
一个简单布尔
值的属性,表面上看起来有些�嗦,但由于GetValue和
SETVALue内部使用了
一个高效的存储结构(意思大概是说,依赖
属性通过Register
方法注册到
一个数据结构中,这个结构的存取效率很好,因此通过那两个
方法操作依赖
属性,
相应的开销也会很小),且IsDefaultProperty是静态字段,这使得依赖
属性的实现相对于
一般的.NET
属性而言,由于不用为
每个实例都分配内存,从而节省内存的开销。
依赖
属性带来的好处不仅仅在内存使用方面,它还集中化并且标准化了许多
属性的实现
代码,这些
属性实现
代码有用来检查线程访问的,也有用来引发重绘所含元素的。例如,在元素
属性值变更的时候(如Button的
BACkground
属性),元素需要被重绘,这可以通过传递FrameworkPropert
ymatadataOption
s.AffectsRender标志到重载的DependencyProperty.Register
方法来实现。此外,依赖
属性还允许三种重要的特性:变更
通知、
属性值继承以及对多种提供器的
支持。
变更
通知
在依赖
属性值发生变化的时候,WPF能够
自动触发一些依赖这个
属性元数据的动作,这些动作可以是重绘
相应的元素、更新当前布局以及刷新数据绑定等。由变更
通知实现的
一个有趣的特性被称作“
属性触发器”(property
triggers),它允许我们不编写程序
代码就可以在
属性值发生改变的时候执行
自定义动作。例如,我们把鼠标悬停在About Dialog(参看之前发布的
文章)的按钮上时,按钮的文本立刻变成蓝色;移开鼠标,按钮的文本又恢复成黑色。为了实现这样的想法,我们在没有
属性触发器的情况下,可以为每
一个按钮附加事件处理器。鼠标悬停的事件是MouseEnter,鼠标移开的事件是Mou
SELEave。
<Button MouseEnter="Button_MouseEnter" Mou
SELEave="Button_Mou
SELEave"
@H_768_21
@minWidth="75" Margin="10">
Help</Button>
<Button MouseEnter="Button_MouseEnter" Mou
SELEave="Button_Mou
SELEave"
@H_768_21
@minWidth="75" Margin="10">OK</Button>
对应的两个事件处理
函数为:
// 当鼠标悬停在按钮上时,将按钮的背景颜色改为蓝色
private void Button_MouseEnter(ob
ject sender,MouseEventArgs
E)
{
Button b = sender as Button;
if (b
!= null) b.Foreground = Brushes
.blue;
}
// 当鼠标离开按钮时,恢复按钮的背景颜色
private void Button_Mou
SELEave(ob
ject sender,MouseEventArgs
E)
{
Button b = sender as Button;
if (b
!= null) b.Foreground = Brushes
.black;
}
如果使用
属性触发器,我们可以大幅简化上面的实现,仅通过XAML就可以完成相同的任务:
<
trigger Property="IsMou
SEOver" Value="True">
<Setter Property="Foreground" Value="Blue" />
</
trigger>
触发器可以作用在按钮的IsMou
SEOver
属性上,它的值在MouseEnter事件被触发的时候变为true,在Mou
SELEave被触发的时候变为
false。值得注意的是,我们并不需要在IsMou
SEOver变为
false时将Foreground恢复成黑色,
这个工作完全由WPF
自动完成。
触发器需要附加在每
一个按钮上,但不幸的是,由于WPF 3.0中的人为限制,我们不能像在Button这样的元素上直接应用触发器,而只能将其应用在其Style对象内部,如下:
<Button MinWidth="75" Margin="10" Content="
Help">
<Button.Style>
<style="COLOR: #a31515">Style TargetType="
{x:Type Button}">
<Style.
triggers>
<
trigger Property="IsMou
SEOver" Value="True">
<Setter Property="Foreground" Value="Blue" />
</
trigger>
</Style.
triggers>
</Style>
</Button.Style>
</Button>
属性触发器只是WPF
支持的三类触发器中的一类,其它两类是数据触发器和事件触发器,将会在以后介绍。此外,尽管FrameworkElement的
triggers
属性是
一个triggerBase类型(三类触发器的基类)的读/写集合,但其在WPF 3.0中只能包含事件触发器,这是由于WPF团队来不及完全实现造成的,因此,如果向该集合
添加属性触发器或数据触发器,将导致运行时异常。(尚未在VS2008中试验这么做是否
会出现异常)
属性值继承(简称
属性继承)与传统的面向对象继承不同,它是指
属性值可以沿着元素树向下传递的过程。
<Window x:Class="Test.Window1"
title="WPF揭秘" SizeToContent="WidthAndHeight"
FontSize="30" FontStyle="Italic"
BACkground="OrangeRed">
<StackPanel>
<Label FontWeight="Bold" FontSize="20" Foreground="White">
<Label>(
C)2006 SAMS 出版集团</Label>
<List
BoxItem>第一章</List
BoxItem>
<List
BoxItem>第二章</List
BoxItem>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button MinWidth="75" Margin="10">
Help</Button>
<Button MinWidth="75" Margin="10">OK</Button>
<StatusBar>您已经
注册了本产品。</StatusBar>
</StackPanel>
</Window>
下图展示了以显式方式设置Window元素的FontSize和FontStyle依赖
属性后,整个窗体的变化情况。
为这两个
属性设置的值将会沿着元素树向下传递,并被
相应的子元素继承(即将子元素的对应
属性也被设定为这个值)。上例中,Button、Label和List
BoxItem都受到了影响,但由于第
一个Label显式设置了FontSize,从而其字体大小未受影响。值得注意的是,StatusBar中的文本并没有受到这两个
值的影响,尽管它与其它控件相同,也包含这两个
属性。由于以下两种原因,
属性值的继承显得有些微妙:
(1)并不是所有的依赖
属性都参与
属性值的继承(依赖
属性可以通过向DependencyProperty.Register传递FrameworkPropert
ymatadataOption
s.Inherits来选择是否参与继承)
StatusBar
显示的结果由第二种原因导致的。一些如StatusBar、Menu和Tooltip控件的内部将它们的字体
属性设定为匹配当前系统的设置。这种结果有些令人迷惑,因为这样的控件阻止了继承
属性值沿着元素树继续传递。例如,当我们在StatusBar中加入
一个Button作
为其逻辑子元素,那么Button的FontSize和FontStyle都将保持
默认值,这与处于StatusBar之外的那些Button不同。
属性值继承
本来是用来操作元素树的,但是它也可以用于在其它情境。例如,
属性值可以传递到某个XML意义上的子元素,而这个子元素并非逻辑树或视觉树的子元素。这些伪子元素可以是某个元素的触发器,也可是任意
属性值,只要它是
一个从Freezable派生的对象就可以。(暂不明)
对多种提供器的
支持
WPF包含许多强大的机制,这些机制尝试独立地设置依赖
属性值。WPF需要五个步骤来计算依赖
属性的最终值:确定基本值―>计算表达式(如果有)―>应用动画―>强制―>验证。
第一步 确定基本值
基本
值的计算被分解成许多
属性值的提供器,下列提供器可以设置大部分依赖
属性,按从高到低的优先级排列:
(1)局部值(local value)提供器;
(2)样式触发(style
trigger)提供器;
(3)模板触发(template
trigger)提供器;
(4)样式设定(style setter)提供器;
(5)
主题样式触发(theme style
trigger)提供器;
(6)
主题样式设定(theme style setter)提供器;
(7)
属性值继承(property value inheritance)提供器;
(8)
默认值(default value)提供器
我们已经见过一些
属性值提供器了,比如
属性值继承提供器。从技术上说,局部值提供器是任何
调用了DependencyOb
ject.
SETVALue的对象,但是它通常可以被看作一种在XAML或程序
代码中给普通
属性赋
值的操作。
默认值提供器设定的值是依赖
属性在
注册时设定的那个值,因此其优先级最低。(详情参见依赖
属性(1)对依赖
属性实现的介绍)
这个优先级顺序解释了为何StatusBar的FontSize和FontStyle
属性没有受
属性值继承影响,这是
因为StatusBar已经被
主题样式提供器(第6级)设定为匹配系统的值。尽管它的优先级超过了
属性值继承提供器(第7级),但是我们仍旧可以通过较高优先级的提供器来设定字体
属性值。
第二步 计算表达式
如果来自第一步的值是
一个表达式(一种派生自Sy
stem.Window
s.Expression的对象),则WPF负责执行
一个将表达式转换为具体
值的特殊计算步骤。在WPF 3.0中,表达式仅可是动态资源或数据绑定,未来版本的WPF可能会允许其它种类的表达式。
第三步 应用动画
在
一个或多个动画正在运行的时候,它们拥有更改当前
属性值(来自第二步的值)的权利。因此,动画比任何其它
属性值提供器的优先级都高,这对WPF初学者来说有些难度。
第四步 强制(Coerce)
在所有的
属性值提供器完成设定后,WPF将结果
属性值传递到为依赖
属性注册的CoerceValueCall
BACk委托中。我们可以
在这个委托内编写自己的
代码,用以重新计算依赖
属性值或执行强制转换。例如,WPF内建控件ProgressBar使
用这个回调来约束其Value依赖
属性。这个
属性的值应在Minimum和Maximum之间,但如果输入值小于Minimum,则其返回Minimum值,如果输入值大于Maximum,则其返回Maximum值。
(这个CoerceValueCall
BACk在MSDN中的说法是“为只要重新计算依赖项
属性值或专门请求强制转换时就
调用的
方法提供
一个模板,此回调的实现应检查 baseValue 中的值,并根据该值或其类型确定该值是否需要进一步进行强制转换”。按我自己的理解,这个委托的作用就是为上一步计算好的
属性值
添加使其合理化的逻辑。
MSDN为这个回调提供了
一个示例(其中d是该
属性所在的对象,value是该
属性在尝试执行任何强制转换之前的新值):
private static ob
ject CoerceButtonColor(DependencyOb
ject d,ob
ject
value)
{
"3">ShirtTypes newShirtType = (d as Shirt).ShirtType;
if (newShirtType == ShirtType
s.Dress || newShirtType == ShirtTypes
.bowling)
{
return ButtonColors
.black;
}
return ButtonColor
s.None;
}
从中
可以看出,依赖
属性ButtonColor的值依赖于对象d的ShirtType,但并没有使用上一步计算好的值value。)
第五步 验证
最终,经过强制转换的值被传递到为依赖
属性注册的ValidateValueCall
BACk委托中。如果输入值是合法的,则此委托必须返回true;否则,必须返回
false。返回
false将导致
一个取消整个过程的异常。
(MSDN中的示
例:
private static bool ShirtValidateCall
BACk(ob
ject
value)
{
ShirtTypes sh = (ShirtTypes)value;
return (sh == ShirtType
s.None || sh == ShirtTypes
.bowling || sh == ShirtType
s.Dress || sh == ShirtType
s.Rugby || sh == ShirtType
s.Te
E);
}
有此可见,
在这个回调的实现中,应当加入验证
属性值的范围的逻辑。同理,在ProgressBar
为其double类型的ValueProperty依赖
属性实现的验证回调中,判断了
属性值是否是合法的double数据。)
如果我们想要查看依赖
属性值是由那
一个提供器设置的,可以使用静态的DependencyProperty
Helper.GetValue
source
方法来调试。该
方法返回
一个ValueSoruce结构,它包含
一个暴露基本值来自于何方的BaseValue
source枚举,以及对应2~4步的IsExpression、IsAnimated、IsCoerced
属性。当我们在前面的StatusBar实例上为FontSize和FontStyle
调用这个
方法后,BaseValue
source的返回值为DefaultStyle,这说明它的值来自于
主题风格设定提供器(
主题风格有时也称作
默认风格,对应
主题风格触发器就是DefaultStyle
trigger)。此外切记不要在产品
代码中使
用这个方法。
在前面通过事件处理器设置按钮
文本颜色的示例中,有
一个问题值得注意:在Mou
SELEave事件处理器中,按钮的Foreground被赋予了局部值Brushes
.black(局部值提供器)。这与按钮的初始状态不同,其初始状态是由
主题风格设定提供器设置的。如果
主题变换了,且新
主题(或其它优先级较高的提供器)尝试改变Foreground的
默认值,那么局部值提供器会胜出(因为它的优先级高于
主题风格提供器),并将Foreground设置为黑色。对于这种情况,我们可能会希望清除这个局部值,以
便可以使用
主题的
默认设定。DependencyOb
ject提供了
一个ClearValue
方法,可以清除局部值:
b.ClearValue(Button.ForegroundProperty
);
值得注意的是,在IsMou
SEOver上的触发器与此不同,当其处于“未触发”状态时,将会忽略对
属性值的计算。
Silverlight学习笔记:“依赖对象”和“依赖
属性”的介绍:
学习silverlight没什么门路,跑了几趟书店都没买到sl的书,真慢。今天装了vs2008,于是打开vs的对象浏览器,看看sl里面的类库都是什么德行。
那么先从xaml文档的根节点元素开始看吧~~~ 查看之下还真是看不出什么所以然~~~
就这个图能看出什么端倪?所以说要搞清楚
一个类库还是得先从她的身家背景开始看起...
Canvas的一家
据说谁的祖先都是从单细胞(ob
ject)发展而来的,那么从第二代开始看....
二代目:
Sy
stem.Window
s.DependencyOb
ject “依赖对象”
一个抽象类,好像没什么好看的。
FindName是通过姓名找孩子(说也奇怪,到DependencyOb
ject还没有Children这个
属性,也没有WPF中独生子女的Content
属性,这是干什么用的?找谁家的孩子?),下面的Name是自己本身的名字……其它的,看不懂……先放着……
第三代:
Sy
stem.Window
s.Media.Visual “可视对象” 也是抽象类
增加了2个
方法,捕捉"鼠标事件"和释放"鼠标事件"
由于子元素存在于父元素“体内”,点击子元素实际上也应该“
通知”父元素,所以鼠标事件是会向
上传递的。
但是
用户点击父元素的任意一部分,父元素怎么知道到底有没有点到什么子元素啊?总之,就是为了弄清楚咯吱哪里该谁笑的疑惑,就弄了这两个
方法。总的来看,可能为了兼容1.0吧,封装的好差的说。
本来应该由事件参数做的事情变成sender
做了,不过从行为来看,这样也合理了,总之就是不同于.net winform。
讲个故事吧,爸爸和儿子守着一条小道,儿子在前。前面跑来一只兔子(或者是一只很身手很差的老鼠),那么如果儿子捕获了兔子(儿子很厉害,想捉一定会捉住,也就是执行CaptureMouse()返回tru
E),那么父亲那里执行CaptureMouse()肯定会返回
false了,除非儿子那里抓了又放了,就是ReleaseMouseCapture()了。
情况如同下面的
代码:(txt是
一个TextBlock)
public partial class Page : Canvas
{
public void Page_Loaded(ob
ject o,EventArgs
E)
{
InitializeComponent(
);
thi
s.Mou
SELEftButtonDown += new MouseEventHandler(Page_Mou
SELEftButtonDown
);
txt.Mou
SELEftButtonDown += new MouseEventHandler(TextBlock_Mou
SELEftButtonDown
);
}
void Page_Mou
SELEftButtonDown(ob
ject sender,MouseEventArgs
E)
{
txt.Text += (sender as Visual).CaptureMouse().To
String(
);
//如果点击了TextBlock,结果会是
false,因为在TextBlock那已经执行了CaptureMouse()
//如果点击了Canvas的其他部分(如空白部分),结果会是true,因为变成是第一手处理了
}
void TextBlock_Mou
SELEftButtonDown(ob
ject sender,MouseEventArgs
E)
{
txt.Text += (sender as Visual).CaptureMouse().To
String(
);//因为是第
一个执行,会是true
//(sender as Visual).ReleaseMouseCapture(
);如果这句被执行了(放开兔子),那么Canvas那里就会是true了
}
}
很多很复杂……
第五代了:
Sy
stem.Window
s.FrameworkElement “框架元素” 抽象的
增加了重要的“父元素”
属性 以及外观
属性:高 和 宽
六代:
子承父业,到了Sy
stem.Window
s.Control
s.Panel,长成了容器级的元素
Children
属性管理子元素,它是@L_809_13
@ms.Interna
l.Collection<Visual>类型的子类
最后到了Canvas 在Penel的基础上
增加了2个依赖
属性
现在没得逃避了。
所谓依赖
属性,就是通过被依赖的对象起作用,就如
一个TextBlock对象是Canvas对象的子对象,那么Canvas对象就通过Canva
s.Left对TextBlock起管理的作用,但离开了Canvas,这个
属性也就没有作用了。就好像“母亲”拥有的“母爱”依赖于“孩子的存在”而存在,当孩子离开了母亲,这种“爱”也就无从谈起了。
依赖关系,也就是WPF中“视觉元素”相互依赖的奥义了。而所有的依赖
属性,都是父元素与子元素之前牵扯不断的关系,而这些
属性都是为“视觉体现”而服务的。
(以上是我自己的解释)
而依赖对象是怎么工作的呢?其实打字到这一行之前我也不清楚,只是以前看过一篇WPF的关于依赖
属性的
文章,留下一点模糊的记忆,然后刚才回去又仔细查阅了对象信息,发现原来DependencyOb
ject类的两个重要
方法有玄机:
其实就是DependencyOb
ject里面有
一个隐藏的“字典”对象,通过父元素
调用的,
比如Canvas
调用了TextBlock的
SETVALue
方法
thi
s.FindName("aTextBlock").
SETVALue(Canva
s.LeftProperty,
20);
相当于,父对象把
一个值(这里是20)存进了子对象的“字典”里,而父亲的(静态只读成员)LeftProperty就是那把钥匙,也就是Key!!!!哈哈哈 ,终于理解了。那把钥匙可以打开所有子对象的“字典”,取出匹配于LeftProperty的值。
foreach (Visual child in thi
s.Children)
{
double left = (doubl
E)child.GetValue(Canva
s.LeftProperty
);
//....
}
这也就是为什么脚本可以这么写<TextBlock Canva
s.Left="20">,
而TextBlock类却没有
一个Canva
s.Top
属性的原因了,而xaml中声明式的指定Canva
s.Left,是由xaml解析引擎的时候
自动完成的。
thi
s.FindName("aTextBlock").
SETVALue(Canva
s.LeftProperty,
20);
就这样了。。。
续:
经过继续研究,发现所谓“依赖
属性”的作用实在“博大”,它可以使用于本对象以外的"包装类"对其包装,如xaml解析对象通过
调用SETVALue对UI对象进行初始化;渲染引擎使用GetValue取得它与父元素相依赖的
属性值(如在渲染Canvas的子对象时需要子元素中Canva
s.Top
属性的值)
“依赖
属性集”既可以放本身需要的
属性,也可以放其他元素的所需要的信息集。以“键”和“值”的方式存取。。。。很方
便,很强大!
.NET Framework 3.0引入了
一个新的
属性类型叫依赖
属性,WPF,WF都在使用依赖
属性用来实现样式化,数据绑定等.我们更多的使用依赖
属性是为了让父元素的
属性值在逻辑树上慢慢的传递到其子元素中,从而可以在整个可是父元素的逻辑子元素中共享
属性值.WF就是依靠依赖
属性来在工作流中的各Activity间传递
属性值的. 所以,依赖
属性内建的传递变更
通知的能力是其最大特征.
如果你想让
属性在
一个包含
内容子控件树的整个逻辑控件树中都有效并共享值时,你仅仅只需要将这个
属性声明为依赖
属性即可,WPF会通过内建的架构来
支持属性的共享. 而在工作流中我们经常
需要用到依赖
属性,它保证了在
一个工作流实例中,多个组件共享了同
一个值. 幸运的是在WPF中大部分空
间的属性都是依赖
属性,这让我们应用时非常方
便,而你并不需要着后边发生了什么。
依赖
属性其实也是普通的.NET
属性,只是通过DependencyProperty.Register
方法将普通的.NET
属性注册为依赖
属性。在依赖
属性的声明中,其实对应的普通.NET
属性并不是必需的,因
为其内部的GetValue和
SETVALue
方法是公开的,依赖
属性的使用者可以通过
调用GetValue/
SETVALue而放弃对普通.NET
属性的依赖。但建立在普通.NET熟悉之上更符合我们通常的做法,而且这样有利于在XAML中设置
属性。
public class Button:ButtonBase
{
//依赖
属性
public static readonly DependencyProperty IsDependencyProperty;
static Button()
{
//
注册依赖
属性
Button.IsDependencyProperty = DependencyProperty.Register("IsDefault",
typeof(bool),
new FrameworkProperty
Metadata(
false,
new PropertyChangedCall
BACk(OnIsDefaultChanged))
);
}
//.NET
属性(可选)
public bool IsDefault
{
get
{ return (bool)GetValue(Button.IsDefaultProperty
); }
set
{ SETVALue(Button.IsDefaultProperty,
value); }
}
//
属性改变时的回调
private static void OnIsDefaultChanged(DependencyOb
ject o,
DependencyPropertyChangedEventArgs
E)
{...}
}
在上面的示例
代码中是标准的实现依赖
属性定义的
方法,这里
需要注意的是依赖
属性始终是定义在普通.NET
属性之上的用Register静态
方法注册的特殊
属性,即
便这里的.NET
属性(如IsDefault
属性)不是必需的。另
一个需要注意的地方是,通过声明依赖
属性,我们多了
一个控制
属性改变时的回调
方法(即
便这同样可以通过声明委托和事件来定义,但这里我们什么都没做,为什么不用呢?),这样的好处是我们可以在
属性改变的时候做些我们想做的事情,而我们却省去了手动声明事件的任务。其实回调
函数的存在是为了让我们保证在
属性包装器中(IsDefault
属性)仅仅使用标准的GetValue/
SETVALue而不用任何其他逻辑,转而将
自定义逻辑写入回调
函数--这样做是为了遵循WPF的统一设计原则,让XAML 设置
属性与使用过程式
代码设置
属性保持一致。
触发器
依赖
属性的实现让我们可以在
一个局部范围内保持
属性值的共享,这样的好处是对于内存的节约,因为GetValue/
SETVALue内部使用了高效的稀疏存储系统. 前边提到过,依赖
属性的一大特征是变更
通知,意思就是当某些依赖
属性的只改变了,WPF就会更具
属性的元数据触发一系列动作.
当某个
属性有
一个特定的值时,
属性触发器会执行
一个Setter的集合来设置相关对象的
属性,而当
属性失去这个
值的时候,
属性触发器会撤消该Setter集合. 这样的好处是大大简化了我们用声明事件的办法来处理满足一定条件时的逻辑,我们在XAML中就可以完成
相应的简单逻辑(如果复杂还
是需要过程式
代码)。
<Style x:key="buttonStyle" TargetType="
{x:Type Button}">
<Style.
triggers>
<
trigger Property="IsMou
SEOver" Value="True">
<Setter Property="RenderTransform">
<Setter.Value>
<RotageTransform Angle="10" />
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Black" />
</
trigger>
</Style.
trigger>
<Setter Property="FontSize" Value="22" />
<Setter Property="Foreground" Value="White" />
<Setter Property="
BACkground" value="Purple" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="50" />
<Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
</Style>
对于
属性触发器的应用我们更多的还可以通过
自定义验证规则来实现对样式和Tooltip的
自动设置。
数据触发器与
属性触发器几乎一样,只是数据触发器可以由任何.NET
属性触发,而不仅仅是依赖
属性.为了使用数据触发器,可向
triggers集合中
添加Data
trigger对象然后指定
属性/值对.同时可以用Binding来指定相关
属性而不仅仅是
属性名.
以下示例通过binding指定当自己的值为disabled的时候将自己禁用. 注意:Text
属性不是依赖
属性.
<StackPanel Width="200">
<StaticPane
l.resources>
<Style TargetType="
{x:Type Text
Box}">
<Style.
triggers>
<Data
trigger Binding="
{Binding Relative
resource=
{Relative
resource Self},Path=Text}"
value = "disabled">
<Setter Property="IsEnabled" Value="
false" />
/Data
trigger>
</Style.
triggers>
<Setter Property="
BACkground" Value=
"
{Binding Relative
resource=
{Relative
resource Self},Path=Text}" />
</Style>
</StackPane
l.resources>
<Text
Box Margin="3" />
</StackPanel>
当
一个已选择的事件产生时事件触发器会被激活.这个事件由触发器的RouteEvent
属性指定,他在Action集合中包含
一个或多个动作(从抽象类
triggerAction继承来的对象).
下边的示例展示了在Button的Click事件被触发时执行DoubleAnimation动作.
<Button>OK
<Button.
triggers>
<Event
trigger RouteEvent="Button.Click">
<Event
trigger.Actions>
<BeginStoryboard>
<Storyboard TargetProperty="width">
<DoubleAnimation From="50" To="100"
Duration="0:0:5" AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</Event
trigger.Actions>
</Event
trigger>
</Button.
triggers>
</Button>
附加
属性也属于XAML为了扩展其能力的一种,它提供了让
用户在父
属性里设置自己没有的子
属性值的能力。而在应用附加
属性时,通常它也可以体现WPF中
属性传递的优点:例如,当你给
一个Panel中的某类元素设置了附加
属性,那么在不显式声明相关
属性值(重写)的情况下,此类元素共同从父元素得到这个值。
作为附加
属性,之所以说是扩展了XAML的应用
是因为它提供了通过父元素来设置子元素并在其可视树范围内
默认
共享的能力。那么我们可以通过给Panel来设置TextElement.FontSize
属性来让所有
在这个Panel下的拥有TextElement.FontSize相关
属性的控件
自动拥有这个值,而不需要我们挨个对所有此类空间设置。例如:
<UserControl xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="
@L_
404_1@">
<StackPanel Orientation="Horizontal" TextElement.FontSize=”10”>
<Image
source="Fireworks
.bR_968_11845@p"/>
<StackPanel Width="200">
<TextBlock Margin="5,0" VerticalAlignment="center">Fireworks</TextBlock>
<TextBlock Margin="5" VerticalAlignment="center"
textwrapping="Wrap">
Sleek,with a black sky containing firework
s. When you need to
celebrate PowerPoint-style,this design is for you!
</TextBlock>
<Button x:name=”btnSubmit”>Agree</Button>
<Button x:name=”btnCancel”>Disagree</Button>
</StackPanel>
</StackPanel>
</ UserControl >
在上述的例子中,由于在StackPanel中设置了TextElement.FontSize,所以在它的
内部的可视控件
自动有
用这个属性:TextBlock,Button都会以FontSize=10来
显示。