剖析WPF依赖属性

    这节来讲一下WPF中的依赖属性 (Dependency Property)

【了解属性和字段】

    我们知道,属性是面向对象语言中用来封装字段的外衣,它像是字段对外界的桥梁,我们可以通过属性来验证数据的合法性或控制对外的访问性等等。每个属性的背后都有其对应的一个字段做支撑,就算是自动属性,在编译时系统也会创建其字段,只不过自动属性是微软给我们的语法糖罢了。在C#中,属性最后是会编译成两个方法:get_属性名set_属性名(如果是只读属性,则没有set方法,反之没有get方法)。

    编译成方法,属性就不会占用太多空间,因为方法存在于内存公共的方法区,每个实例的创建不过是多一个指向该方法的指针。但是字段不一样,每个实例创建的创建,都会在内存中开辟对应的空间来存放字段,一个类中的字段越多,它在内存中占用的空间就越大,理解了这个理论,下面我们来正式说明什么是依赖属性,为什么要有依赖属性。

    

【什么是依赖属性】

    我们使用一个控件,可以看到这个控件有很多的属性,有属性就有字段的内存开销,但实际上对于一个控件,我们大多数只会使用其部分常用属性,比如Button我们最常使用Content,Height等属性,那些不经常使用的属性相当于白白占用着内存。当我们写一个复杂的XAML页面,涉及到很多控件的使用时,这种浪费内存的现象就很严重。

    对此,微软在WPF中引入了依赖属性(Dependency Property),依赖属性允许没有自己的字段,可以通过Binding绑定到其它对象的属性或者说数据源上,从而获得值,这种依赖在其它对象上的属性,就是依赖属性,当明确了它的功能,我想大家就不会对依赖二字产生疑惑了,依赖属性没有自己的字段,只在使用时通过Binding从别的对象身上获取,给自己临时创建内存空间,这样不使用就不会有多余内存消耗。

    包含依赖属性的对象称为依赖对象(Dependency Object),这种对象需要继承DependencyObject这个基类,实际上,WPF中的控件,都继承了DependencyObject这个类,控件中的大部分属性都是依赖属性,这样我们才能通过Binding去绑定值(不熟悉Binding的同学可以参见前文Binding(一):数据绑定系列),才不会有内存浪费现象的发生。

【从代码中学习依赖属性】

    下面我们通过代码来学习一下如何声明并使用依赖属性,请先看我写好的一段代码:

public class Pikachu : DependencyObject
{public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName",typeof(string),typeof(Pikachu));
}

    上文说到,使用依赖属性必须要继承DependencyObject类,另外,声明

依赖属性,需要使用public static readonly三个修饰符修饰,实例依赖属性也不是通过new操作符,而是通过DependencyProperty的Register方法来获取

    依赖对象的名字,有个约定,就是以Property为后缀,在C#中有很多命名约定,比如接口用I做前缀,特性用Attribute做后缀等等,这样做都是为了有个良好的命名规范,做到见名知意。

    Register方法有三个重载,此处用的是其三个参数的重载,它还有四个参数和五个参数的重载。

  • 第一参数是指定依赖属性的包装器名称是什么(包装器就是用来包装依赖属性的,通过一个属性来包装依赖属性供外部使用,具体下文会讲,此处先做了解)

  • 第二个参数是指定依赖属性要存储的值的类型是什么

  • 第三个参数是指定依赖属性属于哪个类的,或者说是为哪个类定义依赖属性

  • 其它重载中第四个参数是指定依赖属性的源数据,用于提供给调用者此依赖属性的信息

  • 其它重载中第五个参数是自定义的依赖属性生成时的验证回调

    声明了依赖属性,但是如何给依赖属性赋值呢,这就要用到DependencyObject基类中的方法了,我们使用其中的SetValue方法和GetValue方法来操作依赖属性的值,请看下面改动后的代码:

