原创系列之:Flex数据绑定基础介绍[转]
转自:http://www.riameeting.com/node/979
相信只要开发过Flex应用程序的读者都已经使用过数据绑定(Data Binding),数据绑定是Flex非常重要的特性之一,它就像一种魔法一样,能快速让你将应用程序中两个不同的部份通过数据绑定联系起来,大大提高了 开发的效率,这也是让Flex如此流行的特性之一。大多时候我们并不需要了解数据绑定背后的机制,然而,随着在Flex应用程序规模不断增大,数据绑定特 性也被开发人员使用得越来越多,其带来的问题也逐渐显现出来,正因为它像魔法一样,使用起来非常简单,因而很多开发人员并未去深入了解数据绑定背后的工作 机制,致使在应用中使用不合理,这样不仅会给应用程序带来性能上的问题,也会使得程序难以维护,甚至可能带来不可预料的Bug,很难跟踪处理。
本节首先会简要介绍数据绑定的基本概念及其常见应用,然后带领大家深入“幕后”,剖析”魔法”倒底是怎样产生的,最后在最佳实践部分给大家介绍一下在实际运用数据绑定时常犯的错误及纠正方案。
Flex数据绑定简介与常见应用¶
数据绑定是这样一种特性,它能方便的让一个对象的数据自动反映到另外一对象的数据上,通常需要我们提供“数据源属性”和”数据目标属性“,有了源与 目标,当我们使用数据绑定特性时,它会自动帮我们把”数据源属性“的值拷贝到“数据目标属性“值中去。本质上来说,Flex绑定功能的实现也是对事件机制 的运用体现,在后序文章:数据绑定背后的故事部分会详细阐述这一点。
Flex为我们提供了多种使用数据绑定的方式,归纳起来通常有以下几种:
- {}绑定实现
- <Binding />标记绑定实现
- 应用BindingUtil类绑定实现
- ChangeWacher绑定实现
- [Bindable]元标签绑定实现
- 双向绑定
下面以一非常简单的程序为例,以不同的数据绑定方式来实现同样的绑定功能,以便读者能对比不同数据绑定实现方式之间的不同之处。
最终程序运行效果如图所示,当我们在上面的文字输入框输入文字时,借助绑定的功能,下面的文字输入框将同步显示在上面输入框输入的内容:
{}数据绑定运行效果
{}绑定实现
完整程序源码如下:
DataBindingSample.mxml
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle" />
</s:layout>
<s:TextInput id="txtInputA" />
<s:TextInput id="txtInputB" text="{txtInputA.text}"/>
</s:Application>
|
程序非常简单,{}花括号表明我们希望将txtInputA组件的text值绑定到textInputB的text,这样,当在txtInputA 中输入文字时,输入的内容会自动绑定到txtInputB的输入框中去。短短几句便实现了数据显示同步的功能,这便是绑定的强大功能。
<Binding />标签绑定实现如下面代码所示(此处略去Application标签及布局相关的代码)
1 2 3 |
<fx:Binding source="txtInputA.text" destination="txtInputB.text" />
<s:TextInput id="txtInputA" />
<s:TextInput id="txtInputB" />
|
这里我们使用了Binding标记(本质上<Binding />标记对应的是Binding类),并由source和destination明确的指出绑定的来源及目标。
BindingUtils类绑定实现
使用BindingUtils类进行绑定又包括两种方式:
- BindingUtils.bindProperty()方法主要用来直接绑定源与目标的属性(见下面代码):
DataBindingSample03.mxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
private function onCreationComplete():void {
BindingUtils.bindProperty(txtInputB, "text", txtInputA, "text");
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle" />
</s:layout>
<s:TextInput id="txtInputA" />
<s:TextInput id="txtInputB" />
</s:Application>
|
这里唯一需要注意的是BindingUtils.bindProperty()方法参数的顺序,需要绑定的目标对象及属性在前,而源对象及属性在后。
- BindingUtils.bindSetter()方法指定当绑定源的属性发生改变时去执行指定的函数:
DataBindingSample04.mxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
private function onCreationComplete():void {
BindingUtils.bindSetter(setterHandler, txtInputA, "text");
}
private function setterHandler(source:String):void {
txtInputB.text = source;
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle" />
</s:layout>
<s:TextInput id="txtInputA" />
<s:TextInput id="txtInputB" />
</s:Application>
|
[Bindable]元标签绑定实现
来看另外一个实例,假设我们有一Person的类[见代码Person.as]用来存储firstName和lastName,在主程序中[见代码Behind_DataBinding.mxml],首先创建一Person类实例并分别为firstName和lastName赋值,最后通过绑定将person的值绑定到Label组件显示 出来。当我们点击“Change Name”按钮时,在”click“事件处理函数中改变person的值,此时绑定person的Label标签值也会相应改变。
代码清单 Person.as
1 2 3 4 5 6 7 |
package com.jexchen.model {
[Bindable]
public class Person {
public var firstName:String;
public var lastName:String;
}
}
|
主程序也非常简单,见代码清单Behind_DataBinding.mxml
代码清单 Behind_DataBinding.mxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import com.jexchen.model.Person;
[Bindable]
public var person:Person = new Person();
private function onCreationComplete():void {
person.firstName = "Anthony";
person.lastName = "Lee";
}
private function changeName():void {
person.firstName = "Bruce";
person.lastName = "Chan";
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
</s:layout>
<s:Label id="firstNameId" text="{person.firstName}" />
<s:Label id="lastNameId" text="{person.lastName}" />
<s:Button label="Change Name" click="changeName()" />
</s:Application>
|
注意到,我们直接在Person类前添加了[Bindable]元标签(直接在类前直接加[Bindable]相当于为类下面所有public属性 添加了[Bindable]元标签),在主程序中,为person实例也添加了[Bindable]元标签,然后将其直接绑定到Label标签用于显示。 这种绑定用法在企业应用开发中很常见,例如在MVC应用模式中,通常会在应用程序中设有称为Model的模型类,Model中存放不同的VO(值对象,如 这里的Person),然后在不同的子组件中引用并绑定到Model中的VO属性值,当特定的事件触发后,通过侦听事件去改变Model中的VO值,而页 面中绑定部分的值将跟随改变,从而实现多组件(页面)之间数据共享与同步。在讲到MVC模式时会以实例的方式详细分析这一用法。
元标签(metadata):在Flex中中经常会遇到类似[Bindable]这样以[]括起来形式的修饰符号,这类 符号被称之为元标签,元标签的作用主要是为编译器提供一些额外的信息,元标签本身不为被编译生成到SWF文件当中去,它只是用来告诉编译器如何来编程程 序,Flex SDK中为开发人员提供了大量的元标签,例如[Bindable]、[Event]、[Embed]均是很常用的元标签。在不同的应用场合,元标签可以加 在变量、类或方法的前面。
在类定义前添加[Bindable],绑定仅作用于类下面所有public的属性(所有这些属性均可作为源进行数据绑 定),而不会应用于类下面的private及protected属性。若要使非public属性也能作为数据绑定的源,则必须在属性定义前添加 [Bindable]元标签。注意,若在类定义前已经加了[Bindable],则不应在类的属性前再添加[Bindable]
[Bindable]的完整形式为[Bindable(event=”propertyChange”)],实际上我们简写为[Bindable] 默认指的是当“propertyChange”事件产生时会触发绑定功能应用,其中“propertyChange”事件的派发是由Flex编译为我们自 动生成的代码去完成的(在项目属性中的Flex编译参数中添加 -keep 参数),读者可尝试在我们前面的示例中将[Bindable]改为 [Bindable(event=”propertyChange”)],结果是一样的。
当然,我们也可以使用自定义事件去触发绑定,由于使用自定义事件,则自定义的派发由开发人员自己实现,在实际应用开发中也经常这样这样使用。
将Person类的改为:
1 2 3 4 5 6 7 |
package com.jexchen.model {
[Bindable(event="customEvent")]
public class Person {
public var firstName:String;
public var lastName:String;
}
}
|
相应主程序更改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
import com.jexchen.model.Person;
[Bindable(event="customEvent")]
public var person:Person = new Person();
private function onCreationComplete():void {
person.firstName = "Anthony";
person.lastName = "Lee";
dispatchEvent(new Event("customEvent"));
}
private function changeName():void {
person.firstName = "Bruce";
person.lastName = "Chan";
dispatchEvent(new Event("customEvent"));
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
</s:layout>
<s:Label text="{person.firstName}" />
<s:Label text="{person.lastName}" />
<s:Button label="Change Name" click="changeName()" />
</s:Application>
|
由于我们更改了[Bindable]的默认方式,则在程序需手动派发相应的自定义事件才能触发绑定的实现(见上述代码所示)。
甚至可以在属性或方法前加多个事件绑定(),这样则可以让在不同的事件customEvent1和customEvent2派发时均可触发绑定功能。
package com.jexchen.model {
[Bindable(event="customEvent1")]
[Bindable(event="customEvent2")]
public class Person {
public var firstName:String;
public var lastName:String;
}
}
ChangeWatcher绑定实现
ChangeWatcher的使用也非常简单,例如,对person的firstName属性,使用ChangeWatcher的watch方法去 监测,一旦属性值发生变化,将会执行onWatcher回调方法,这里注意,默认情况下,person属性值改变会触发 “propertyChangeEvent”事件(Person类添加的是[Bindable]元标签),onWatcher参数需指明事件类型。
1 2 3 4 5 6 7 8 |
var watcher:ChangeWatcher = ChangeWatcher.watch(person, "firstName", onWatcher);
private function onWatcher(evt:PropertyChangeEvent):void {
firstNameId.text = evt.newValue.toString();
}
...
//当你要停止绑定时,手动调用
watcher.unwatch();
|
注意,ChangeWatcher.watcher()方法的返回值为ChangeWatcher实例,当我们需要停止绑定时,需手动调用unwatch()来解除绑定,以避免带来内存泄漏的问题。
双向绑定
前面我们实现的均是单一绑定功能,若希望在更改txtInputB组件text值的同时,txtInputA的text值也为同步变化,也即实现“双向绑定”的功能,我们可以为txtInputA的text属性再添加一次绑定即可,如下面代码所示。
<s:TextInput id="txtInputA" text="{txtInputB.text}"/>
<s:TextInput id="txtInputB" text="{txtInputA.text}"/>
在Flex4中,直接为我们提供了更为简捷的双向绑定功能,绑定也非常简单,以{}花括号和<Binding />标签两种方式如下:
//仅需在绑定符号{}外加上@符号即可
<s:TextInput id="txtInputB" text="@{txtInputA.text}"/>
//或者
//在<Binding/>标签中指定twoWay为true即
<fx:Binding source="txtInputA.text" destination="txtInputB.text" twoWay="true"/>
需要注意的是:
1. style或effect属性不能使用双向绑定
2. 当使用RemoteOject、WebService或HTTPServer时,作为通信传递的参数值不能使用双向绑定