【IT168技术分析】Flex应用程序可能有复杂的结构但它们通常都是由多个MXML,Actionscript 以及CSS文件组成。
在单个文件里编写整个程序不是一个非常好的实践。那样会使代码难以维护和重用,而且程序没有被架构成逻辑上的各个部分。
Flex允许开发者将一个工程分成外部的模块,创建分离的MXML文件并且单独维护它们。将Flex程序分离成各个逻辑模块有很多好处。它允许开发小组独立地开发和调试单个模块,模块中的错误和功能可以被独立出来。这使得代码变得容易维护,也提高了代码在多个应用程序间的重用性。
每个MXML文件是一个MXML组件,但是只有主MXML程序可以加载外部组件。事实上一个应用程序中只能有一个 Application 标签,其它所有都是MXML组件。
为了设计一个MXML组件,需要创建一个MXML文件。这个文件的根标签不是Application而是一个组件标签(比如VBox,Panel,Canvas,Button,DataGrid等等),而且根标签中需要声明命名空间http://www.adobe.com/2006/mxml 。
下面的代码展示了一个简单的叫做custDataGrid.mxml 的MXML组件,它由一个用来存储数据的DataGrid组成。
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:DataGrid id="myDG" >
<mx:columns>
<mx:DataGridColumn headerText="Posts" dataField="title" />
<mx:DataGridColumn headerText="Date" dataField="pubDate" width="100" />
</mx:columns>
</mx:DataGrid>
</mx:VBox>
根标签是一个简单的VBox组件并且其中声明了xmlns:mx="http://www.adobe.com/2006/mxml"。一旦创建了外部模块,主程序就可以像下面一样调用这个MXML组件:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:cust="comp.*" >
<mx:Panel title="Comtaste's Blog Reader">
<cust: custDataGrid width="80%" />
</mx:Panel>
</mx:Application>
主程序添加了一个新的XML命名空间并使用一个包名来表示在一个文件夹中定义的所有组件:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:cust="comp.*" >
“cust”命名空间指向”comp”文件夹中的MXML组件,实际上如果是非常好的实践所有的组件都应当被保存在一个子目录下。
使用组件和使用其它MXML标签一样,唯一不同的是不再使用”mx”前缀而是使用自定义前缀:
<cust: custDataGrid width="80%" />
注意MXML标签的名字对应着这个组件的文件名。
紧耦合组件与松耦合组件
为了使组件在程序中可以配置和重用,你可能会想要创建可以接受属性,创建方法以及分派事件的MXML组件。为了做到这样,组件必须不依赖于特定的程序(紧耦合),包括它们的变量名或标签实例的名字。紧耦合情况下如果程序或者组件的代码改变了,相应的代码就不能再工作,我们必须修改紧耦合的组件来相应这些变化。
一种更清晰也更好的方式是开发松耦合的组件,它可以分派传播返回数据给程序的事件并且包含一些属性使得可以从程序传送信息给它。
这种方式可以让你像创建“黑盒子”一样创建MXML组件,并且有以下好处:
•它们容易重用和维护
•除了内部的组件它们不了解其它的东西
•它们不依赖于一个变量名和标签实例的名字
如我们所说的那样,为了创建一个松耦合的组件我们需要属性来存储信息,需要一个事件模型来分派包含有返回数据的事件。
属性经常在ActionScript中定义(尽管你可以在MXML中定义它们)并且放在一个<mx:Script>块中:
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var lista:ArrayCollection;
]]>
</mx:Script>
定义MXML组件的方法也是一样:
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var lista:ArrayCollection;
public function justWrite():String
{
return "This is a method of the component";
}
]]>
</mx:Script>
在下面的例子中,组件定义了一个作为DataGrid的dataProvider的属性和一个返回字符串的方法。下面是custDataGrid.mxml 文件的全部代码:
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var lista:ArrayCollection;
public function justWrite():String
{
return "This is a method of the component";
}
]]>
</mx:Script>
<mx:DataGrid id="myDG" dataProvider="{lista}" >
</mx:DataGrid>
</mx:VBox>
Flex 2文档中的声明:在<mx:Script>标签中声明的公有变量或set函数会是这个组件的一个属性。<mx:Script>标签中的公有函数会是组件的一个方法。
我们可以在主程序文件中访问组件的方法或者传值给组件的属性:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:cust="*">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var myData:ArrayCollection = new ArrayCollection (
[{A:2000},{A:3000},{A:4000},{A:4000},{A:3000},{A:2000},{A:6000}]);
]]>
</mx:Script>
<cust:custDataGrid id="custDG" x="258" y="89" lista="{myData}"/>
<mx:Label text="{custDG.justWrite()}" x="300" y="44"/>
</mx:Application>
在主程序中我们创建了一个叫做”myData”的ArrayCollection变量,我们可以通过下面的方法在MXML组件标签声明中将它传递给组件:
我们用来展示自定义组件中定义的属性的Flex数据绑定机制可以利用数据绑定特性。”lista”变量是custDataGrid组定义组件的一个公有变量,所以我们将它写在custDataGrid标签声明中。
类似地,也可以创建一个数据绑定调用自定义组件中的一个方法:
Label控件的text属性将接收自定义组件custDataGrid的公有方法返回的文本:
{
return "This is a method of the component";
}
设计一个松耦合组件的最后一步是处理包含返回数据的事件的分派。每个自定义MXML组件都可分派事件,事件可以通过三个简单的步骤自定义:
•使用[Event]元数据标签
•创建一个事件对象
•分派事件并创建一个函数来处理该事件
[Event]元数据标签定义了组件可以分派的事件。可以在一个ActionScript类中包定义之后类定义之前声明[Event]元数据标签:
{
[Event(name=" changeBlog", type=" flash.events.Event ")]
public class custComp
{
}
}
或者在MXML文件中的<mx:Metadata>标签中:
[Event(name="changeBlog", type=" flash.events.Event ")]
</mx:Metadata>
一旦[Event]元数据标签创建,组件就可以使用dispatchEvent()方法来分派该事件。dispatchEvent()方法将事件对象作为参数,如下面的代码中所示:
我们来修改一下custDataGrid.mxml文件,使其可以在用户点击DataGrid中一项的时候分派一个事件。主程序将处理自定义组件触发的事件并在一个TextArea控件中添加一些文字。
使用Flex Builder 2打开custDataGrid.mxml 并添加[Event]元数据标签:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Metadata>
[Event(name="changeBlog", type="flash.events.Event")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var lista:ArrayCollection;
public function justWrite():String
{
return "This is a method of the component";
}
private function changeHandler():void
{
dispatchEvent(new Event("changeBlog"));
}
]]>
</mx:Script>
<mx:DataGrid id="myDG" dataProvider="{lista}" change="changeHandler()" >
</mx:DataGrid>
</mx:VBox>
[Event]元数据标签定义了一个普通Event类型的"changeBlog"事件并且将这个事件设为公有的,这样MXML编译器就会发现它。
[Event(name="changeBlog", type="flash.events.Event")]
</mx:Metadata>
当DataGrid的change事件被触发时这个事件就会被分派:
正如你看到的那样,changeHandler"函数简单地执行了dispatchEvent()方法:
{
dispatchEvent(new Event("changeBlog"));
}
主程序通过使用<mx:Script>块中定义的事件处理函数来处理自定义MXML组件所触发的事件:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:cust="*">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var myData:ArrayCollection = new ArrayCollection (
[{A:2000},{A:3000},{A:4000},{A:4000},{A:3000},{A:2000},{A:6000}]);
private function changeBlogHandler (event:Event):void
{
myLabel2.text += "Event fired by Datagrid:"+ event.type + "\n" ;
}
]]>
</mx:Script>
<cust:custDataGrid id="custDG" x="258" y="89" lista="{myData}" changeBlog="changeBlogHandler(event)"/>
<mx:Label text="{custDG.justWrite()}" id="myLabel" x="283" y="63"/>
<mx:TextArea id="myLabel2" x="205" y="239" height="102" width="273"/>
</mx:Application>
custDataGrid组件触发"changeBlog"事件并将它与一个事件处理函数相关联:
<cust:custDataGrid id="custDG" x="258" y="89" lista="{myData}" changeBlog="changeBlogHandler(event)"/>
changeBlogHandler函数将事件对象作为一个参数并在TextAream控件中打印了一个简单的文本:
private function changeBlogHandler (event:Event):void
{
myLabel2.text += "Event fired by Datagrid:"+ event.type + "\n" ;
}
注意使用事件对象的 event.type 会返回事件触发者的类型 (这个例子中会返回changeblog )。
注意:Flex Builder 2会自动找到在组件中自定义事件的引用并使用显示代码提示显示它:
[fig. flexbuilder_customEvent.png] Flex Builder 2 identifies the event name
尽管我们可以看到为松耦合组件创建一个自定义事件是多么简单,但是如果我们想要发送数据给这个自定义事件,这种方式也会有一些限制。实际上flash.events.Event类不允许开发者向其添加属性。
实际上我们分派事件只是为了通知在自定义MXML组件中有一些事情发生了。但是如果我们需要发送复杂的数据给一个自定义事件的时候要怎么办呢?
不用担心。我们可以通过在ActionScript中创建自定义事件类来实现!我将会通过一个例子向你展示如何开发自定义事件类。
在ActionScript中创建自定义事件类来发送复杂数据
使用Flex可以创建一个使用自定义类型的事件对象的自定义事件。只需要创建一个扩展自Event类的子类然后向其中添加属性。不要忘了扩展自既有Flex类的自定义组件会继承基类的所有事件。
要创建一个自定义类我们需要创建一个扩展自flash.events.Event的ActionScript类,这个类将作为所有事件对象的基类。接下来,为了调用父类的构造方法并将事件类型传给它,我们要调用super()方法。我们还需要覆写 "clone()" 方法,通过设定type属性和其它一些属性来返回事件对象的一个拷贝。
为了从自定义组件中分派一个新的事件,我们创建了这个自定义类的一个实例并传送属性值给它。
现在我会用一个例子来说明这种方式。我们创建了一个简单的Blog阅读器,它允许用户使用下拉列表框从Blog列表中选择一个。这个程序由2个MXML组件组成,这两个组件是一个用来显示日志内容的DataGrid以及一个含有Blog列表下拉列表框。
我们将从一个自定义事件类(它被保存为"evtClass.as",并放在了”com”文件夹里)开始,它接受一个简单字符串属性:
package com
{
import flash.events.Event;
public class evtClass extends Event
{
public var evProp:String;
public function evtClass(evParam:String,type:String)
{
super(type);
this.evProp = evParam;
}
override public function clone():Event
{
return new evtClass(evProp,type);
}
}
}
象我们之前说的那样,这个类扩展自flash.events.Event类,调用了super()方法并且覆写了clone()方法。
现在,在Flex Builder 2中创建一个组件(File > New > MXML Component)并保存为custBlogList.mxml。这个组件包含有一个下拉列表框(ComboBox)并且它的dataProvider是一个ArrayCollection对象。
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Metadata>
[Event(name="changeBlog", type="com.evtClass")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import com.evtClass;
private function init():void
{
myArray.addItemAt({label:"Alistair McLeod", data:"http://weblogs.macromedia.com/amcleod/index.xml"}, 0);
}
private function changeHandler():void
{
var eventObj:evtClass = new evtClass(myCombo.value as String,"changeBlog");
dispatchEvent(eventObj);
}
]]>
</mx:Script>
<mx:ComboBox id="myCombo" change="changeHandler()" creationComplete="init();myCombo.selectedIndex=0" >
<mx:ArrayCollection id="myArray">
<mx:Object label="Mike Chambers" data="http://weblogs.macromedia.com/mesh/index.xml"/>
<mx:Object label="Matt Chotin" data="http://weblogs.macromedia.com/mchotin/index.xml"/>
</mx:ArrayCollection>
</mx:ComboBox>
</mx:VBox>
我们使用了[Event]元数据标签定义了这个MXML组件可以分派的事件。赋给type属性的值就是我们的自定义事件类:
[Event(name="changeBlog", type="com.evtClass")]
</mx:Metadata>
一旦用户在下拉列表框中选择了一个Blog,change事件就会被触发并且changeHandler()会被调用:
<mx:ComboBox id="myCombo" change="changeHandler()" creationComplete="init();myCombo.selectedIndex=0" >
这个事件处理器只有一个作用,就是创建一个自定义事件类的实例并将用户所选择的值传送给这个实例(将myCombo中的值作为一个字符串):
{
var eventObj:evtClass = new evtClass(myCombo.value as String,"changeBlog");
dispatchEvent(eventObj);
}
第二个MXML组件包含了DataGrid控件并且从主程序中接收dataProvider。现在你需要将这个文件保存为custBlogData.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import flash.net.*;
[Bindable]
public var lista:ArrayCollection;
]]>
</mx:Script>
<mx:DataGrid id="myDG" horizontalCenter="14" verticalCenter="18.5" dataProvider="{lista}" width="100%"
change="navigateToURL(new URLRequest(myDG.selectedItem.link),'_blank');" >
<mx:columns>
<mx:DataGridColumn headerText="Posts" dataField="title" />
<mx:DataGridColumn headerText="Date" dataField="pubDate" width="100" />
</mx:columns>
</mx:DataGrid>
</mx:VBox>
这个DataGrid定义了两列。第一列从用户选择的RSS feed(在custBlogList中选择的)中获取数据。第二列从类型为ArrayCollection 的变量"lista" 获取数据。
接下来,就是主程序文件了(BlogReader.mxml),它会调用上面的两个组件,调用一个HTTPService来获取rss数据并且定义了一个事件处理函数:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:cust="*"
creationComplete="hs.send()" >
<mx:HTTPService
id="hs"
url="{selectedMenu}"
useProxy="false"/>
<mx:Script>
<![CDATA[
import com.evtClass;
[Bindable]
private var selectedMenu:String= "http://weblogs.macromedia.com/amcleod/index.xml";
private function eventFired(event:evtClass):void
{
selectedMenu = event.evProp;
if (selectedMenu == "null")
{
mx.controls.Alert.show("Please Choose a valid Blog");
return;
};
hs.send();
}
]]>
</mx:Script>
<mx:Panel width="70%" height="70%" layout="absolute" title="Comtaste's Blog Reader" horizontalCenter="0"
verticalCenter="-14.5">
<mx:Label text="{hs.lastResult.rss.channel.title}'s Blog" id="myLbl" x="48.5" y="43" fontWeight="bold"/>
<cust:custBlogList x="161" y="10" changeBlog="eventFired(event)" />
<mx:Label x="48.5" y="10" text="Select a Blog" height="25"/>
<cust:custBlogData width="80%" x="48.5" y="69" lista="{hs.lastResult.rss.channel.item}" />
<mx:ControlBar>
<mx:Label text="Developed by Marco Casario" />
<mx:LinkButton label="http://casario.blogs.com" click="navigateToURL(new
URLRequest('http://casario.blogs.com'),'_blank');"/>
</mx:ControlBar>
</mx:Panel>
</mx:Application>
HTTPService标签对它的url属性进行了数据绑定。当custBlogList中的changeBlog事件触发时{selectedMenu}的值就会被确定:
<mx:HTTPService
id="hs"
url="{selectedMenu}"
useProxy="false"/>
<cust:custBlogList x="161" y="10" changeBlog="eventFired(event)" />
eventFired()是一个事件处理器,它使用事件对象来获取在actionscript(evtClass.as)类中定义的evProp,它包含myCombo的值:
private function eventFired(event:evtClass):void
{
selectedMenu = event.evProp;
if (selectedMenu == "null")
{
mx.controls.Alert.show("Please Choose a valid Blog");
return;
};
hs.send();
}
松耦合的组件是Flex 2程序中一个强大的工具,因为这使得开发者可以提高代码的重用性和可维护性。使用这种方式可以定义在多个程序中都能使用的响应不同用户交互的逻辑模块。
模块也可以被自定义成包含有能够接收其它模块传值的属性以及可以返回信息的事件。