WPF,Silverlight与XAML读书笔记第七 WPF新概念之二依赖属性

说明:本系列基本上是《WPF揭秘》的读书笔记。在结构安排与文章内容上参照《WPF揭秘》的编排,对内容进行了总结并加入一些个人理解。

      WPF引入了一种新的属性类型依赖属性。依赖属性用于整个WPF平台,用来实现样式化,自动属性绑定,动画等。详细说即使用属性替代方法和事件处理对象的行为,通过属性驱动来加强系统的行为。如将属性绑定到数据源来驱动用户界面的显示。

      依赖属性可以发挥作用的场合如:将一个属性绑定到另一个对象的某属性,要求当被绑定的属性改变时,依赖于那个属性的属性会自动改变(这要求双方均支持属性绑定)。另一个场景是继承可以被继承的属性(如常见的字体大小属性),要求当父对象变化时,子对象属性作相应变化。另外依赖属性在模版中也发挥着重要作用,使用依赖属性可以将模版中的属性封装起来,并应用到为各个控件建立的模版,这样就可以使这些控件在风格上实现统一(其内部原理是,控件继承了模板的属性来保证相对的一致性,当然也可以添加自己的属性,以实现自身所需展现的内容)。

    依赖属性在任何时刻都依靠多个提供程序来判断它的值。这些提供程序可以是一段一直在改变值的动画,或者一个父元素的属性值由上而下传递给子元素。依赖属性的最大特征是其内建的传递变更通知(change notification)的能力。

     依赖属性给程序带来的最大特点是,其更有利于用声明式代码(如XAML)进行程序设计。声明式代码中属性的设置是实现程序很重要的途径。有了依赖属性(对传统属性的增强),声明式代码才可以通过设置属性的方式获得原来过程式代码才能提供的如属性的垂直传递,变更通知等高级特性。

依赖属性的实现

依赖属性完全由WPF API实现,只有XAML天生可以理解依赖属性,对应到过程式代码,依赖属性仅是.NET属性的一种增强。

下面代码演示了在Button中实现一个叫IsDefault的依赖属性。

public class Button : ButtonBase
{
    //依赖属性
    public static readonly DependencyObject IsDefaultProperty;
 
    static Button()
    {
        //注册属性
        Button.IsDefaultProperty   = 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(DependencyProperty o, DependencyPropertyChangedEventArgs e)
    { }
}

分析:

    代码中IsDefaultProperty静态成员是依赖属性,所有依赖属性都是System.Windows.DependencyProperty类的对象,则其必须是public static且使用Property作为名称的后缀。声明过依赖属性后,要通过调用DependencyProperty.Register方法来创建。这个函数有3个必选的参数。

1) 名称,不带Property的依赖属性变量名。如上面代码对应名称为IsDefault。

2) 此依赖属性的类型,对应上文代码即是bool。

3) 拥有这个属性的类,对应Button。

另外Register方法还有不同的重载允许你另外传入metadata(元数据)来通知WPF如何处理该属性,如果处理属性改变的回调,如何处理强制值转换,如何验证值

在上面代码中,构建的元数据对象中传入false作为依赖属性的默认值。另外定义一个PropertyChangedCallBack委托用作变更时的通知。

         下一步,定义一个名为IsDefault的传统的.NET属性。在其访问器中又分别调用System.Windows.DependencyObject下的GetValueSetValue来作为其实现。DependencyObject是底层的基类,这是拥有依赖属性的类必须继承的。GetValue返回最后一次由SetValue设置的值,如果SetValue从未被调用过,则返回属性注册时设置的默认值。

         这个Button.NET属性是可选的,如果不加,代码中不能以使用传统属性的方法访问依赖属性,但是仍然可以使用GetValue/SetValue方法(它们是public的)来访问依赖属性。但实现.NET可以使依赖属性的使用变得像使用传统属性一样容易,另外通过XAML设置(依赖)属性,也是通过这个.NET属性。

         上文提到的XAML通过传统属性(XAML Attribute)访问依赖属性指的是编译时(如编写程序)时,在运行时WPF直接调用GetValueSetValue

         有一个规则是在封装对依赖属性的.NET属性的访问器,不应包括调用GetValue/SetValue以外的任何代码。所有需要在存取依赖属性时触发的方法,应该放在注册的回调函数中。

         依赖属性(如IsDefaultProperty)是一个静态成员,所以相对而言其它传统属性在数目增加时(比如由于初始化新实例导致属性增加)更能节省内存空间,且GetValueSetValue内部使用高效的稀疏存储系统。依赖属性的好处还体现在其相当把一部分代码(如完成检查线程访问,请求容器元素重新呈现)集中起来,并做标准化处理。在这之前这些功能需要用户在属性的访问器中逐个实现。

依赖属性三大主要作用

