zoukankan      html  css  js  c++  java
  • Silverlight的依赖属性与附加属性

    依赖属性(Dependency Property)和附加属性 (Attached Property)这两个算是很基础的知识都不是很记得了。写一写,当做 一下笔记吧。

    CLR属性 与 依赖属性

    CLR属性我们非常熟悉了,在DotNet编程中随处可见。最简单最常见的属性访 问器就是直接操纵类的私有成员,如下:

    public class Person
    {
       private String _name;
       public string Name
       {
         get { return _name; }
         set { _name = value; }
       }
    }

    C#3.0对这种常见的写法提供了“自动属性”这一特性,方便了偶等这些懒惰 的码农。

    public class Person
    {
       public string Name { get; set; }
    }

    这两种写法是等价的,都是需要设立一个实例级的私有变量作为属性访问器的 持久存储。这对于我们非UI应用来说没什么。因为第一,我们一般不会创建太多 类实例;第二,一个类的属性通常不会很多,加几个私有变量不会增加系统负担 。但是这两个理由对于UI应用程序来说恰恰不成立。

    在很多UI应用中,我们经常会创建很多类实例,成千上万个实例在UI系统中是 很普遍的事情。同时,UI类通常会包含大量的属性供设计人员使用,例如背景颜 色,前景颜色,字体,边距等等,这些属性在绝大多数情况下会保持默认值,如 果为每个实例都建立这么多的私有变量来存储UI属性的值,势必会造成极大的浪 费,对系统负担的开销也是不小。

    鉴于以上提到的问题,设计一个高效的属性存储系统对于UI应用程序的开发是 非常重要的。因此Silverlight引入了“依赖属性(DependencyProperty)”。

    采用键值对替代成员变量作为属性内部存储

    传统CLR属性,一个属性对应一个私有变量,UI元素的属性那么多,创建过多 的私有变量不是一件简单的事情,况且大多数属性只会用到默认值。因此 Silverlight使用键值对的形式来存放那些用户显式设置的属性(称为Local Value本地值),没有设置的属性就不存。那属性的默认值存放在哪?既然各个实 例的默认值都一样(不然也不叫默认值了),那么直接存放到静态成员变量上就 行了。这也就大大提高了存储的效率。

    注册依赖属性

    既然依赖属性采用键值对这样的哈希结构进行存储,那么要获取不同属性的值 ,我们就必须使用不同的哈希键,否则就会读取到其他属性的值了。因此,当我 们在向Silverlight属性系统注册依赖属性的时候,Silverlight会返回一个唯一 的属性标识对象,类型为 DependencyProperty。我们以后就通过这个唯一标识对 象去访问依赖属性的值。

    由于这个唯一标识符是所有类实例都公用并且不会被修改的,因此我们通常将 其保存到一个static readonly的成员变量中。

    DependencyProperty类提供了两个方法,一个是Register方法,用于注册依赖 属性;另外一个是RegisterAttached,用于注册附加属性,这个后面再讲。

    public static DependencyProperty Register(
       string name,
       Type propertyType,
       Type ownerType,
       PropertyMetadata typeMetadata
    )

    Register方法的签名由几部分组成,Name参数指明了依赖属性使用的名称,这 个名字很重要,在定义控件Style和Template的时候,Setter的Property属性填入 的值就是注册依赖属性时使用的名称;propertyType指明了依赖属性实际的类型 ,ownerType指明了是哪个类注册了此依赖属性,最后typeMetadata存放了一些依 赖属性的元信息,包括依赖属性使用的默认值,还有属性值发生变更时的通知函 数。

    属性的存取

    和CLR属性不同,依赖属性不是直接对私有变量的操纵,而是通过GetValue和 SetValue的方法来操作属性值的。

    下面的代码演示了为Ball控件设置一个Center的依赖属性,并且在程序中读取 和修改此属性的过程:

    public class Ball : Control
    {
       public static readonly DependencyProperty CenterProperty  =
         DependencyProperty.Register("Center", typeof(Point),  typeof(Ball), null);
    }
    public class BallApp
    {
       public void RollBall(Ball ball)
       {
         Point curCenter = (Point)ball.GetValue (Ball.CenterProperty);
         curCenter.X++;
         // 注意对值类型对象操作完毕之后一定要调用SetValue修改才能生 效
         ball.SetValue(Ball.CenterProperty, curCenter);
       }
    }

    由于上述对依赖属性的操作经常需要涉及到类型的转换,比较麻烦,而传统 CLR属性用起来和直接操纵普通变量一样方便,因此通常在设计依赖属性的时候, 都会使用CLR属性将其包装起来,我们称之为增强型的CLR属性。

    public class Ball : Control
    {
       public static readonly DependencyProperty CenterProperty  =
         DependencyProperty.Register("Center", typeof(Point),  typeof(Ball), null);
       public Point Center
       {
         get { return (Point)GetValue(CenterProperty); }
         set { SetValue(CenterProperty, value); }
       }
    }

    按照约定,依赖属性的名称通常是相应CLR属性名称后面加上个“Property” 字符串。

    事实上,使用CLR包装依赖属性并不只是为了方便 (http://msdn.microsoft.com/en-us/library/cc221408%28VS.95% 29.aspx#back_dependency_properties),很多依赖于CLR属性作为基础的工具或 者子系统并不能直接访问依赖属性,而只能通过CLR属性去间接访问依赖属性。例 如上面的例子中,假设我们并没有设置一个Center的CLR属性,那么以下的Xaml将 会编译失败,因为Xaml解析器无法知道Ball类有一个Center的依赖属性(在Style 中设置Center属性值就可以编译成功,因为Style是动态查找属性的)。

    <Ball Center="2" />

    依赖属性的寻值逻辑和值变更通知

    上面提到的只是依赖属性相比CLR属性在存储效率的不同,实际上,依赖属性 还有其他实用的特性。

    寻值逻辑

    CLR属性在获取值的时候是直接读取成员变量值返回的,而依赖属性在使用的 时候是通过GetValue函数的调用来获取属性的值。实际上,GetValue内部做的事 情可不止是简单的读取字典里头存放的值。他还有寻值逻辑。如下图所示:

    当你调用GetValue去读取一个依赖属性的值的时候,Silverlight的属性系统 会首先从动画系统中查找当前是否有作用在此依赖属性上的动画,如果有,则返 回此动画值。从这里也可以看出,依赖属性是Silverlight实现动画机制的基础。 注意,如果动画已经停止了,并且没有设置FillBehavior=HoldEnd的话,那么 Silverlight就不会返回此动画值。

    如果读不到动画值,那么Silverlight就会尝试读取本地值。本地值有几种类 型,一种是用户通过代码或者Xaml直接设定的值。一种是通过资源绑定得到的值 ,最后一种是通过数据绑定得到的值。这些都被视为本地值。

    <StackPanel x:Name="LayoutRoot">
       <StackPanel.Resources>
         <System:String x:Key="TextBlockResource">资源数据绑定文本 </System:String>
       </StackPanel.Resources>
       <TextBlock Text="{Binding Source={StaticResource  TextBlockResource}}" />
       <TextBlock x:Name="DataBindingElement" Text="{Binding  ElementName}" />
    </StackPanel>

    如果还是读取不到,那么就继续尝试读取控件模板和样式中设置的值。

    如果所有这些值都读取失败,那么Silverlight属性系统就会返回该依赖属性 的默认值。当我们注册依赖属性的时候,可以传入一个 PropertyMetaData对象, 这个对象包含了此依赖属性的默认值和值变更通知回调函数。如果注册的时候没 有传入默认值,则对于引用类型的依赖属性,返回null,对于字符串,返回 String.Empty,对于值类型,则返回一个以默认值初始化的实例。这里需要对集合类型特别注意,由于通过PropertyMetaData传入的默认值是所 有类实例共享的,因此,一定要在类构造函数中显式传入集合的实例。

    public class GameRoom : Control
    {
       public List<Ball> Balls
       {
         get { return (List<Ball>)GetValue(BallsProperty);  }
         set { SetValue(BallsProperty, value); }
       }
       public static readonly DependencyProperty BallsProperty  =
         DependencyProperty.Register("Balls", typeof (List<Ball>), typeof(GameRoom), null);
       public GameRoom()
       {
         Balls = new List<Ball>();
       }
    }

    可能正是因为Silverlight的依赖属性在获取值的时候需要从多个地方去读取 值,而不是像CLR属性一样,直接从成员变量中读取值,所以才被称之为“依赖” 属性吧。

    值变更通知

    属性值的变更通知我们并不陌生。我们在DotNet中实现的时候,一般是让类实 现INotifyPropertyChanged接口。在UI系统中,值变更通知是经常需要用到的。 数据源一旦变更,所有相应的UI元素的值都要相应的做出调整。Silverlight的依 赖属性对此有内置的支持。只要你在绑定时使用依赖属性,那么当依赖属性值发 生变更的时候,所有绑定的地方的值都会同步更新。而且,依赖属性也提供了一 个值变更通知函数(在注册依赖属性时通过PropertyMetaData传入),你可以自 定义一个函数来控制值变更时需要执行的操作。

    public class Ball : Control
    {
       public static readonly DependencyProperty CenterProperty  =
         DependencyProperty.Register("Center", typeof(Point),  typeof(Ball), new PropertyMetadata(OnCenterChanged));
       public Point Center
       {
         get { return (Point)GetValue(CenterProperty); }
         set { SetValue(CenterProperty, value); }
       }
       private static void OnCenterChanged(DependencyObject d,  DependencyPropertyChangedEventArgs e)
       {
         Ball ball = d as Ball;
         // 获取新的球心
         Point newCenter = (Point)e.NewValue;
         // ...
       }
    }

    Silverlight的附加属性(Attached Property)——全局的依赖属性

    刚才提到的依赖属性和CLR属性一样都是服务于某一个类的。只不过将属性改 造得存储上更有效率,使用上更加强大。在Silverlight中还有一种特殊的依赖属 性,这种依赖属性并不只是服务于某个特定的类,而是服务于全局,这就是附加 属性。从名字上也可以看出来,附加属性是在某个类里面注册,然后可以被其他 类所使用。什么情况下需要使用附加属性呢?举Canvas类的ZIndex属性作为例子。

    容器类在叠加子控件的时候,需要考虑哪个控件放置在最上面,那个放在下面 。

    那么容器类怎么知道子控件的叠放顺序呢?最不动脑子的设计就是为所有的控 件都添加一个ZIndex的属性,属性的值代表叠放的顺序。但这样的后果就是,如 果我这个控件不参与布局,那多这个属性就会显得很浪费。所以比较理想的设计 是,需要用到这个属性的时候就有这个属性,不需要的时候就没有这个属性的负 担。附加属性的出现就是为了解决这样的问题。一旦控件需要某个属性的时候, 我们可以把这个属性附加到这个控件类上。

    注册附加属性和依赖属性差不多,只不过函数名为RegisterAttached。这个就 不多说了。

    什么时候应该用到依赖属性

    既然依赖属性那么高效,而且那么强大,那么我们是不是应该保持使用依赖属 性的习惯呢?事实上,任何好处都是有代价的。Silverlight的依赖属性在访问效 率上并不如直接访问成员变量那么高效。因此,对于那些比较简单而访问频率又 非常高的属性,建议还是使用传统的CLR属性去实现。

    暂时想到这么多了,以后有新的认识再补充补充。

    Powered By D&J (URL:http://www.cnblogs.com/Areas/)
  • 相关阅读:
    DNS智能解析的搭建与配置
    使用dnsmasq快速搭建内网DNS
    安装Fedora 21工作站后要做的10件事情
    MySQL + KeepAlived + LVS 单点写入主主同步高可用架构实验
    SOC-EDS之DS5安装和破解
    vs2015安装与卸载
    opencv实现的图像缩放
    基于Haar+Adaboost的人脸识别
    win10+python3.7+Anaconda3+CUDA10.0+cuDNN7.5+tensorflow_gpu1.13.1+opencv4.1.0 教程(最新)
    图片合成视频
  • 原文地址:https://www.cnblogs.com/Areas/p/2169744.html
Copyright © 2011-2022 走看看