北京动点飞扬软件

近七年行业项目解决方案、专注WPF外包、SaaS外包、GoLang外包、H5外包、微信小程序外包、UE4外包、U3D外包等 案例丰富 — 您最值得信赖的合作伙伴 — 可签公司合同
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

依懒属性是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 FrameworkPropertyMetadata(false,
                new PropertyChangedCallback(OnIsDefaultChange)));
        // ……
    }
    // .NET属性包装器(可选的)
    public bool IsDefault
    {
        get { return (bool)GetValue(Button.IsDefaultProperty); }
        set { SetValue(Button.IsDefaultProperty, value); }
    }
    // 属性变更回调(可选的)
    private static void OnIsDefaultChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e) { }
    // ……
}
       静态的IsDefaultProperty字段是真正的依赖属性,它是System.Windows.DependencyProperty类型的。所有的依赖属性习惯上以Property为后缀,且其访问权限是pulbic和static。依赖属性一般由DependencyProperty.Register方法创建,它需要一个名称(IsDefault)、一个属性类型(bool)和声明拥有这个属性的类型(Button)。

通过该方法的不同重载,我们可以有选择地传递处理自定义属性元数据,也可以编写那些用于处理属性值变更、强制转换(coerce)和验证(validate)的回调方法。Button在其静态构造方法中调用了Register方法的一种重载,并向其提供了依赖属性的默认值(false),并且为处理变更通知(change notification)附加了一个委托。
       IsDefault属性最终通过调用继承自依赖属性基类System.Windows.DependencyObject的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属性),元素需要被重绘,这可以通过传递FrameworkPropertyMatadataOptions.AffectsRender标志到重载的DependencyProperty.Register方法来实现。此外,依赖属性还允许三种重要的特性:变更通知、属性值继承以及对多种提供器的支持。


变更通知
       在依赖属性值发生变化的时候,WPF能够自动触发一些依赖这个属性元数据的动作,这些动作可以是重绘相应的元素、更新当前布局以及刷新数据绑定等。由变更通知实现的一个有趣的特性被称作“属性触发器”(property triggers),它允许我们不编写程序代码就可以在属性值发生改变的时候执行自定义动作。例如,我们把鼠标悬停在About Dialog(参看之前发布的文章)的按钮上时,按钮的文本立刻变成蓝色;移开鼠标,按钮的文本又恢复成黑色。为了实现这样的想法,我们在没有属性触发器的情况下,可以为每一个按钮附加事件处理器。鼠标悬停的事件是MouseEnter,鼠标移开的事件是MouseLeave。
<Button MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave"
MinWidth="75" Margin="10">Help</Button>
<Button MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave"
MinWidth="75" Margin="10">OK</Button>
       对应的两个事件处理函数为:
// 当鼠标悬停在按钮上时,将按钮的背景颜色改为蓝色
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null) b.Foreground = Brushes.Blue;
}
// 当鼠标离开按钮时,恢复按钮的背景颜色
private void Button_MouseLeave(object sender, MouseEventArgs e)
{
Button b = sender as Button;
if (b != null) b.Foreground = Brushes.Black;
}
       如果使用属性触发器,我们可以大幅简化上面的实现,仅通过XAML就可以完成相同的任务:
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Blue" />
</Trigger>
       触发器可以作用在按钮的IsMouseOver属性上,它的值在MouseEnter事件被触发的时候变为true,在MouseLeave被触发的时候变为false。值得注意的是,我们并不需要在IsMouseOver变为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="IsMouseOver" Value="True">
                    <Setter Property="Foreground" Value="Blue" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>
       属性触发器只是WPF支持的三类触发器中的一类,其它两类是数据触发器和事件触发器,将会在以后介绍。此外,尽管FrameworkElement的Triggers属性是一个TriggerBase类型(三类触发器的基类)的读/写集合,但其在WPF 3.0中只能包含事件触发器,这是由于WPF团队来不及完全实现造成的,因此,如果向该集合添加属性触发器或数据触发器,将导致运行时异常。(尚未在VS2008中试验这么做是否会出现异常)

属性值的继承

       属性值继承(简称属性继承)与传统的面向对象继承不同,它是指属性值可以沿着元素树向下传递的过程。

例:在Window元素上设置属性