public class Pikachu : DependencyObject
{public string PikachuName {get => (string)GetValue(PikachuNameProperty); set => SetValue(PikachuNameProperty, value); }public static readonly DependencyProperty PikachuNameProperty =DependencyProperty.Register("PikachuName", typeof(string), typeof(Pikachu));
}

   上述代码,就是一个比较完善的声明依赖属性并通过包装器将依赖属性暴露出去的例子,属性PikachuName就是依赖属性的包装器,在get块中通过GetValue方法传入依赖属性的名字获取依赖属性的值,在Set块中通过SetValue方法,给依赖属性赋值,对依赖属性的这层包装,使得我们在外部操作依赖属性变得简单,这也是为什么我们在正常使用中感觉不到依赖属性的存在,因为字段也好,依赖属性也好,我们在外部看到的操作的都是它的属性。

    下面通过一个实例展示一下依赖属性的使用:

    前台代码是一个名为btn_show的Button控件,后台代码如下:

public MainWindowBase()
{InitializeComponent();this.DataContext = this;Data = "我是皮卡丘";Pikachu pikachu = new Pikachu(); //使用Binding操作类将皮卡丘对象的皮卡丘名字依赖属性关联到Data上BindingOperations.SetBinding(pikachu,Pikachu.PikachuNameProperty, new Binding("Data") { Source = this });//将按钮的Content依赖属性绑定到皮卡丘的皮卡丘名字包装器上btn_show.SetBinding(Button.ContentProperty, new Binding(nameof(pikachu.PikachuName)) { Source = pikachu });
}

    这个例子的逻辑是有一个名为Data的属性作为数据源,先将皮卡丘对象的依赖属性绑定到Data数据源上,再将Button的Content依赖属性绑定到皮卡丘对象的依赖属性包装器上,这就形成了一个Binding链,运行效果如下:

    整个过程中,只有Data属性是有字段在背后支撑的,它存储了“我是皮卡丘”这个数据,皮卡丘对象和Button对象都是依赖属性,不占内存空间,它们之间使用Binding关联,形成数据通道,这样就实现了一块内存,供给多处使用。按照之前的编程模式,需要皮卡丘和Button各自开辟一段空间存储Data来的数据,现在由三块内存节省为一块内存,这就是依赖属性对于节省内存的效果。

【从源码分析依赖属性】

    下面我们来分析一下,为什么依赖属性不是用new实例,而是要注册,以及Get/SetValue的操作依赖属性值的原理。

    我们先从Register方法看起:

    Register的三个和四个参数的重载都指向了五个参数的重载,我们主要看一下这五参数重载的方法里边都有什么。方法体里边,前几行实际上是一些验证代码,当参数有误时,会抛出异常。紧接着的是一个返回依赖属性对象的RegisterCommon方法,从名字和返回值来看这就是最核心的方法了,我们接着跟进去看:

    代码内部第一行使用FromNameKey生成了一个key对象,这个FromNameKey是Dependency类的一个内部类,它构造器需要传入的包装器名称和依赖对象所在的类的Type,    这个类及构造器代码如下:

    构造器第三行代码比较重要,我们可以看到,这个类通过传入的参数两者异或生成了一个hashcode,经过这个异或运算,那就保证了同一个类,同样的包装器名称生成的hashcode是一样的。

    同时这个类重写了GetHashCode方法,就是把异或生成的hashcode返回出去了。

    了解了这个类,我们再回到RegisterCommon类中,接着往下看,下面是一个线程同步块:

    这个代码块里边,出现了一个PropertyFromName参数,看样子是个集合,我们找到这个属性的定义处,发现它是个全局的HashTable:

    那这个代码块的意思就明了了,目的就是判断生成的Key是否已存在,如果存在,就抛异常,从这里就控制了,在类内部定义两个相同包装器名称的依赖属性是不允许的,实际上也必须是这样,同一个类中,属性肯定是不能同名的,依赖属性也是如此,那我们从此处还能获得一个信息,就是PropertyFromName肯定和我们要生成的依赖属性有很大的关系,具体我们继续往下看代码:

    如果没有传入依赖属性的源数据,系统会生成默认的源数据,在往下看是一些校验逻辑,具体内容此处就不分析,有兴趣的可以自己点进去看,紧接着就到代码核心了:

    经过层层把关,依赖属性终于new出来了,new出来后,下面我们又看到PropertyFromName的影子了:

    原来PropertyFromName是存储依赖属性的一个集合,所有new出来的依赖对象都存储在这里,它的hashcode就是之前通过FromNameKey类异或出的。

    最后,通过return,返回了这个依赖属性 ,至此,依赖属性的整个创建过程解析完毕。

    我们再来了解一下依赖属性的值的读取:

    先看GetValue方法:

    前几句代码还是校验,核心代码是最后一句,此处涉及到了依赖属性的GlobalIndex属性,这个属性是系统经过一系列算法得出的,具有唯一性,我们看到,这个GlobalIndex传入了名为LookupEntry方法中,Entry是入口的意思,从方法名上看,我们能得知,是根据GlobalIndex找到了一个访问入口,实际上,这个入口就是依赖属性值的访问入口。

    我们进入GetValueEntry方法中查看,会找到一个名为_effectiveValues的属性,这是一个EffectiveValueEntry类型的数组,原来,依赖属性所有的值都存放在这个数组中,根据依赖属性唯一的GlobalIndex,我们就能从这个数组中找到依赖属性的值。

    再来看SetValue方法:

    其实明白了GetValue,SetValue也就很好理解了,道理都是一样的,根据依赖属性的GlobalIndex值获取到入口,更新上新值,我们进入SetValueCommon方法中看,代码比较繁琐,实际上的流程有三块:

  • 判断值是不是DependencyProperty.UnsetValue,如果是,则清除依赖属性的值,所以我们要想对依赖属性设置空值,不要用null,要用DependencyProperty.UnsetValue

  • 判断能否找到入口,如果没有入口,则新建一个入口对象,将值放进去,有入口则更新值

  • 最后,通过UpdateEffectiveValue方法对依赖属性的值做一些处理

    至此依赖属性的读取流程解析完毕。

  

    本节到此结束...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/300558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring与SpringMVC集成出现的问题

