WPF 依赖属性 DependencyProperty

前言:

在使用WPF的时候,总会有一个疑问,依赖属性跟普通的类属性有什么区别,微软要在WPF引入它想要解决什么问题?如果不解除这个疑惑,在编程的时候心里总会不踏实。因此我在网上找了一些资料,终于弄懂了它的由来和机制,特意和大家分享,如有不足,请各位指正!

依赖属性的由来:

在WinForm时代,每个控件类(如TextBox)都会包含许多属性,但是真正用到的少之又少(如text),其他属性就会白白耗费内存资源。那么问题来了,如果只生成一个控件对象,“无用”的属性对性能影响不大,但是当你在一个窗体实例化几十个控件对象的时候,那内存消耗就很可观了。所以,我们应该引入一个机制,在实例化对象的时候,按需“给”它属性,这样子就不会让“无用”的属性白白占用内存资源,因此依赖属性诞生了。

依赖属性的定义:

先比较浅显地讲讲依赖属性的定义,即本身可以没有值,而依赖于其他数据源而取得值的属性。

自定义依赖属性:

在深入了解依赖属性之前,很有必要弄清楚依赖属性是如何敲出来的。假设有一个Person类,现在我要定义一个依赖属性NameProperty,代码如下:
 

    //依赖属性必须在依赖对象DependencyObject或其子类中定义
    class Person : DependencyObject
    {
        //CLR属性包装器,使得依赖属性NameProperty在外部能够像普通属性那样使用
        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }
 
        //依赖属性必须为static readonly,后续讲解
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(Person), new PropertyMetadata("DefaultName"));
    }

代码分析:
1.依赖属性必须在依赖对象(DependencyObject)或其子类中定义,因此你能够发现绝大多数WPF控件都是继承自DependencyObject的;

2.CLR属性Name的作用之一是为了让外界在使用依赖属性时,像使用普通属性一样;

3.依赖属性约定以Property结尾;

4.依赖属性必须声明为static readonly,原因后续讲解;

5.生成依赖属性需要Register方法而不是new,方法的参数依次是CLR属性名、依赖属性对应的类型(注意:所有依赖属性真正的类型是DependencyProperty,这里的意思是依赖属性所“存储”的值的类型)、依赖属性的宿主类、依赖属性的默认元数据;

6.默认元数据有许多重载,这里使用的是只有一个参数的重载类型,传入的参数是依赖属性的默认值。

Register方法的内部机制:

很明显,自定义依赖属性的关键在于Register方法,它的内部机制是:

1.创建DependencyProperty实例dp;

2.根据CLR属性名和宿主类型名生成哈希码hashcode,用于唯一标示依赖属性;

3.在DependencyObject类中有一个全局变量:哈希表PropertyFromName,用于存储所有依赖属性的hashcode,因此在这一步检查是否已经存在相同hashcode,若无则以hashcode和实例dp作为键值对存进哈希表PropertyFromName,否则报错;

4.最后返回实例dp。

注意,hashcode并不是实例dp的哈希值,而是一个名为GlobalIndex的int全局变量,由DependencyObject内复杂的算法算出来。

GetValue和SetValue方法的内部机制:

也许这个时候你会有疑问:既然依赖属性声明为static,就代表依赖属性只有一份拷贝,那么我定义多个Person类的实例对象时,一旦改变其中一个对象的依赖属性时,其他的对象岂不是都跟着改变?这时候就需要探讨GetValue和SetValue的内部机制了,这里只说GetValue,因为弄懂了GetValue后,对SetValue的机制就自然明白了。

在GetValue函数中能够看到一个EffectiveValueEntry类的实例,它就好像是一个房间入口(Entry),进去后就能获取想要的值;在每个EffectiveValueEntry的构造函数中有一个名为PropertyIndex的参数,传入的值其实就是上面提到的唯一标示不同实例的GlobalIndex,这样子,每一个实例对象的依赖属性值所对应的入口(Entry)就不一样了;而在DependencyObject类中能够看到 private EffectiveValueEntry[] _effectiveValues 这样一个语句,当某个实例的依赖属性被读取时,就会到这个数组检索它对应的EffectiveValueEntry,如果找不到则代表依赖属性还没有被手动赋值(如Name="LiMing"),DependencyObject类的内部算法就会返回依赖属性的默认值(在Register方法第四个参数中设定)。

这样就明白了,被static关键字所修饰的依赖属性对象真正作用只是用来检索真正的属性值,而不是用来存储值的;而用于检索值的是GlobalIndex,因此为了保证GlobalIndex的稳定性,需要添加readonly关键字。

所以,依赖属性是以牺牲算法来节省内存空间。

结语:

其实依赖属性的内部机制比上述复杂的多,如果想真正理解其中的原理,还是需要各位自己去钻研。Thanks for your attention!
原文链接:https://blog.csdn.net/NA_OnlyOne/article/details/53308243

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你在自定义的 PasswordBox 类中使用了依赖属性 Password,并且设置了 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault 选项来实现双向绑定。 这段代码看起来是没有问题的,它定义了一个名为 Password 的依赖属性,并且在设置和获取属性值时使用 GetValue 和 SetValue 方法。同时,通过 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault 选项,确保该属性默认支持双向绑定。 但是需要注意的是,这段代码只是在自定义的 PasswordBox 类中定义了一个属性,实际上你还需要在 XAML 中使用这个自定义的 PasswordBox 控件,并将它绑定到你的 ViewModel 中的 Password 属性。 在 XAML 中,你可以这样使用你的自定义 PasswordBox 控件: ```xml <local:CPasswordBox Password="{Binding MyPassword, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> ``` 这里的 `local` 是你在 XAML 文件开头添加的命名空间引用,指向你定义的 CPasswordBox 类所在的命名空间。 然后,在你的 ViewModel 中声明一个名为 MyPassword 的属性,用于实际存储密码值。当用户在自定义 PasswordBox 中输入密码时,双向绑定会自动更新 MyPassword 属性的值。同样地,当 MyPassword 属性的值改变时,也会自动更新到自定义 PasswordBox 控件中。 如果你按照以上方法实现了双向绑定,而且没有出现错误或异常,那么你的代码应该是没有问题的。如果你仍然遇到问题,请提供更多的代码和错误信息,以便我能更好地帮助你解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值