<Window x:Class="Test.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="WPF揭秘" SizeToContent="WidthAndHeight"

    FontSize="30" FontStyle="Italic" Background="OrangeRed">

    <StackPanel>

        <Label FontWeight="Bold" FontSize="20" Foreground="White">

            WPF揭秘(版本3.0)

        </Label>

        <Label>(C)2006 SAMS 出版集团</Label>

        <Label>已安装的章节:</Label>

        <ListBox>

            <ListBoxItem>第一章</ListBoxItem>

            <ListBoxItem>第二章</ListBoxItem>

        </ListBox>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">

            <Button MinWidth="75" Margin="10">Help</Button>

            <Button MinWidth="75" Margin="10">OK</Button>

        </StackPanel>

        <StatusBar>您已经注册了本产品。</StatusBar>

    </StackPanel>

</Window>

下图展示了以显式方式设置Window元素的FontSize和FontStyle依赖属性后,整个窗体的变化情况。

       为这两个属性设置的值将会沿着元素树向下传递,并被相应的子元素继承(即将子元素的对应属性也被设定为这个值)。上例中,Button、Label和ListBoxItem都受到了影响,但由于第一个Label显式设置了FontSize,从而其字体大小未受影响。值得注意的是,StatusBar中的文本并没有受到这两个值的影响,尽管它与其它控件相同,也包含这两个属性。由于以下两种原因,属性值的继承显得有些微妙:

(1)并不是所有的依赖属性都参与属性值的继承(依赖属性可以通过向DependencyProperty.Register传递FrameworkPropertyMatadataOptions.Inherits来选择是否参与继承)

(2)可能存在更高优先级的属性设定源(稍后解释)

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)提供器
       我们已经见过一些属性值提供器了,比如属性值继承提供器。从技术上说,局部值提供器是任何调用了DependencyObject.SetValue的对象,但是它通常可以被看作一种在XAML或程序代码中给普通属性赋值的操作。默认值提供器设定的值是依赖属性在注册时设定的那个值,因此其优先级最低。(详情参见依赖属性(1)对依赖属性实现的介绍)
       这个优先级顺序解释了为何StatusBar的FontSize和FontStyle属性没有受属性值继承影响,这是因为StatusBar已经被主题样式提供器(第6级)设定为匹配系统的值。尽管它的优先级超过了属性值继承提供器(第7级),但是我们仍旧可以通过较高优先级的提供器来设定字体属性值。
第二步    计算表达式
       如果来自第一步的值是一个表达式(一种派生自System.Windows.Expression的对象),则WPF负责执行一个将表达式转换为具体值的特殊计算步骤。在WPF 3.0中,表达式仅可是动态资源或数据绑定,未来版本的WPF可能会允许其它种类的表达式。
第三步    应用动画
       在一个或多个动画正在运行的时候,它们拥有更改当前属性值(来自第二步的值)的权利。因此,动画比任何其它属性值提供器的优先级都高,这对WPF初学者来说有些难度。
第四步    强制(Coerce)
       在所有的属性值提供器完成设定后,WPF将结果属性值传递到为依赖属性注册的CoerceValueCallback委托中。我们可以在这个委托内编写自己的代码,用以重新计算依赖属性值或执行强制转换。例如,WPF内建控件ProgressBar使用这个回调来约束其Value依赖属性。这个属性的值应在Minimum和Maximum之间,但如果输入值小于Minimum,则其返回Minimum值,如果输入值大于Maximum,则其返回Maximum值。
       (这个CoerceValueCallback在MSDN中的说法是“为只要重新计算依赖项属性值或专门请求强制转换时就调用的方法提供一个模板,此回调的实现应检查 baseValue 中的值,并根据该值或其类型确定该值是否需要进一步进行强制转换”。按我自己的理解,这个委托的作用就是为上一步计算好的属性值添加使其合理化的逻辑。
       MSDN为这个回调提供了一个示例(其中d是该属性所在的对象,value是该属性在尝试执行任何强制转换之前的新值):
private static object CoerceButtonColor(DependencyObject d, object value)
{

 

"3">ShirtTypes newShirtType = (d as Shirt).ShirtType;
if (newShirtType == ShirtTypes.Dress || newShirtType == ShirtTypes.Bowling)
{
return ButtonColors.Black;
}
return ButtonColors.None;
}
从中可以看出,依赖属性ButtonColor的值依赖于对象d的ShirtType,但并没有使用上一步计算好的值value。)
第五步    验证
       最终,经过强制转换的值被传递到为依赖属性注册的ValidateValueCallback委托中。如果输入值是合法的,则此委托必须返回true;否则,必须返回false。返回false将导致一个取消整个过程的异常。