这几天在学习SpringMVC,在与Spring集成时,出现了两个小问题,记录下来,提醒自己,同时如果大家遇到同样问题,能够帮助大家的话,那我就感到幸哉了!1.java.lang.NullPointerExceptionSEV…

java drawline_Java Graphics.drawLine方法代码示例

import javax.microedition.lcdui.Graphics; //导入方法依赖的package包/类public void draw(Graphics g) {g.setColor(255, 255, 255);g.fillRect(0, 0, getWidth(), getHeight());g.setColor(200, 200, 200);int baseLineY;if (signal.baseLineY() > 0) {baseLineY (int)…

Active Directory的用户属性说明

Active Directory中User对象属性User Object User Interface Mapping The following tables identify the property pages supplied by the Active Directory Users and Computers snap-in. Each table identifies the user interface elements of the property page and the A…

柳传志与马云绸缪宏观经济“冬天影子”

春江水暖鸭先知。尽管认同中国经济的繁荣将继续保持10~20年,中国商界的领军者和经济学家上周六还是提醒企业必须警惕可能到来的调整。 “我为什么上市?一个很重要的原因,是我在准备‘过冬’。”12月8日,在“2007(第六届)中国企业领…

你在孩子身上偷的懒,终将会变成最大的遗憾

全世界只有3.14 %的人关注了青少年数学之旅我们来看一个非常有趣的统计:2007年—2016年全国高考状元父母职业统计最优秀的孩子大多数出自教师家庭。很家长说,教师有着和孩子一样的寒暑假,有着教书育人的先天优势,我们普通人工作忙…

巧用Environment.UserInteractive 实现开发和生产环境的分开调试部署

概述平常我们在做服务开发的时候,经常是希望本地可以直接调试;在生产环境是以服务允许的;这时候,一般的做法写2段代码,需要什么环境就注释那段代码,这样很麻烦,这时候就可以利用Environment判断…

aggregation java_Elasticsearch Aggregation 多个字段分组统计 Java API实现

