WPF的数据绑定笔记摘录
所谓数据绑定,其概念很简单,数据绑定决定了一个Source的改变会不会,以及怎样自动通知并改变Destination。
Source可以是数据表,可以是XML,可以是一个内存数据,也可以是一个控件的某个属性值。
Destination必须是一个Dependency Object的某个属性。
这样的设定,使得数据的需求者(往往同时也是业务的处理者)和界面的提供者得以松绑。数据需求者仅需完成本身的业务逻辑,例如,被击中导致当前玩家掉血10点。至于这个减掉的10点如何在界面逻辑中表现,这个完全跟业务没关系,我一个业务的制作者没有知道它的必要。
这样的松绑看似简单,但解决了一个潜在的问题:界面的开发往往比业务更费时费力,松绑后,业务可以极速地实现并投入测试,而界面则可以快速地铺量、修改、变化,而不用因此牵扯到任何逻辑和业务流程。
数据绑定事实上并不需要去参考WPF的实现,C++实现的数据绑定一抓一把,UE3什么的实现都中规中矩,但WPF确实是一个很经典的体系,而且很清晰的概念。
木有代码?呃,您可以Reflector其……
基本来说,这么几个流程,我们先按照最简单的OneWay来说:
有这么几个概念:源数据,就是将要发生改变的属性所在的数据,里面包含大量属性,其中有一个即将发生改变。目标数据,就是将要因为源改变而改变其属性的数据。Binding:指示“对源数据中的X元素的修改”将使得我“修改目标数据中的Y属性”。
首先,源数据必须实现INotifyPropertyChange接口,这个接口实际上指明了源数据这个对象中必须有一个event,可以挂接一个OnPropertyChanged的代理。
Dependency Object虽然没有显示实现这个接口,但因其内部调用流程做了特殊处理,因此可以理解为实现了这个接口,所以WPF界面元素中的很多属性基本上都支持了数据绑定。
然后,解析时,发现了这个Binding对象,就会将这个Binding对象的相应方法以代理的形式注册到这个event中。
所以WPF的使用者绝对不会注意到有一个这个接口存在,因为你根本不必手动调用它。
再然后,Source发生了变化。
这时,如果是Dependency Object派生类的属性发生变化,因为一般调用的都是SetPropertyValue(DependencyProperty, value),这个方法内部自动就会调用OnPropertyChanged代理,转而调用了Binding对象的相应方法,而Binding对象的相应方法将按照配置,寻找到目标元素的目标属性,通知其已被改变。
而如果是用户自定义的数据结构呢?那您必须实现INotifyPropertyChange接口,这样Binding才可能把自己挂到Source上,并在Source发生变化时(这时机你一定是知道的)通知OnPropertyChanged代理。
有这三步基本就齐活,剩下的就无非是一些中间检查是否实现了接口之类的工作,是否有Convertor,以及数据改变后,触发动画之类的工作了。
于此类同,ItemsControl对ItemsSource要求一定要实现INotifyCollectionChange接口,跟这套东西基本上是一样的。
由此可以总结出数据绑定关键的几个组分:
必须要有一个数据源,这个数据源知道自己属性什么时候将要发生更改(也就是必须调用数据源的方法使得其属性发生更改),发生更改时,数据源将通知绑定者。
必须要有一个绑定者,绑定者接收到数据源的改变通知后,根据自己的配置,决定接下来使得哪些或者哪个目标属性得到改变。
目标和目标属性,就不用说了。
只要完成了这几个组分,基本上数据绑定就没的跑了。
而这几个组分的关键就在于目标属性和绑定者采取什么方法互相访问和存储,这个可以根据当前的情况自由选择了。
对于WPF,您可以认为Binding存储于Dependency Object的值表中。这就体现出来了WPF这个Dependency Property设计的优势,值表里的所有值都是无差别的object,其类型是由Dependency Property来决定的,所以用这些object存一个int可以,存一个Binding也是松松的,无非就是个怎么取得数据的问题,都知道数据有改变了,怎么获取,就应该不是问题了吧?
而xaml脚本的Binding会被解释为FrameworkElement的SetBinding调用,实际上就将Binding对象本身设到了这个值表中,同时,向源中挂接自己的代理。源改变了,Binding得到通知,然后通知挂接者(某个Dependency Object)相应的Property发生了变化,Property再去值表中取得当前的真实值,再通知其它逻辑自己发生了改变即可。
备注:WPF的Dependency Object体系
Reflector了WPF的数据绑定实现,会发现它本身实现其实没有多复杂,但是却跟我们平常对事物的认识稍有不同,所以这里解释一下:一般来说我们来写一个程序,总是习惯性地去划分好模块关系、类的关系、对象关系,类里面有哪些成员是死的,对象之间的调用型是既定的。然而WPF数据绑定完全不同。WPF数据绑定的核心同时就是它所有UI元素的基类:Dependency Object。事实上,Dependency Object这套体系唯一所做的一切事情,就是打破了“类里面的成员是死的”这个基本假设。
C#本身已经具有许多动态语言的特性了,但是毕竟还是一个编译语言,这就注定很多东西一旦编译就不能再改变了。如果顺着这个思路做下去,迟早是另一套MFC,程序员的思维去做界面,那干嘛要做WPF和Silverlight呢。而Dependency Object这样的方法就比较巧妙了,实际上相当于在C#上又构建了一个……呃,怎么说呢?虚拟机?它自己用自己的方式来保存属性(Dependency property),自己解析,自己按照自己的方式来处理。
Dependency Object的实现很简单,所有的Dependency Object都是一样的,基本没有什么有意义的数据成员,最关键的是保存了一个值表,Cache了每个Property的取值,可以是实值,也可以是Binding。而每向一个Dependency Object类注册一个static Dependency Property,虽然不是真刀真枪地在Class里面写了一个,其概念相当于为这个类增加了一个属性。SetProperty的过程,就是通过Property找到相应的值表元素,并且修改之的过程。
所以你看到某个具体的WPF类,可以将其视为一个具体的C++类,而Dependency Property就可以将其理解为一个具体的C++成员变量。而WPF之所以要费尽这么写,主要是为了做一系列的内部工作。
而对于咱们来说,真正关键的只是这些内部工作,其中最主要的是两个,一个是自己改变时调用Binding代理的这个过程,另一个就是值表这个东西。
跟着看挺痛苦的,去年这时候跟的这个东西,然后写了一堆笔记,最近要做这个东西所以重新整理了一下之前的笔记,写了这篇文字。中间的很多东西是提炼出来的,也有一些是根据印象整理的,可能有所疏漏,有些地方也是简化了不少,不过基本上应该就是这个思路了。
自己实现的绑定系统基本快要完成了,那之前再聊聊UE3又是怎么处理这个问题的,对比一下。敬请期待下文。