(MSDN中的示例:
private static bool ShirtValidateCallback(object value)
{
ShirtTypes sh = (ShirtTypes)value;
return (sh == ShirtTypes.None || sh == ShirtTypes.Bowling || sh == ShirtTypes.Dress || sh == ShirtTypes.Rugby || sh == ShirtTypes.Tee);
}
有此可见,在这个回调的实现中,应当加入验证属性值的范围的逻辑。同理,在ProgressBar为其double类型的ValueProperty依赖属性实现的验证回调中,判断了属性值是否是合法的double数据。)
       如果我们想要查看依赖属性值是由那一个提供器设置的,可以使用静态的DependencyPropertyHelper.GetValueSource方法来调试。该方法返回一个ValueSoruce结构,它包含一个暴露基本值来自于何方的BaseValueSource枚举,以及对应2~4步的IsExpression、IsAnimated、IsCoerced属性。当我们在前面的StatusBar实例上为FontSize和FontStyle调用这个方法后,BaseValueSource的返回值为DefaultStyle,这说明它的值来自于主题风格设定提供器(主题风格有时也称作默认风格,对应主题风格触发器就是DefaultStyleTrigger)。此外切记不要在产品代码中使用这个方法。
       在前面通过事件处理器设置按钮文本颜色的示例中,有一个问题值得注意:在MouseLeave事件处理器中,按钮的Foreground被赋予了局部值Brushes.Black(局部值提供器)。这与按钮的初始状态不同,其初始状态是由主题风格设定提供器设置的。如果主题变换了,且新主题(或其它优先级较高的提供器)尝试改变Foreground的默认值,那么局部值提供器会胜出(因为它的优先级高于主题风格提供器),并将Foreground设置为黑色。对于这种情况,我们可能会希望清除这个局部值,以便可以使用主题的默认设定。DependencyObject提供了一个ClearValue方法,可以清除局部值:
b.ClearValue(Button.ForegroundProperty);
        值得注意的是,在IsMouseOver上的触发器与此不同,当其处于“未触发”状态时,将会忽略对属性值的计算。

 

Silverlight学习笔记:“依赖对象”和“依赖属性”的介绍:

学习silverlight没什么门路,跑了几趟书店都没买到sl的书,真慢。今天装了vs2008,于是打开vs的对象浏览器,看看sl里面的类库都是什么德行。

那么先从xaml文档的根节点元素开始看吧~~~ 查看之下还真是看不出什么所以然~~~


就这个图能看出什么端倪?所以说要搞清楚一个类库还是得先从她的身家背景开始看起...

Canvas的一家

据说谁的祖先都是从单细胞(object)发展而来的,那么从第二代开始看....

二代目:
System.Windows.DependencyObject “依赖对象”  一个抽象类,好像没什么好看的。


FindName是通过姓名找孩子(说也奇怪,到DependencyObject还没有Children这个属性,也没有WPF中独生子女的Content属性,这是干什么用的?找谁家的孩子?),下面的Name是自己本身的名字……其它的,看不懂……先放着……

第三代:
System.Windows.Media.Visual    “可视对象” 也是抽象类
增加了2个方法,捕捉"鼠标事件"和释放"鼠标事件"

由于子元素存在于父元素“体内”,点击子元素实际上也应该“通知”父元素,所以鼠标事件是会向上传递的。
但是用户点击父元素的任意一部分,父元素怎么知道到底有没有点到什么子元素啊?总之,就是为了弄清楚咯吱哪里该谁笑的疑惑,就弄了这两个方法。总的来看,可能为了兼容1.0吧,封装的好差的说。本来应该由事件参数做的事情变成sender做了,不过从行为来看,这样也合理了,总之就是不同于.net winform。

讲个故事吧,爸爸和儿子守着一条小道,儿子在前。前面跑来一只兔子(或者是一只很身手很差的老鼠),那么如果儿子捕获了兔子(儿子很厉害,想捉一定会捉住,也就是执行CaptureMouse()返回true),那么父亲那里执行CaptureMouse()肯定会返回false了,除非儿子那里抓了又放了,就是ReleaseMouseCapture()了。

情况如同下面的代码:(txt是一个TextBlock)

public partial class Page : Canvas
    {
public void Page_Loaded(object o, EventArgs e)
        {
            InitializeComponent();
this.MouseLeftButtonDown += new MouseEventHandler(Page_MouseLeftButtonDown);
            txt.MouseLeftButtonDown += new MouseEventHandler(TextBlock_MouseLeftButtonDown);
        }
void Page_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            txt.Text += (sender as Visual).CaptureMouse().ToString();
//如果点击了TextBlock,结果会是false,因为在TextBlock那已经执行了CaptureMouse()
//如果点击了Canvas的其他部分(如空白部分),结果会是true,因为变成是第一手处理了
        }