现有索引数据:index:schooltype:student---------------------------------------------------{"grade":"1", "class":"1", "name":"xiao 1"}{"grade":"1", "class":&q…

开发们 点广告-赚点BT币

2019独角兽企业重金招聘Python工程师标准>>> http://freebitco.in/?r14320 转载于:https://my.oschina.net/wangtao/blog/180765

Asp.net页面的生命周期

介绍Asp.net是微软.Net战略的一个组成部分。它相对以前的Asp有了很大的发展,引入了许多的新机制。本文就Asp.net页面的生命周期向大家做一个初步的介绍,以期能起到指导大家更好、更灵活地操纵Asp.net的作用。当一个获取网页的请求(可能是通过…

太赞了!微软《dotnet中文手册》火了,完整PDF开放下载!

这是微软高级架构师基于最新的.net编写,循序渐进地对.net/C#进行讲解。对于零基础可以作为.net的快速入门教材,对于高级程序员而言,这也是你的进阶之路,今天来了,就是缘份,看到了就送给你!资料介…

你穿衣品味还不如AI,这有一款时尚着装网络模型

全世界只有3.14 %的人关注了青少年数学之旅有一件衣服的时候,怎样的小改动可以提升其整体的时尚性?近日 UT 奥斯汀、康奈尔大学、乔治亚理工和 Facebook AI 研究中心的研究者提出了一种名为 Fashon 的模型,用于给一件衣服进行改进&#xff0…

java的安装包下载 百度云_Java开发相关安装包网盘下载链接分享

虚拟机VMWare10软件 网盘链接:链接:https://pan.baidu.com/s/1dff3m0EBzMegtgRaJFEadQ提取码:8cafCentOS-6.6-x86_64-bin-DVD1.iso 网盘链接:链接:https://pan.baidu.com/s/13wIXuoJkza95U-rBs0AUpQ提取码:…

Git 远程分支的查看及相关问题

命令:git ls-remote -t 或者 git ls-remote --tag 运行结果如下: 0975ebc0f9a6b42ecbe066a50a26a678a0753b4d refs/tags/1.0 fecbbe3a7ed4c8b5305ddd77b69d7ee64acbefe5 refs/tags/1.0^{} 解读: 由于 tag 1.0 是一条带注释的 t…

分割移动微小物体

思路&#xff1a;1. 前后两帧相减&#xff0c;得到差分图像2.在差分图像里求像素最大的点&#xff08;最亮的点&#xff09;这样就得到&#xff0c;移动微小物体#include "check.h"#include <iostream.h>voidmain(){ DWORD t1,t2; IplImage *frame0; …

一个非常实用的Python SSH库

前言 Python的Paramiko库&#xff0c;它是一个用于实现SSHv2协议的客户端和服务器的库。通过使用Paramiko&#xff0c;我们可以在Python程序中轻松地实现远程服务器的管理、文件传输等功能。特别做智能硬件产品的同学要熟悉它&#xff0c;因为它能为你减少很多麻烦&#xff0c…

在腾讯,我的试用期总结!

在腾讯的 3 个月&#xff0c;我经历了些什么&#xff1f;大家好&#xff0c;我是鱼皮。最近不少小伙伴顺利毕业&#xff0c;入职了新公司&#xff0c;开始了人生第一段正式工作经历。但对于很多公司&#xff0c;如果你想要成为正式员工&#xff0c;是需要先经历几个月的试用期的…

这6个动作,据说只有20%的人能做到!| 今日最佳

全世界只有3.14 %的人关注了青少年数学之旅你的手指够灵活吗&#xff1f;能做出下面这些动作在回答吧。据说就是下面这6个动作&#xff0c;据说只有20%的人能做到哦&#xff01;大家不妨试试&#xff0c;看看你是不是这20%中的一员。PS&#xff1a;反正笔者已经十指已打结了...…

打造个性化的Internet Explorer

作者&#xff1a;孙辉 在Microsoft的软件哲学中&#xff0c;框架窗口是一个十分重要的角色&#xff0c;这类窗口简直无处不在。所谓框架窗口&#xff0c;就是四个窗口边上具有停靠对象能力的窗口对象&#xff0c;从现象上看&#xff0c;框架窗口有十分特别的“边”&#xff0c;…

java wrapper linux_Java Service Wrapper linux 服务 java 自启动

测试通过:以java应用程序为例&#xff0c;打包为tianlong.jar&#xff0c;程序入口为tianlong.QueueTest。在tianlong目录下&#xff1a;mkdir bin conf lib 创建3个目录在linux的/opt下建立tianlong目录&#xff0c;复制tianlong.jar到/opt/tianlong/lib目录下。1、下载Java S…

Serializable和Parcelable

Serializable&#xff08;接口&#xff09;通过intent 的bundle传递参数Bundle bundle new Bundle(); bundle.putSerializable(IntentKeys.IMG_ARR_ENVIR_IMG, mArrListEnvir_img); intent.putExtras(bundle);mListEnvir (ArrayList<EnvirImg>) (bundle .ge…