1. 变更通知

         所谓变更通知就是当依赖属性的值改变时,WPF会自动根据注册依赖属性时提供的元数据(metadata)触发一系列的动作。这些动作可能包括但不限于重新呈现元素,更新当前布局,刷新数据绑定。

         给出一个简单的例子:当ButtonBackground属性(一个依赖属性)的值改变时,要求子元素重新呈现,则可以将FrameworkPropertyMetadataOptions.AffectsRender标记传递给Register方法的重载中接收的元数据的参数。

         另外变更通知的一个很常见的实现方式是“属性触发器”,如下XAML代码(黑体部分展示了属性触发器)

<Button MinWidth="75" Margin="10">
    <Button.Style>
        <Style   TargetType="{x:Type Button}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="Blue"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
    OK
</Button>

 WPF3.0版本不能直接将属性触发器应用到Button上,只可以应用到Button Style属性上。)

         使用触发器后,当Trigger的属性(依赖属性IsMouseOver)值改变时,会自动应用对Background的设置。

2. 属性值(依赖属性的属性值)继承

      WPF中的属性值继承概念指的是属性值自顶向下沿着元素树传递。举一个很形象的例子,在ASP.NET多个文件夹下都可以存在web.config文件。默认情况下,子目录的web.config设置会继承父目录的web.config文件设置。子目录的web.config也可以给出自己的设置来重写父目录或更高级别的web.config的设置。同样WPF中的子节点可以覆写父节点的依赖属性传下来的属性值。

     当然也不是每个依赖属性都参与属性值的继承。参与继承的依赖属性在DenpendencyProperty.Register()方法中传入FrameworkPropertyMetaOptions.Inherits来设置继承。下文还会继续介绍其他影响属性值设置,覆写等的因素。

     属性值向下传递不仅限于传递给真实的元素,也可以传递给属性元素这样的伪子元素,只要其是由Freezable派生的对象。

3. 对多个提供程序的支持

    WPF有许多机制可以独立的尝试设置依赖属性的值,下面来讨论这个设置过程。依赖属性的变更通知机制保证如下计算依赖属性的值的过程可以自动发生。

第一步:判断基础值

常见的8个可以设置大多数依赖属性值的提供程序(按优先级由高到低)依次是:

1)         本地值

2)         样式触发器

3)         模版触发器

4)         样式设置器(Setter)

5)         主题样式触发器

6)         主题样式设置器(Setter)

7)         属性值继承

8)         默认值

下面逐一详细解释:

1)         本地值:指任何形式对DependencyObject.SetValue的调用,包括使用XAML(以简单赋值的方式)或使用过程式代码。

2)         样式触发器:定义在元素的样式中的触发器对属性做出的改变

3)         模板触发器:应用到元素的模板中的触发器对属性做出的改变

4)         样式设置器:样式中的Setter对属性的设置

5)         主题样式触发器:不同系统主题下默认样式中的触发器对属性做出的改变

6)         主题样式设置器:按系统主题(Theme)的下的默认样式Setter来设置元素的字体等样式。

7)         属性值继承:由父元素的设置继承得到的值

8)         默认值:依赖属性注册时,使用DependencyProperty.Register方法初始设置的值。

第二步:计算表达式的值

         当第一步中设置的值是表达式(System.Windows.Expression的一个对象)时,第二步将会对这个表达式进行计算得到一个具体的值。(.NET3.0中的WPF,表达式仅可用于使用动态资源或数据绑定)。

第三步:应用动画

         运行中的动画有能力改变当前的属性值或者完全替换这个值。其优先级高于其它任何属性,甚至是本地值。在所有内置的属性值提供程序中,动画是最后一个可以影响属性值的

第四步:限制(Coerce

         从这往后,内置的提供程序将不再影响属性值。这些步骤中用户自定义的委托等进一步影响属性值。这里的限制就是指的在依赖属性注册时传入的CoreceValueCallback委托实例。属性处理进行到这一步时,会自动将属性值传入这个委托来进行进一步处理,在这里用户可以根据需要对属性值做限制。

第五步:验证

       要让这一步起作用的方式与第四步相同,即也是在注册依赖属性时传入一个委托这里传入的是ValidateValueCallback。在第四步或之前步骤处理完成的值,将被传入这个委托。如果输入的值有效则回调函数返回true;否则返回false

         当回调函数返回false时,将会抛出异常,并取消此设置依赖属性的过程。

小提示:

         静态方法DependencyPropertyHelper.GetValueSource可以用于调试过程,来得到此依赖属性设置过程的相关信息。此方法会返回一个ValueSource类型的结构,其包含信息:

1)           BaseValueSource:枚举值,反映基础值来源,即第一步中值是怎样得到的。

2)           IsExpression:布尔属性,是否经过表达式计算的过程。

3)           IsAnimated:是否有动画影响了这个依赖属性值的设置。

4)           IsCoerced:是否受到传入的CoercedCallback的影响。

小提示:

依赖属性有一个实例方法 - ClearValue,其可以用来清除本地值,并用下一个高级别的依赖属性提供程序来设置这个值。(其作用范围仅限于第一步的几种提供程序间)。

本文完

参考:

WPF揭秘》

原文地址:https://www.cnblogs.com/lsxqw2004/p/4554361.html