void TextBlock_MouseLeftButtonDown(object sender, MouseEventArgs e)
        {
            txt.Text += (sender as Visual).CaptureMouse().ToString();//因为是第一个执行,会是true
//(sender as Visual).ReleaseMouseCapture();如果这句被执行了(放开兔子),那么Canvas那里就会是true了
        }
    }

第四代了:
System.Windows.UIElement  “用户对象” 还是抽象类  增加一堆事件和属性、依赖属性。

很多很复杂……

第五代了:
System.Windows.FrameworkElement “框架元素” 抽象的
增加了重要的“父元素”属性 以及外观属性:高 和 宽

六代:
子承父业,到了System.Windows.Controls.Panel,长成了容器级的元素

Children属性管理子元素,它是一个MS.Internal.Collection<Visual>类型的子类

最后到了Canvas 在Penel的基础上增加了2个依赖属性

现在没得逃避了。
所谓依赖属性,就是通过被依赖的对象起作用,就如一个TextBlock对象是Canvas对象的子对象,那么Canvas对象就通过Canvas.Left对TextBlock起管理的作用,但离开了Canvas,这个属性也就没有作用了。就好像“母亲”拥有的“母爱”依赖于“孩子的存在”而存在,当孩子离开了母亲,这种“爱”也就无从谈起了。
依赖关系,也就是WPF中“视觉元素”相互依赖的奥义了。而所有的依赖属性,都是父元素与子元素之前牵扯不断的关系,而这些属性都是为“视觉体现”而服务的。

(以上是我自己的解释)

而依赖对象是怎么工作的呢?其实打字到这一行之前我也不清楚,只是以前看过一篇WPF的关于依赖属性的文章,留下一点模糊的记忆,然后刚才回去又仔细查阅了对象信息,发现原来DependencyObject类的两个重要方法有玄机:

其实就是DependencyObject里面有一个隐藏的“字典”对象,通过父元素调用的,比如Canvas调用了TextBlock的SetValue方法
this.FindName("aTextBlock").SetValue(Canvas.LeftProperty,20);
相当于,父对象把一个值(这里是20)存进了子对象的“字典”里,而父亲的(静态只读成员)LeftProperty就是那把钥匙,也就是Key!!!!哈哈哈 ,终于理解了。那把钥匙可以打开所有子对象的“字典”,取出匹配于LeftProperty的值。

foreach (Visual child in this.Children) {
double left = (double)child.GetValue(Canvas.LeftProperty);
//....
            }

这也就是为什么脚本可以这么写<TextBlock Canvas.Left="20">,TextBlock类却没有一个Canvas.Top属性的原因了,而xaml中声明式的指定Canvas.Left,是由xaml解析引擎的时候自动完成的。
this.FindName("aTextBlock").SetValue(Canvas.LeftProperty,20);

了解了依赖属性的工作方式后,很容易通过这个特点来使用xaml标记用户自定义控件……

就这样了。。。

续:

经过继续研究,发现所谓“依赖属性”的作用实在“博大”,它可以使用于本对象以外的"包装类"对其包装,如xaml解析对象通过调用SetValue对UI对象进行初始化;渲染引擎使用GetValue取得它与父元素相依赖的属性值(如在渲染Canvas的子对象时需要子元素中Canvas.Top属性的值)

“依赖属性集”既可以放本身需要的属性,也可以放其他元素的所需要的信息集。以“键”和“值”的方式存取。。。。很方便,很强大!

来自www.cnblogs.com/zlgcool

  依赖属性(Dependency Property)

  .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), typeof(Button),
      new FrameworkPropertyMetadata(false,
      new PropertyChangedCallback(OnIsDefaultChanged)));
  }
  //.NET属性(可选)
  public bool IsDefault
  {
    get { return (bool)GetValue(Button.IsDefaultProperty); }
    set { SetValue(Button.IsDefaultProperty, value); }
  }
  //属性改变时的回调
