13.1 MXML的数据模型 在Flex应用开发中,处理数据是非常核心的工作。MXML提供了简单的方式帮助开发者在客户端存储数据,而更复杂的应用则须要利用ActionScript类来定义数据模型。 13.1.1 <mx:Model> <mx:Model>适合描述树状层次结构的数据。<mx:Model>声明必须置于MXML主应用文件或MXML组件文件的根标签下。<mx:Model>数据模型示例的代码如代码13-1所示。 代码13-1:<mx:Model>数据模型示例 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white"> <mx:Script> <![CDATA[ import mx.controls.Alert; private function showAuthor():void{ mx.controls.Alert.show(bookModel.name +"的作者是:" + bookModel.author.firstName + " " + bookModel.author.lastName + "n" + " 订购数量:" + bookModel.quantity); } ]]> </mx:Script> <mx:Model id="bookModel"> <book> <author> <firstName>{txtFirstName.text}</firstName> <lastName>{txtLastName.text}</lastName> </author> <name>{txtName.text}</name> <quantity>{quantity.value}</quantity> </book> </mx:Model> <mx:Form width="288"> <mx:FormItem label="书名:"> <mx:TextInput id="txtName" /> </mx:FormItem> <mx:FormItem label="作者:"> <mx:TextInput id="txtFirstName" /> <mx:TextInput id="txtLastName" /> </mx:FormItem> <mx:FormItem label="订购数量"> <mx:NumericStepper id="quantity"/> </mx:FormItem> <mx:FormItem> <mx:Button id="button1" label="显示" click="showAuthor()"/> </mx:FormItem> </mx:Form> </mx:Application> 在上述应用示例中,用户填写完图书信息,点击“显示”按钮后,结果如图13-1所示。 图13-1 <mx:Model>示例结果 在这个例子中,可以看到,<mx:Model>声明的数据模型,最终被编译为ActionScript对象,通过其id进行引用。<mx:Model>能够很好地存储树型结构数据,但也仅限于此。其存储的数据局限于标量数据,不支持对数据的进一步处理。 在复杂的应用中,我们更经常使用ActionScript代码定义数据模型类。 13.1.2 ActionScript的DTO DTO,全称Data Transfer Object(数据传输对象),也被称作Value Object(简称VO)。DTO实际上就是简单的ActionScript类,该类的全部意义在于描述对象实体数据模型、存储数据,并在应用内部不同模块及应用与服务器之间传递数据。 与<mx:Model>定义的数据模型相比,DTO的属性变量不仅仅是标量类型(例如int、String等),也可以是复杂类型。DTO也能内嵌业务逻辑(例如数据校验、格式化等)。 一般来说,DTO类代码的最佳实践是,将属性声明为私有变量,并使用getter和setter方法提供属性的读写接口,DTO示例代码如代码13-2所示: 代码13-2:DTO示例 package com.longfei.bookLabs.bookStore.model { [Bindable] public class Book { public var name:String; public var price:Number; private var _quantity:int; public function Book() { } //5本起订 public function set quantity(value:int):void { if (value>= 5) { _quantity = value; } else { trace('不足起订数!'); } } public function get quantity():int { return _quantity; } } } 许多专家和文章建议将DTO或VO对象的属性变量设置为私有属性,并通过setter和getter访问器实现读写。但事实上,如果不须要对属性变量进行业务逻辑处理,例如校验或格式化,那么使用公共属性也不见得会带来什么坏处。 如果要在MXML中使用DTO对象,须要引入并实例化该类。可以使用MXML标签声明方式,也可以采用ActionScript代码声明并创建类实例。 如果使用MXML标签声明,须要定义指向DTO对象的命名空间,然后为该DTO对象设置id属性。 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:vo="com.longfei.bookLabs.bookStore.model.*" > <!―省略代码--> <!--使用MXML标签声明DTO对象实例--> <vo:Book id="tempBook"/> <!―省略代码--> </mx:Application> 用ActionScript代码创建DTO类实例的代码如下: <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"> <mx:Script> <![CDATA[ import com.longfei.bookLabs.bookStore.model.*; //ActionScript代码创建类实例 private var book:Book; ]]> </mx:Script> <!―省略代码--> </mx:Application> 13.2 深入解析数据绑定 数据绑定能帮助开发者快速地把应用界面与数据联系在一起,用户在应用界面上的操作,能够即时反应到绑定的数据上。 在本书前面章节的许多例子中,我们已经使用过数据绑定。数据绑定就是把数据源A和目标数据B关联在一起的过程,数据源A也许是属性变量、对象、方法,甚至是数组元素。使用MXML和ActionScript都能够实现数据绑定。 Flex数据绑定的背后是事件机制的应用。开发者使用不同方式(MXML或ActionScript)声明绑定的时候,即为绑定事件注册了事件侦听器,当数据源发生变化时,就会调度时间侦听器,实现数据绑定逻辑,默认的绑定逻辑就是将更新的数据复制到目标对象。 13.2.1 声明可绑定数据源――[Bindable]解析 数据绑定的数据源可以是属性变量、方法及ActionScript类对象实例。Flex通过元数据标签[Bindable]进行标识。标识为绑定数据源意味着当数据源变化时,能够调度事件,通知Flex更新目标数据。 Bindable元数据标签的签名如下: [Bindable] 或者 [Bindable(event=”eventName”)] 开发者一般忽略事件(event)名称,只使用[Bindable]标识可绑定数据源。这种情况下,Flex会默认地创建mx.events.PropertyChangeEvent类型事件,事件名为propertyChange。当数据绑定的数据源发生变化时,数据源会自动调度propertyChange事件,通知Flex将新值复制给目标数据。 如果在标识绑定时说明了事件,也就是说采用了[Bindable (event=”eventName”)]方式,开发者须要自己定义和调度事件。 我们依次说明如何声明属性、ActionScript类和方法作为可绑定的数据源。 声明可绑定属性 在属性变量定义之前,使用元数据标签[Bindable]声明属性可绑定。属性可以是公共属性、私有属性或被保护属性。声明如下: [Bindable] public var employee:Employee=new Employee(); [Bindable] private var acEmployees:ArrayCollection=new ArrayCollection(); 如上声明后,employee和acEmployees变量可作为绑定数据源。 对于使用getter和setter方法定义的属性变量,需要在get或set方法前声明[Bindable]。如代码13-3所示。 代码13-3:getter和setter的属性绑定声明 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="init();"> <mx:Script> <![CDATA[ private var _prop:String; private function init():void{ prop='bind was triggerd'; } [Bindable] private function set prop(value:String ):void{ //自定义代码 _prop=value; } private function get prop():String{ //自定义代码 return _prop; } ]]> </mx:Script> <mx:Text id="txtDest" text="{prop}"/> <mx:Button label="修改" click="prop='bind was triggerd Again!'"/> </mx:Application> 声明可绑定ActionScript类 在公共类定义前,使用[Bindable]声明该类是可绑定的。 声明ActionScript类可绑定意味着告诉Flex,这个类的所有公共属性都是可绑定的。因此,代码13-4、代码13-5具有相同的意义。 代码13-4:可绑定ActionScript类一 package com.longfei.bookLabs.bookStore.model { [Bindable] public class Employee { public var name:String; public var title:String; public function Employee(){ } } } 代码13-5:可绑定ActionScript类二 package com.longfei.bookLabs.bookStore.model { public class Employee { [Bindable] public var name:String; [Bindable] public var title:String; public function Employee(){ } } } 声明可绑定方法 在Flex中,方法也可以作为数据绑定的数据源,前提条件是方法的参数必须声明为可绑定的属性变量。当属性变量发生变化时,方法就会被调用,并把结果传递给目标数据,如代码13-6所示。 代码13-6:可绑定ActionScript方法 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white"> <mx:Script> <![CDATA[ private var strDest:String; private function functionSrc(value:Object):String{ strDest="新数据是: " + value; return strDest; } ]]> </mx:Script> <mx:Form> <mx:FormItem label="数据源:"> <mx:TextInput id="txtSrc" /> </mx:FormItem> <mx:FormItem label="目标数据:"> <mx:TextInput id="txtDest" text="{functionSrc(txtSrc.text)}"/> </mx:FormItem> </mx:Form> </mx:Application> 13.2.2 MXML定义数据绑定 使用MXML,可以通过{}句法或<mx:Binding>标签完成数据绑定(如图13-2所示)。下面两段代码13-7、13-8实现了相同的结果。 代码13-7:使用{}句法进行数据绑定 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white"> <mx:Form> <mx:FormItem label="数据源1:"> <mx:TextInput id="txtSrc1" /> </mx:FormItem> <mx:FormItem label="数据源2:"> <mx:TextInput id="txtSrc2" /> </mx:FormItem> <mx:FormItem label="目标数据:"> <mx:TextInput id="txtDest" text="{'信息来自:' + txtSrc1.text + ' 和 '+ txtSrc2.text}"/> </mx:FormItem> </mx:Form> </mx:Application> 代码13-8:使用<mx:Binding>句法进行数据绑定 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white"> <mx:Form> <mx:FormItem label="数据源1:"> <mx:TextInput id="txtSrc1" /> </mx:FormItem> <mx:FormItem label="数据源2:"> <mx:TextInput id="txtSrc2" /> </mx:FormItem> <mx:FormItem label="目标数据:"> <mx:TextInput id="txtDest"/> </mx:FormItem> </mx:Form> <mx:Binding source="'信息来自:' + txtSrc1.text + '和' + txtSrc2.text" destination="txtDest.text"/> </mx:Application> 图13-2 在MXML中进行数据绑定 在上面两个代码示例中,绑定的数据源都是txtSrc1.text和txtSrc2.text,而绑定目标是txtDest.text。Flex对数据绑定要做的事情就是,当绑定的数据源发生变化时,将新的数据复制到绑定目标上。 使用{}句法,{}内包含的属性就是绑定的数据源,其中也可以包含ActionScript代码结合绑定数据源构成完整的表达式。 使用<mx:Binding>时,source属性是绑定的数据源,而destination则是绑定目标。在<mx:Binding>标签中,也可以在source属性中包含{}。在我们上面的样例代码中没有包含{},那么source属性中的值被视为单一的ActionScript代码表达式,这就像source属性的值被完全包含在{}中一样。 例如,代码13-8中<mx:Binding>标签代码可以改写成如下格式。{}中的代码被视为ActionScript表达式。 <mx:Binding source="{'信息来自:' + txtSrc1.text + '和' + txtSrc2.text }" destination="txtDest.text"/> 这段代码也可以改为如下方式: <mx:Binding source="信息来自:{ txtSrc1.text }和{ txtSrc2.text }}" destination="txtDest.text"/> 在这里,source属性的值被视为串联在一起的代码表达式。 13.2.3 ActionScript定义数据绑定 在ActionScript中,使用mx.binding.utils.BindingUtil类完成数据绑定。BindingUtil提供了两个静态方法分别完成绑定属性(bindProperty方法)和绑定方法(bindSetter方法)。 bindProperty() bindProperty()方法签名 public static function bindProperty(site:Object, prop:String, host:Object, chain:Object, commitOnly:Boolean= false):ChangeWatcher bindProperty把对象host的属性或属性链的值(由chain参数说明)绑定到对象site的公共属性prop上。更准确地说,Flex将监控host对象中由chain参数声明的公共属性或属性链,当这些属性发生变化时,将会复制新值到site对象的prop属性上。 bindSetter()方法 bindSetter()方法签名 public static function bindSetter(setter:Function, host:Object, chain:Object, commitOnly:Boolean= false):ChangeWatcher bindSetter()与bindProperty()方法类似,不同的是把对象host的属性或属性链的值(由chain参数说明)传递给setter方法,由setter方法返回最终结果。setter()方法事实上就与绑定相关的事件侦听器方法。bindSetter()方法提供了更多的空间,开发者能够在绑定发生时进一步的处理数据,而不仅仅是单纯的绑定属性。 ChangeWatcher类 bindProperty()方法和bindSetter()都返回ChangeWatcher类实例。类如其名,mx.binding.utils.ChangeWatcher类负责监控绑定数据源的属性或属性链,当它们发生变化时获取通知,调用事件侦听器。关于ChangeWatcher类的具体说明请参考“ActionScript 3.0语言参考”。 在代码13-9中,应用初始化时调用initBinding()方法绑定了txtSrc.text(源)和txtDest.text(目标),也获取了该绑定的ChangeWaatcher实例cw。cw.setHandler方法修改了绑定事件发生后的侦听器方法,也就是说我们用自己的watcherListner()方法替换了bindProperty()默认的事件处理方法。 watcherListner()从cw.getValue()获取所监控属性或属性链的更新数据,并复制到目标对象(txtDest.text)上。一切都与绑定的默认行为一样。只不过我们埋下了一个圣诞彩蛋,如果txtSrc.text的值为“unbind”的时候,就利用cw.unwatch()解除绑定。 代码13-9:ChangeWatcher类的样例 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="initBinding()"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.binding.utils.*; private var cw:ChangeWatcher; private function initBinding():void{ cw=BindingUtils.bindProperty(txtDest,"text",txtSrc,"text"); cw.setHandler(watcherListner); } private function watcherListner(event:Event):void{ if(txtSrc.text=="unbind"){ txtDest.text=cw.getValue().toString(); cw.unwatch(); }else{ txtDest.text=cw.getValue().toString(); } } ]]> </mx:Script> <mx:Form> <mx:FormItem label="数据源"> <mx:TextInput id="txtSrc"/> </mx:FormItem> <mx:FormItem label="绑定目标"> <mx:TextInput id="txtDest"/> </mx:FormItem> </mx:Form> </mx:Application> 该样例的运行结果如图13-3所示,在“数据源”中输入unbind后,绑定关系就被解除了。 图13-3 ChangWatcher类使用示例 属性链 数据绑定中很重要的一环就是指定监听哪些数据源。Flex不仅可以监控单一的属性,也能够监控属性链。 在下面这个例子中,属性链为book.name,属性链中的任何一环发生变化,都会调度绑定的事件侦听器方法。 BindingUtils.bindProperty(txtDest,"text",,this,["book","name"]); 使用MXML也能够达到同样的目的: <mx:Text id="txtDest" text="{book.name }"/> 13.3 数据绑定样例――图书信息维护 我们通过一个例子,更好地说明如何在应用中使用ActionScript数据绑定。示例完成后的结果如图13-4所示,代码如代码13-10、代码13-11所示。 图13-4 更新图书信息样例 用户输入书名、价格和ISBN号,输入的内容会同时显示在其下方的描述文本上。点击“创建新书”按钮后,会在数据列表中添加图书的信息。 代码13-10:样例中使用的数据模型――Book.as package com.longfei.bookLabs.bookStore.model { [Bindable] public class Book { public var name:String; public var ISBN:String; public var price:Number; public function Book(){ } } } 代码13-11:更新图书信息的样例――主应用bindPOC.mxml <?xml version="1.0" encoding="utf-8"?> <!-- http://blog.flexexamples.com/2007/10/01/data-binding-in-flex/ --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="init();"> <mx:Script> <![CDATA[ import mx.binding.utils.ChangeWatcher; import mx.collections.ArrayCollection; import mx.binding.utils.BindingUtils; import com.longfei.bookLabs.bookStore.model.Book; //Book数据模型的实例 [Bindable] public var book:Book=new Book(); //存储新增的所有图书,作为数据表books的数据源 [Bindable] public var acBooks:ArrayCollection=new ArrayCollection(); private var cwName:ChangeWatcher; private var cwISBN:ChangeWatcher; private var cwPrice:ChangeWatcher; //初始化绑定 private function init():void { cwName =BindingUtils.bindProperty(book, "name", txtName, "text"); cwISBN=BindingUtils.bindProperty(book, "ISBN", txtISBN, "text"); cwPrice=BindingUtils.bindProperty(book, "price", txtPrice, "text"); BindingUtils.bindProperty(txtBPDesc,"text",book,"name"); BindingUtils.bindSetter(updateBook,this,["book","name"]); } //绑定方法 private function updateBook(value:String):void{ txtBSDesc.text="新书的名字是" + value + ",价格为" + book.price; } //新建图书,更新books数据表,创建新的图书实例 private function newBook():void{ acBooks.addItem(book); cwName.unwatch(); cwISBN.unwatch(); cwPrice.unwatch(); book=new Book(); cwName=BindingUtils.bindProperty(book, "name", txtName, "text"); cwISBN=BindingUtils.bindProperty(book, "ISBN", txtISBN, "text"); cwPrice=BindingUtils.bindProperty(book, "price", txtPrice, "text"); } ]]> </mx:Script> <mx:Form> <mx:FormItem label="书名:"> <mx:TextInput id="txtName" /> </mx:FormItem> <mx:FormItem label="价格:"> <mx:TextInput id="txtPrice"/> </mx:FormItem> <mx:FormItem label="ISBN:"> <mx:TextInput id="txtISBN"/> </mx:FormItem> </mx:Form> <mx:Text id="txtBPDesc"/> <mx:Text id="txtBSDesc"/> <mx:Button id="button1" label="创建新书" click="newBook()"/> <mx:DataGrid id="books" dataProvider="{acBooks}"/> </mx:Application> 应用的关键点说明,其中涉及的[Bindable]元数据标签和数据集合ArrayCollection,将在本章后面详细介绍。 图13-5 更新图书示例说明 1. 使用[Bindable]元数据标签,声明ActionScript类Book为可绑定类,这意味着其所有公共属性都为可绑定属性,可以作为数据绑定的数据源。 2. 使用[Bindable]元数据标签,在主应用bindPOC.mxml中,声明了变量book和acBooks为可绑定变量。 3. init()方法分别使用bindProperty()方法绑定数据输入框txtName、txtISBN、txtPrice的text属性到book实例的对应属性上。当用户在输入框输入数据时,就会实时地更新book实例。 4. init()方法使用bindProperty()和bindSetter()方法分别为txtBPDesc和txtBSDesc进行了数据绑定。txtBSDesc的数据绑定是通过updateBook()方法实现的。 BindingUtils.bindProperty(txtBPDesc,"text",book,"name"); BindingUtils.bindSetter(updateBook,this,["book","name"]); 需要进一步说明的是,它们为txtBPDesc文本控件仅仅绑定了name属性(如上第一行代码),而调用updateBook为txtBSDesc文本控件所做的绑定则监控了属性链:book实例和name属性。这意味着,当book实例发生变化时(用户点击“创建新书”按钮后,就会创建新的book实例),txtBPDesc会失去对新的实例的监控,其显示不会进行更新。而txtBSDesc则仍然会监控新的book实例,并相应得更新数据。在本例的图13-4中,也可以看到txtBPDesc文本控件并没有显示新的书名。 5. 数据表格控件books的dataProvider属性与acBooks数据集合绑定。 6. 用户点击“创建新书”按钮后,book实例被加入到数据集合acBooks中,其结果自动地更新到DataGrid控件books上。在创建新的book实例前(book=new Book()),须要解除当前book实例与文本输入框的绑定,之后为新的book实例重新设定绑定关系。读者可以尝试注释newBook()方法中的unWatch代码行及bindProperty代码,重新运行应用,点击“创建新书”按钮后,在文本框中输入书名等信息,此时会发现文本输入框绑定的仍然是原有的book实例(该实例已被存入acBooks,显示为数据列表的第一行),同步更新的是数据列表的第一行。而当再次点击按钮,添加第二本新书时,新的book实例并没有获取文本框输入的信息,由此添加进来的是空的book实例。图13-6显示了修改代码后,应用运行的结果。 图13-6 错误的代码运行结果 13.4 关键的数据集合类 Flex应用处理数据离不开数据集合类。Flex赋予了数据集合类非凡的能力和重大的责任。许多Flex组件都是数据驱动的组件,例如DataGrid、ButtonBar、ComboBox,无一例外的依赖数据集合类提供数据。常用的数据集合类包括ArrayCollection类和XMLListCollection类。本节将介绍数据集合类的共同特点,以及如何使用ArrayCollection类。 13.4.1 数据集合类介绍 数据集合类通常用作Flex数据驱动组件和原始数据对象之间的数据处理层,通过实现IList和ICollectionView接口提供了访问和操纵数据的统一方法。数据集合类依赖数组和XMLList对象提供原始数据,但在对其包装之后,其具有了更丰富的数据处理能力。 ArrayCollection和XMLListCollection类都继承自mx.collections.ListCollection- View。ListCollectionView实现了IList和ICollectionView接口。从IList和ICollectionView接口,我们能够看到数据集合类支持哪些特别的数据处理能力。 mx.collection.IList接口 与ICollectionView接口相比,IList相对简单。IList接口代表了按顺序组织的项目的集合,实现该接口的类提供了对集合项目元素基于索引的访问和处理方法。 IList接口的方法很好地展现了实现该接口类的能力,见表13-1。 表13-1 mx.collection.IList接口的方法(引自ActionScript3.0 Language Reference) 方法签名描述addItem(item:Object):void向列表末尾添加指定项目addItemAt(item:Object, index:int):void在指定的索引处添加项目getItemAt(index:int, prefetch:int= 0):Object获取指定索引处的项目getItemIndex(item:Object):int如果项目位于列表中(此时getItemAt(index) == item),则返回该项目的索引itemUpdated(item:Object, property:Object= null,oldValue:Object= null, newValue:Object= null):void通知视图,某个项目已更新removeAll():void删除列表中的所有项目 续表 方法签名描述removeItemAt(index:int):Object删除指定索引处的项目并返回该项目setItemAt(item:Object, index:int):Object在指定的索引处放置项目toArray():Array返回与 IList 实现的填充顺序相同的Array 实现IList接口的类能够对集合中的项目元素进行添加、删除和获取操作,这些操作会直接作用到原始数据上。IList没有提供数据排序、筛选及游标操作等高级功能。 mx.collection.ICollectionView接口 ICollectionView是数据集合的视图,可以修改该视图以显示根据各种条件排序的数据,或显示在不修改基本数据的情况下经过筛选后的数据。实现ICollectionView接口的类能够对数据集合进行排序和过滤等操作,这些操作不会改变原始数据。 实现ICollectionView接口的类通过属性接口,允许开发者定义过滤方法和排序类操纵数据集合,见表13-2。 表13-2 mx.collection.ICollectionView接口的公共属性(引自ActionScript3.0 Language Reference) 属性描述filterFunction:Function视图用来消除不符合函数条件的项目的函数length:int此视图中的项目数 sort:Sort将应用于 ICollectionView 的排序 实现ICollectionView接口的类也提供了创建游标的方法,游标提供了对数据集合进行查找、搜索和标记书签的功能,还提供修改方法实现插入和删除。这些操作也会作用到原始数据上,并即时反映到由该集合类驱动的Flex可视化组件上,见表13-3。 表13-3 mx.collection.ICollectionView接口的公共方法(引自ActionScript3.0 Language Reference) 方法签名描述contains(item:Object):Boolean返回指示视图是否包含指定对象的信息createCursor():IViewCursor创建使用此视图的新 IViewCursordisableAutoUpdate():void防止视图调度对集合本身和集合中的项目的更改enableAutoUpdate():void启用自动更新 续表 方法签名描述itemUpdated(item:Object, property:Object = null,oldValue:Object = null, newValue:Object = null):void通知视图,某个项目已更新refresh():Boolean将排序和滤镜应用到视图 13.4.2 集合类在数据绑定中的应用 Flex时刻监控集合类数据的变化,它能够及时更新这些集合类数据驱动的组件。当对数组类型数据源进行数据绑定时,要使用集合类对象,而不是Array或XMLList类型对象。当通过IList和ICollectionView接口方法操作集合类对象(例如ListCollectionView 、ArrayCollection或XMLListCollection)时,数据的更新会即时地调度PropertyChanged事件,触发数据绑定,如代码13-12所示。 代码13-12:数组类型数据绑定 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.collections.*; [Bindable] private var arrBooks:Array = new Array("ActionScrip基础", "追风筝的 人", "神秘河"); [Bindable] private var acBooks:ArrayCollection=new ArrayCollection(arrBooks); private var myCursor:IViewCursor; private function updateData():void{ arrBooks[2]="数字城堡"; //在数组对象上的更新,无法即时触发数据绑定, 更新List组件 } private function removeData():void{ arrList.dataProvider.removeItemAt(0); //删除了"神秘河" acBooks.removeItemAt(0); //删除了"追风筝的人" } ]]> </mx:Script> <mx:HBox> <mx:List id="arrList" dataProvider="{arrBooks}"/> <mx:List id="acList" dataProvider="{acBooks}"/> </mx:HBox> <mx:Label text="arrBooks长度={arrList.dataProvider.length}, acBooks长度 ={acBooks.length}"/> <mx:HBox> <mx:Button id="button1" label="更新列表" click="updateData()"/> <mx:Button id="button2" label="删除数据" click="removeData()"/> </mx:HBox></mx:Application> 在上面例子中,第一个List组件arrList使用了数组arrBooks作为绑定数据源。此时,Flex会自动将arrBooks数组包装为ArrayCollction类。第二个List组件acList使用了ArrayCollection对象acBooks作为绑定数据源,其包装了数组arrBooks。 “更新列表”按钮调用了updateData()方法,该方法直接更新了arrBooks数组的第三个元素。但更新完毕后,List组件并没有发生变化。也就是说数据绑定没有触发。 图13-7显示了点击“更新列表”按钮后的显示。 图13-7 更新列表后的显示 此时,点击“删除数据”,调用removeData()方法。该方法分别调用了arrList.dataProvider和acBooks的removeItemAt(0)方法。arrList.dataProvider代表了封装arrBooks的ArrayCollection类对象。此时,两行代码分别删除了数组当前的第一个元素,并触发了数据绑定。updateData()方法中对数组的更新也同时显示在List组件上,删除数据后的显示如图13-8所示。 图13-8 删除数据后的显示 13.5 理解ArrayCollection类 上一节,我们介绍了集合类提供了丰富的数据操纵能力,本节,我们将通过ArrayCollection类来具体了解如何应用集合类。 13.5.1 创建ArrayCollection类实例 mx.collection.ArrayCollection类是将Array公开为集合的封装类,可使用ICollectionView或IList接口的方法和属性实现访问和处理ArrayCollection实例。对ArrayCollection实例进行操作会修改数据源,例如,如果对ArrayCollection使用removeItemAt()方法,就会删除基础Array中的项目。 ArrayCollection的MXML语法如下: <mx:ArrayCollection id=”” source="null"/> 下面两段代码分别使用MXML标签和ActionScript代码声明并创建了ArrayCollection实例。 使用MXML代码创建ArrayCollection实例。 <mx:ArrayCollection id="arrColl"> <mx:source> <mx:Array> <mx:Object label=" ActionScript基础" data=" A-1" /> <mx:Object label="追风筝的人" data="B-1" /> <mx:Object label="神秘河" data="C-1" /> </mx:Array> </mx:source> </mx:ArrayCollection> 使用ActionScript代码创建ArrayCollection实例。 private var books:Array = new Array({label:"ActionScript基础", data:"A-1"},{label:"追风筝的人", data:"B-1"},{label:"神秘河", data:"C-1"}); private var acBooks1:ArrayCollection=new ArrayCollection(books); Flex应用的数据通常来自外部文件。或者是SWF应用所在服务器的本地资源文件,或者通过HTTP、Web服务等其他方式获取的远端服务器数据。 代码13-13是一个典型的初始化ArrayCollection实例的XML数据源。数据呈现规则的结构,每一个子节点都具有相同的属性,代表了数据集合中的实例。 代码13-13:xml文件样例――book.xml(位于assets目录下) <?xml version="1.0" encoding="utf-8"?> <books> <book> <ISBN>9787806239421</ISBN> <name>大秦帝国</name> <price>369.00</price> <author>孙皓晖</author> <publication>河南文艺出版社</publication> </book> …… <book> <ISBN>9787115145543</ISBN> <name>C++ Primer中文版</name> <price>99.00</price> <author>Stanley</author> <publication>人民邮电出版社</publication> </book> </books> 有很多种方法可以从服务器端获取数据,包括HTTPService、Web服务、远程对象等,我们将在第18章“访问Web和HTTP服务”(见第421页)做具体介绍。下面的例子中,通过简单的HTTP服务(id为service)读取了服务器本地的XML数据文件。数据读取完毕后,会调度result事件,触发handleData事件侦听方法,把读取的数据置入ArrayCollection实例acBooks中(如代码13-14所示)。由于该变量被绑定在数据表grid上,因此即时更新了grid的显示。 代码13-14:从本地xml文件初始化ArrayCollection实例 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="init()"> <mx:Script> <![CDATA[ import mx.rpc.events.ResultEvent; import mx.collections.ArrayCollection; [Bindable] private var acBooks:ArrayCollection=new ArrayCollection(); //初始化数据 private function init():void{ service.send(); } //从本地XML数据文件中读取数据到acBooks:ArrayCollection中 private function handleData(evt:ResultEvent):void{ acBooks=evt.result.books.book; } ]]> </mx:Script> <mx:HTTPService id="service" url="assets/book.xml" result="handleData(event)"/> <!--绑定acBooks数据源到表格组件--> <mx:DataGrid id="grid" dataProvider="{acBooks}"/> </mx:Application> 上述代码最终把XML数据文件转换为ArrayCollection实例。在调试模式下运行上述应用,并监控acBooks变量,可以更加清楚地看到XML数据文件如何存储在ArrayCollection实例中,如图13-9所示。 图13-9 Debug模式下监控ArrayCollection实例acBooks XML数据文件的每个节点被作为独立的对象实例(mx.utils.ObjectProxy对象)依次存储在数据集合中。 13.5.2 筛选数据 ArrayCollection类自ListCollectionView继承了filterFunction属性。 filterFunction:Function[] 通过设置filterFunction属性,为ArrayCollection实例指定筛选数据的方法。该方法必须具有如下形式签名: f(item:Object):Boolean 在设置filterFunction属性后,必须调用refresh()方法,将筛选函数应用到数据集合上,更新集合视图。 以上一节应用为基础。我们在数据列表grid上增加滑动条组件,用户通过该组件设置价格范围达到筛选图书的目的,如代码13-15所示。应用完成后结果如图13-10所示: 图13-10 ArrayCollection筛选数据示例 代码13-15:筛选数据示例 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="init()"> <mx:Script> <![CDATA[ import mx.events.SliderEvent; import mx.controls.Alert; import mx.rpc.events.ResultEvent; import mx.collections.ArrayCollection; [Bindable] private var acBooks:ArrayCollection=new ArrayCollection(); //初始化数据 private function init():void{ service.send(); } //从本地XML数据文件中读取数据到acBooks:ArrayCollection中 private function handleData(evt:ResultEvent):void{ acBooks=evt.result.books.book; //设置acBooks的过滤函数 acBooks.filterFunction=priceFilter; } //acBooks的价格过滤器 private function priceFilter(item:Object):Boolean{ var isMatch:Boolean = false; if(item.price>=priceSlider.values[0] && item.price<= priceSlider.values[1]){ isMatch = true; } return isMatch; } //价格滑动条变化后触发,应用过滤方法,更新列表 private function dispatchPriceFilter():void { acBooks.refresh(); } ]]> </mx:Script> <mx:HTTPService id="service" url="assets/book.xml" result="handleData(event)"/> <!--控制价格筛选--> <mx:HSlider id="priceSlider" minimum="0" maximum="500" tickInterval="5" snapInterval="5" width="{grid.width}" thumbCount="2" values="[0,500]" labels="[$0,$500]" liveDragging="true" change="dispatchPriceFilter()"/> <!--绑定acBooks数据源到表格组件--> <mx:DataGrid id="grid" dataProvider="{acBooks}"/> </mx:Application> HSlider组件说明 mx.controls.HSlider类实现了水平滑动条控件。用户可通过在滑块轨道的终点之间移动滑块来选择值。 maximum和minimum属性分别指定了滑块控件允许的最大和最小值。thumCount指定了Slider 控件所允许的滑块数量。在我们的例子中,使用两个滑块来设定价格范围。此时,每个滑块的值在一起构成了数组,通过属性values可以访问和设定该数组值。默认设置values="[0,500]",使得应用初始化时滑块位于轨道的两端。属性liveDragging设置为true,为滑块启动了实时拖动,此时当用户移动滑块时连续调度change事件。可以看到,我们为change事件的侦听器方法定义为dispatchPriceFilter()。 设置ArrayCollection实例acBooks的过滤器属性 在handleData()方法中,acBooks.filterFunction=priceFilter设定了过滤器属性为priceFilter方法。 过滤器方法:priceFilter(item:Object)说明 过滤器方法通过参数item:Object访问ArrayCollection的每个元素。如果价格范围在滑动条设置值范围内,则返回true,该元素会保留在集合视图中。 dispatchPriceFilter()方法说明 每一次滑动滚动条,都会由于触发change事件,而调用dispatchPriceFilter()方法。在该方法中,调用refresh方法以应用过滤。由于acBooks被绑定在grid数据表上,因此过滤结果会即时反应在应用界面上。 13.5.3 数据排序 ArrayCollection自ListCollectionView继承了sort属性。 sort:Sort[] sort属性用于对 ICollectionView视图进行排序。同数据筛选一样,设置排序不会自动刷新视图,因此必须在设置此属性后调用refresh()方法。开发者须要创建mx.collection.Sort类实例赋予ArrayCollection的sort属性。 mx.collection.Sort类 Sort类提供了在现有视图上建立排序所需的排序信息,其实例通常被赋予ICollectionView视图的sort属性(例如ArrayCollection的sort属性),实现对集合数据视图的排序。 Sort类的常用属性包括: fields:Array //指定要比较的字段的 SortField 对象,数组类型。 以及 compareFunction:Function//用于在排序时比较项目的方法。 mx.collection..SortField SortField提供对现有视图中的字段或属性建立排序所需的排序信息。mx.collection.Sort类中,数组类型的fields属性中的数组项目即为SortField类实例。 我们为上一节ArrayCollection数据筛选的例子加入排序功能。 1. 在主应用上加入新的按钮,用户点击该按钮则触发“SortData()”方法,进行排序。 <mx:Button label="排序" id="buttonSort" click="sortData()"/> 2. 引入mx.collection.Sort和SortField类。 在<mx:Script>标签中,加入 import mx.collections.Sort; import mx.collections.SortField; 3. 实现SortData()方法。 在<mx:Script>标签中,加入新的方法: private function sortData():void{ var sortBooks:Sort = new Sort(); sortBooks.fields=[new SortField("price"), new SortField("ISBN")]; acBooks.sort=sortBooks; acBooks.refresh(); } 在上述代码中,首先创建了Sort类实例。然后分别创建了两个SortField实例(对应集合对象元素的price和ISBN属性),并赋予了Sort实例的fileds属性。在将Sort类实例赋予集合对象acBooks的sort属性之后,通过调用refresh方法刷新了视图,并即时更新了数据表。 按下按钮后的数据表显示如图13-11所示。 图13-11 数据集合ArrayCollection的排序结果 13.5.4 使用游标 ArrayCollection有些类似数据表。数据集合中的每一个元素(通常为复杂对象,也可以是String、int等基本类型)就好比数据表中的一条记录。数据库技术中,通过游标可以在数据表的记录中游历。有的时候,在数据集合中也须要实现类似功能。ListCollectionView提供了createCursor()方法,可以创建能够在数据集合中游历的游标。ArrayCollection继承了该方法。 createCursor()方法 createCursor方法签名: public function createCursor():IViewCursor 该方法创建了基于此视图的新 IViewCursor实现。IViewCursor是定义双向枚举集合视图的接口。此游标提供查找、搜索和标记为书签等功能,还提供修改方法用于插入和删除。 IViewCursor接口的方法和属性,见表13-4、表13-5。 表13-4 mx.collection.IViewCursor接口的公共方法(引自ActionScript3.0 Language Reference) 方法签名描述findAny(values:Object):Boolean查找集合中具有指定属性的项目并将光标定位到该项目findFirst(values:Object):Boolean查找集合中具有指定属性的第一个项目,并将光标定位到该项目findLast(values:Object):Boolean查找集合中具有指定属性的最后一个项目,并将光标定位到该项目insert(item:Object):void在光标的当前位置之前插入指定的项目moveNext():Boolean将光标移动到集合中的下一个项目movePrevious():Boolean将光标移动到集合中的上一个项目remove():Object删除当前项目并返回该项目seek(bookmark:CursorBookmark,offset:int= 0, prefetch:int= 0):void将光标移动到与指定书签位置存在一定偏移量的某个位置 表13-5 mx.collection.IViewCursor接口的属性(引自ActionScript3.0 Language Reference) 方法签名描述afterLast:Boolean如果将光标定位于视图中最后一个项目之后,则此属性为 truebeforeFirst:Boolean如果将光标定位于视图中第一个项目之前,此属性为 truebookmark:CursorBookmark可以访问与当前属性返回的项目相对应的书签current:Object可以访问位于此光标引用的源集合中的位置的对象view:ICollectionView对与此光标关联的 ICollectionView 的引用 游标查找的局限性 IViewCursor实现提供了查找方法(findAny、findFirst和findAny)。通过游标查找数据集合中的元素对象非常方便,但由于这些方法依赖于Sort类(使用了其compareFunction属性),由此带来了许多限制: 1. 只能对排序后的视图调用查找方法;如果视图未排序,则会引发 CursorError。 2. 指定的查询值必须配置为名称-值对。这些名称值必须与排序中指定的属性相匹配。这意味着,一方面查询使用的名称-值对必须包含在排序使用的名称值对(Sort类的fields属性制定)范围内;另一方面,查询指定的集合元素属性顺序必须与排序指定的属性顺序相同。例如:如果mySort.fields属性指定的排序属性次序分别为price、bookName和ISBN,那么游标查询指定的名称值对需为{price:value,bookName:value;ISBN:value}。查询指定的数组集合元素对象的属性不一定覆盖所有的排序属性,但必须保证顺序一致。例如{price:value,bookName:value}是可以接受的查询方法参数。但是{price:value, ISBN:value}则会导致游标查询方法抛出CursorError异常。 我们通过为上一个应用加入查找功能,了解如何实际应用游标进行查找,如代码13-16、图13-12所示。 代码13-16:游标查找的样例 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" backgroundColor="white" creationComplete="init()"> <mx:Script> <![CDATA[ import mx.collections.SortField; import mx.events.SliderEvent; import mx.controls.Alert; import mx.rpc.events.ResultEvent; import mx.collections.ArrayCollection; import mx.collections.Sort; import mx.collections.SortField; import mx.collections.IViewCursor; [Bindable] private var acBooks:ArrayCollection=new ArrayCollection(); private var cursor:IViewCursor; //初始化数据 private function init():void{ service.send(); } //从本地XML数据文件中读取数据到acBooks:ArrayCollection中 private function handleData(evt:ResultEvent):void{ acBooks=evt.result.books.book; //创建游标 cursor = acBooks.createCursor(); } //价格滑动条变化后触发,设置acBooks的过滤函数,并更新列表 private function dispatchPriceFilter():void { acBooks.refresh(); } //查询方法 private function searchItem(event:Event):void{ //查询之前进行排序 var sortBooks:Sort = new Sort(); //排序使用的属性和其顺序,将决定查询时的查询属性及顺序 sortBooks.fields=[new SortField("price"),new SortField("bookName"),new SortField("ISBN")]; acBooks.sort=sortBooks; acBooks.refresh(); //创建查询属性对象 var searchObj:Object = new Object(); //查询属性对象必须在排序使用的属性对象范围内,并保持一致顺序 searchObj = {price:priceKey.text,bookName:nameKey.text}; //游标查询 var result:Boolean = cursor.findAny(searchObj); if (result) { //定位数据表中记录 grid.selectedItem=cursor.current; } else { grid.selectedItem = null; mx.controls.Alert.show("抱歉,没有找到!"); } } ]]> </mx:Script> <mx:HTTPService id="service" url="assets/book.xml" result= "handleData(event)"/> <!--通过ApplicationControlBar组织工具条--> <mx:ApplicationControlBar width="{grid.width}"> <mx:Spacer width="100%"/> <!--图书名称查询输入框--> <mx:TextInput id="nameKey" toolTip="图书名称" /> <!--图书价格查询输入框--> <mx:TextInput id="priceKey" toolTip="价格" /> <mx:Button id="buttonSearch" label="查找" click="searchItem(event);" /> </mx:ApplicationControlBar> <!--绑定acBooks数据源到表格组件--> <mx:DataGrid id="grid" dataProvider="{acBooks}"/> </mx:Application> 图13-12 ArrayCollection游标查找样例应用
Adobe Flex 大师之路——第13章 数据基础
书名: Adobe Flex 大师之路
作者: 董龙飞 | 肖娜
出版社: 电子工业出版社
出版年: 2009年
页数: 558
定价: 69.80元
装帧: 平装
ISBN: 9787121085918