相信只要開發過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時,作爲通信傳遞的參數值不能使用雙向綁定