private static void OnIsDefaultChanged(DependencyObject o,
DependencyPropertyChangedEventArgs e)
  {...}
}


  在上面的示例代码中是标准的实现依赖属性定义的方法,这里需要注意的是依赖属性始终是定义在普通.NET属性之上的用Register静态方法注册的特殊属性,即便这里的.NET属性(如IsDefault属性)不是必需的。另一个需要注意的地方是,通过声明依赖属性,我们多了一个控制属性改变时的回调方法(即便这同样可以通过声明委托和事件来定义,但这里我们什么都没做,为什么不用呢?),这样的好处是我们可以在属性改变的时候做些我们想做的事情,而我们却省去了手动声明事件的任务。其实回调函数的存在是为了让我们保证在属性包装器中(IsDefault属性)仅仅使用标准的GetValue/SetValue而不用任何其他逻辑,转而将自定义逻辑写入回调函数--这样做是为了遵循WPF的统一设计原则,让XAML 设置属性与使用过程式代码设置属性保持一致。

  触发器

  依赖属性的实现让我们可以在一个局部范围内保持属性值的共享,这样的好处是对于内存的节约,因为GetValue/SetValue内部使用了高效的稀疏存储系统. 前边提到过,依赖属性的一大特征是变更通知,意思就是当某些依赖属性的只改变了,WPF就会更具属性的元数据触发一系列动作.

  WPF中有三种方式来实现这样的变更通知:

  属性触发器: 在属性发生改变时执行自定义动作,而不用改任何过程代码。

  数据触发器: 当普通.NET属性的值改变时调用自定义动作。

  事件触发器: 当路由事件被触发时调用自定义动作。

  属性触发器(Trigger)

  当某个属性有一个特定的值时,属性触发器会执行一个Setter的集合来设置相关对象的属性,而当属性失去这个值的时候,属性触发器会撤消该Setter集合. 这样的好处是大大简化了我们用声明事件的办法来处理满足一定条件时的逻辑,我们在XAML中就可以完成相应的简单逻辑(如果复杂还是需要过程式代码)。


<Style x:key="buttonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
  <Trigger Property="IsMouseOver" 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的自动设置。

  数据触发器(Data Trigger)

  数据触发器与属性触发器几乎一样,只是数据触发器可以由任何.NET属性触发,而不仅仅是依赖属性.为了使用数据触发器,可向Triggers集合中添加DataTrigger对象然后指定属性/值对.同时可以用Binding来指定相关属性而不仅仅是属性名.

  以下示例通过binding指定当自己的值为disabled的时候将自己禁用. 注意:Text属性不是依赖属性.

<StackPanel Width="200">
  <StaticPanel.Resources>
    <Style TargetType="{x:Type TextBox}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding RelativeResource=
{RelativeResource Self}, Path=Text}"
               value = "disabled">
          <Setter Property="IsEnabled" Value="false" />
        /DataTrigger>
      </Style.Triggers>
      <Setter Property="Background" Value=
"{Binding RelativeResource={RelativeResource Self}, Path=Text}" />
    </Style>
  </StackPanel.Resources>
  <TextBox Margin="3" />
</StackPanel>

  事件触发器(Event Trigger)

  当一个已选择的事件产生时事件触发器会被激活.这个事件由触发器的RouteEvent属性指定,他在Action集合中包含一个或多个动作(从抽象类TriggerAction继承来的对象).

  下边的示例展示了在Button的Click事件被触发时执行DoubleAnimation动作.

<Button>OK
  <Button.Triggers>
  <EventTrigger RouteEvent="Button.Click">
  <EventTrigger.Actions>
    <BeginStoryboard>
      <Storyboard TargetProperty="width">
        <DoubleAnimation From="50" To="100"
Duration="0:0:5" AutoReverse="True" />
      </Storyboard>
    </BeginStoryboard>
  </EventTrigger.Actions>
  </EventTrigger>
  </Button.Triggers>
</Button>


  属性值继承

  附加属性(Attached Property)

  附加属性也属于XAML为了扩展其能力的一种,它提供了让用户在父属性里设置自己没有的子属性值的能力。而在应用附加属性时,通常它也可以体现WPF中属性传递的优点:例如,当你给一个Panel中的某类元素设置了附加属性,那么在不显式声明相关属性值(重写)的情况下,此类元素共同从父元素得到这个值。

  作为附加属性,之所以说是扩展了XAML的应用是因为它提供了通过父元素来设置子元素并在其可视树范围内默认共享的能力。那么我们可以通过给Panel来设置TextElement.FontSize属性来让所有在这个Panel下的拥有TextElement.FontSize相关属性的控件自动拥有这个值,而不需要我们挨个对所有此类空间设置。例如:

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <StackPanel Orientation="Horizontal" TextElement.FontSize=”10”>
   <Image Source="Fireworks.bmp"/>
   <StackPanel Width="200">
    <TextBlock Margin="5,0" VerticalAlignment="center">Fireworks</TextBlock>
    <TextBlock Margin="5" VerticalAlignment="center" TextWrapping="Wrap">
     Sleek, with a black sky containing fireworks. 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来显示。