Flex4 模块 [转载]
文章来源:http://www.pilihou.com/archives/191
使用flex4模块(1):传递数据到模块
在主程序与模块之间,模块与模块之间通信是可行的。你可以使用下列方法,以便在模块间,主程序和模块,模块和主程序的通信:
接口——你可以创建AS接口,该接口定义了可以被主程序和模块访问的属性和方法。这使你更好地控制主程序和模块的交互。它还可以防止你在模块和主程序之间建立依赖关系。
查询字符串参数——模块都是用URL加载的,你可以在这个URL上传递参数,然后在模块中解析这些参数。
ModuleLoader的child属性,ModuleManager的factory属性,以及Application的parentApplication属性——你可以用这些属性接入模块和主程序。但是,用这些属性,你会建立阻止代码重用的紧耦合的结构,此外,你在主程序和模块之间的建立了依赖关系而使类变大。
下面的访问属性和方法的技术同时适用于主程序和模块。模块可以加载其它模块。加载方法类似于简单的例子中的父应用程序加载子模块。
使用flex4模块(2):通过接口进行模块通信
你可以用接口来实现模块和主程序之间的通信,模块实现了接口,你的主程序设置接口的属性或调用接口的方法。该接口定义了你希望在应用程序和模块中共享的属性和方法。模块实现了在主程序可以访问的接口,或主程序实现了在模块中可以访问的接口。这让你避免了在模块和主程序之间的硬依赖关系。
在主程序中,当你想调用模块中的方法,你将ModuleLoader的child属性转换为一个自定义接口。
下面的示例应用程序让你可以自定义模块的外观,该模块是通过调用在自定义的IModuleInterface接口中定义的方法加载的。主程序同时调用了getModuleName()方法。该方法从模块中返回了一个值,并用这个值设置了本地属性。
<?xml version="1.0"?> <!-- modules/interfaceexample/MainModuleApp.mxml --> <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" xmlns="*"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ import mx.events.ModuleEvent; import mx.modules.ModuleManager; [Bindable] public var selectedItem:Object; [Bindable] public var currentModuleName:String; private function applyModuleSettings(e:Event):void { /* 将ModuleLoader的子对象转换为接口,这个子对象是一个模块的实例,现在你可以调用该实例的方法了。*/ var ichild:* = mod.child as IModuleInterface; if (mod.child != null) { /* 当模块加载时,在模块中调用setter方法来调整外观 */ ichild.setAdjusterID(myId.text); ichild.setBackgroundColor(myColor.selectedColor); } else { trace("Uh oh. The mod.child property is null"); } /* 通过调用接口的方法设置本地变量的值 */ currentModuleName = ichild.getModuleName(); } private function reloadModule():void { mod.unloadModule(); mod.loadModule(); } ]]> </fx:Script> <mx:Form> <mx:FormItem label="Current Module:"> <mx:Label id="l1" text="{currentModuleName}"/> </mx:FormItem> <mx:FormItem label="Adjuster ID:"> <mx:TextInput id="myId" text="Enter your ID"/> </mx:FormItem> <mx:FormItem label="Background Color:"> <mx:ColorPicker id="myColor" selectedColor="0xFFFFFF" change="reloadModule()" /> </mx:FormItem> </mx:Form> <s:Label text="Long Shot Insurance" fontSize="24"/> <s:ComboBox labelField="label" close="selectedItem=ComboBox(event.target).selectedItem"> <s:dataProvider> <s:ArrayList> <fx:Object label="Select Module"/> <fx:Object label="Auto Insurance" module="AutoInsurance2.swf"/> </s:ArrayList> </s:dataProvider> </s:ComboBox> <s:Panel width="100%" height="100%"> <mx:ModuleLoader id="mod" width="100%" url="{selectedItem.module}" ready="applyModuleSettings(event)"/> </s:Panel> <s:Button id="b1" label="Reload Module" click="reloadModule()"/> </s:Application>
下面的例子定义了一个含有两个getter方法和一个setter方法简单的接口。这个接口在前面的例子中被用于主程序。
// modules/interfaceexample/IModuleInterface.as package { import flash.events.IEventDispatcher; public interface IModuleInterface extends IEventDispatcher { function getModuleName():String; function setAdjusterID(s:String):void; function setBackgroundColor(n:Number):void; } }
下面的例子定义了在前面例子中调用的模块。它实现了自定义了IModuleInterface接口。
<?xml version="1.0"?> <!-- modules/interfaceexample/AutoInsurance2.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%" implements="IModuleInterface"> <mx:Panel id="p1" title="Auto Insurance" width="100%" height="100%" backgroundColor="{bgcolor}"> <s:Label id="myLabel" text="ID: {adjuster}"/> </mx:Panel> <fx:Script> <![CDATA[ [Bindable] private var adjuster:String; [Bindable] private var bgcolor:Number; public function setAdjusterID(s:String):void { adjuster = s; } public function setBackgroundColor(n:Number):void { /* 用绑定的属性来设置模块中控件的值。这确保了即使flex在模块加载之后使用属性,属性也将被设置,但它是由用户渲染的 */ bgcolor = n; /* 别这么干,当ModuleLoader触发ready事件时,背景颜色样式未必被设置 */ // p1.setStyle("backgroundColor", n); } public function getModuleName():String { return "Auto Insurance"; } ]]> </fx:Script> </mx:Module>
一般来说,如果你想通过外部的设置来控制模块属性,你应该创建绑定的变量。之后你可以在接口实现的方法中设置这些变量的值,如果你尝试使用外部价值直接设置模块控件的属性,当模块被加载时该控件可能不会被实例化并且尝试设置的属性可能会失败。
使用flex4模块(3):用查询字符串传递数据到模块
将数据传递给模块的一种方法是给你加模块的URL添加查询字符串参数。然后,您可以使用ActionScript解析查询字符串来访问数据的。
在模块中,你可以用loaderInfo的属性来访问URL。这个属性指向加载的SWF的 LoaderInfo对象(在主程序的情况下)。LoaderInfo对象所提供的信息包括加载进度,加载的网址,被加载的内容,应用程序的文件大小和应用程序的宽高。
下面的示例应用程序建立含有唯一的查询字符串的加载模块,查询字符串包含了firstName和lastName参数。
<?xml version="1.0"?> <!-- modules/QueryStringApp.mxml --> <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" height="500" width="400"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ public function initModule():void { // 构建查询字符串,以便它看起来像这样; // "QueryStringModule.swf?firstName=Nick&lastName=Danger" var s:String = "movies/QueryStringModule.swf?" + "firstName=" + ti1.text + "&lastName=" + ti2.text; // 改变着ModuleLoader的url属性致使ModuleLoader加载新的模块。 m1.url = s; } ]]> </fx:Script> <mx:Form> <mx:FormItem id="fi1" label="First Name:"> <mx:TextInput id="ti1"/> </mx:FormItem> <mx:FormItem id="fi2" label="Last Name:"> <mx:TextInput id="ti2"/> </mx:FormItem> </mx:Form> <mx:ModuleLoader id="m1"/> <s:Button id="b1" label="Submit" click="initModule()"/> </s:Application>
下面的示例模块分析用来加载它的查询字符串,如果设置了firstName和lastName参数,模块在TextArea显示出结果。该模块还trace一些通过LoaderInfo对象提供的额外的信息:
<?xml version="1.0"?> <!-- modules/QueryStringModule.mxml --> <mx:Module 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="parseString()"> <fx:Script> <![CDATA[ import mx.utils.*; [Bindable] private var salutation:String; public var o:Object = {}; public function parseString():void { try { /* 删除问号前面的所有内容,包括问号。 */ var myPattern:RegExp = /.*\?/; var s:String = this.loaderInfo.url.toString(); s = s.replace(myPattern, ""); /* 创建一个形如name=value字符串数组 */ var params:Array = s.split("&"); /* 输出数组中的参数 */ var keyStr:String; var valueStr:String; var paramObj:Object = params; for (keyStr in paramObj) { valueStr = String(paramObj[keyStr]); ta1.text += keyStr + ":" + valueStr + "\n"; } /* 设置称呼的值 */ for (var i:int = 0; i < params.length; i++) { var tempA:Array = params[i].split("="); if (tempA[0] == "firstName") { o.firstName = tempA[1]; } if (tempA[0] == "lastName") { o.lastName = tempA[1]; } } if (StringUtil.trim(o.firstName) != "" && StringUtil.trim(o.lastName) != "") { salutation = "Welcome " + o.firstName + " " + o.lastName + "!"; } else { salutation = "Full name not entered." } } catch (e:Error) { trace(e); } /* 显示loaderInfo的一些有用的信息 */ trace("AS version: " + this.loaderInfo.actionScriptVersion); trace("App height: " + this.loaderInfo.height); trace("App width: " + this.loaderInfo.width); trace("App bytes: " + this.loaderInfo.bytesTotal); } ]]> </fx:Script> <s:Label text="{salutation}"/> <s:TextArea height="100" width="300" id="ta1"/> </mx:Module>
这个例子调用了String类和StringUtil类的方法,再加上在for-in循环来解析网址。你也可以用URLUtil类和URLVariables类的方法来实现。
模块都是通过它们的包含查询字符串的URL来缓存的。因此,如你改变了URL地址或URL上的任意的查询字符串参数你将加载一个新的模块。如果你想根据在URL中传递的参数来加载一个模块的多个实例,这可能是有用的。
使用flex4模块(4):从子模块访问父应用程序
子模块可以通过使用parentApplication属性的引用来访问父应用程序的属性和方法。
下面的示例在模块第一次加载时访问父应用程序的价钱属性,然后该模块使用这个属性–ArrayCollection作为图表的数据源。当用户点击按钮,子模块调用父应用程序的getNewData()方法为图表返回一个新的ArrayCollection:
<?xml version="1.0"?> <!-- modules/ChartChildModule.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%" creationComplete="getDataFromParent()"> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] private var expenses:ArrayCollection; // 访问父应用程序的属性 private function getDataFromParent():void { expenses = parentApplication.expenses; } ]]> </fx:Script> <mx:ColumnChart id="myChart" dataProvider="{expenses}"> <mx:horizontalAxis> <mx:CategoryAxis dataProvider="{expenses}" categoryField="Month"/> </mx:horizontalAxis> <mx:series> <mx:ColumnSeries xField="Month" yField="Profit" displayName="Profit"/> <mx:ColumnSeries xField="Month" yField="Expenses" displayName="Expenses"/> </mx:series> </mx:ColumnChart> <mx:Legend dataProvider="{myChart}"/> <s:Button id="b1" click="expenses = parentApplication.getNewData();" label="Get New Data"/> </mx:Module>
下面的例子展示了在前面例子中模块中使用的父应用程序:
<?xml version="1.0"?> <!-- modules/ChartChildModuleLoader.mxml --> <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"> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; [Bindable] public var expenses:ArrayCollection = new ArrayCollection([ {Month:"Jan", Profit:2000, Expenses:1500}, {Month:"Feb", Profit:1000, Expenses:200}, {Month:"Mar", Profit:1500, Expenses:500} ]); public function getNewData():ArrayCollection { return new ArrayCollection([ {Month:"Apr", Profit:1000, Expenses:1100}, {Month:"May", Profit:1300, Expenses:500}, {Month:"Jun", Profit:1200, Expenses:600} ]); } ]]> </fx:Script> <mx:ModuleLoader url="movies/ChartChildModule.swf" id="m1"/> </s:Application>
你也可以访问和调用其它模块上的属性和方法。更多内容请看从其它模块访问本模块。
这种方法的缺点是,它在模块内的创建了和父应用程序的依赖关系。另外,这些模块不再便于多个应用程序移植,除非你确保复制了应用程序的行为。
为了避免上述缺点,您应该使用在父应用程序和它的子模块之间的安全接口,它定了你可以访问的属性和方法。有这么个接口可以让你重复使用的应用程序和模块,只要你不断更新的接口。欲了解更多信息,请看通过接口进行模块通信。
使用flex4模块(5):从其它模块访问本模块
你可以通过父应用程序用其它模块的引用来访问其它模块的属性和方法。你通过ModuleLoader的chlid属性来实现。这个属性指向让你访问和调用其属性和方法的模块类的实例。
下面的例子定义了一个单一的父应用程序,它加载了两个模块。InterModule1模块定义了返回一个字符串的方法。InterModule2调用了这个方法并用这个方法返回的值设置了它的Label组件的值。
父应用程序:
<?xml version="1.0"?> <!-- modules/InterModuleLoader.mxml --> <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/> </s:layout> <fx:Script> <![CDATA[ ]]> </fx:Script> <mx:ModuleLoader url="movies/InterModule1.swf" id="m1"/> <mx:ModuleLoader url="movies/InterModule2.swf" id="m2"/> </s:Application>
模块1:
<?xml version="1.0"?> <!-- modules/InterModule1.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%"> <fx:Script> <![CDATA[ /* 定义其它模块调用的方法 */ public function getNewTitle():String { return "New Module Title"; } ]]> </fx:Script> </mx:Module>
模块2:
<?xml version="1.0"?> <!-- modules/InterModule2.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%"> <fx:Script> <![CDATA[ [Bindable] private var title:String; // 调用另一个模块的方法 private function changeTitle():void { title = parentApplication.m1.child.getNewTitle(); } ]]> </fx:Script> <s:HGroup> <s:Label id="l1" text="Title: "/> <s:Label id="myTitle" text="{title}"/> </s:HGroup> <s:Button id="b1" label="Change Title" click="changeTitle()"/> </mx:Module>
父应用程序在这个例子中让两个模块互相通信。不过,你可以在父应用程序中定义模块可以访问的属性和方法。欲了解更多信息,请看从子模块访问父应用程序。
正如直接访问父应用程序的属性和方法,使用本节描述的技术可能让你的模块难以重用,也会建立依赖关系,从而导致模块比需要的更大。相反,你应该使用接口来定义模块之间的通信。欲了解更多信息,请看通过接口进行模块通信。
使用flex4模块(6):从父应用程序访问模块
你可以通过获得模块类的一个实例从它的父应用程序访问模块的属性和方法。通过它在父应用程序的类名引用模块导致整个模块和其所有相关的被链接到应用程序。这违背了使用模块的目的。您应该只使用接口来访问模块的属性和方法,除非你想创建这些依赖关系。欲了解更多信息,请看通过接口进行模块通信。
如果你用ModuleLoader来加载模块,你可以从父应用程序通过引用ModuleLoader的child属性调用模块上的方法,和转换到模块的类。child属性是模块类的一个实例。
在这种情况下,模块的类是定义了模块的名字MXML文件。
下面的示例从父应用程序调用模块的getTitle()方法:
父应用程序:
<?xml version="1.0"?> <!-- modules/ParentApplication.mxml --> <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/> </s:layout> <fx:Script><![CDATA[ [Bindable] private var s:String; private function getTitle():void { s = (m1.child as ChildModule1).getModTitle(); } ]]></fx:Script> <s:Label id="l1" text="{s}"/> <mx:ModuleLoader url="movies/ChildModule1.swf" id="m1" ready="getTitle()"/> </s:Application>
模块:
<?xml version="1.0"?> <!-- modules/ChildModule1.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%"> <fx:Script><![CDATA[ /* 定义父应用程序调用的方法 */ public function getModTitle():String { return "Child Module 1"; } ]]></fx:Script> </mx:Module>
如果你加载的模块要调用的ModuleManager API,它是一些额外的shell应用程序。你可以使用ModuleManager factory属性来获取模块类的一个实例。然后,你可以在这个实例中调用模块的方法。
下面的模块实例定义了一个computeAnswer()方法:
<?xml version="1.0"?> <!-- modules/mxmlmodules/SimpleModule.mxml --> <mx:Module xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <fx:Script> <![CDATA[ public function computeAnswer(a:Number, b:Number):Number { return a + b; } ]]> </fx:Script> </mx:Module>
下面的示例通过使用factory属性调用create()方法来获取SimpleModule类的实例。然后,它在该实例调用模块的computeAnswer()方法:
<?xml version="1.0"?> <!-- modules/mxmlmodules/SimpleMXMLApp.mxml --> <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="initApp()"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ import mx.modules.IModuleInfo; import mx.modules.ModuleManager; public var assetModule:IModuleInfo; public var sm:Object; [Bindable] public var answer:Number = 0; public function initApp():void { /* 获取指定的URL的IModuleInfo接口 */ assetModule = ModuleManager.getModule("movies/SimpleModule.swf"); assetModule.addEventListener("ready", getModuleInstance); assetModule.load(null, null, null, moduleFactory); } public function getModuleInstance(e:Event):void { /* 获取模块的实例 */ sm = assetModule.factory.create() as SimpleModule; } public function addNumbers():void { var a:Number = Number(ti1.text); var b:Number = Number(ti2.text); /* 调用模块上的方法 */ answer = sm.computeAnswer(a, b).toString(); } ]]> </fx:Script> <mx:Form> <mx:FormHeading label="Enter values to sum."/> <mx:FormItem label="First Number"> <mx:TextInput id="ti1" width="50"/> </mx:FormItem> <mx:FormItem label="Second Number"> <mx:TextInput id="ti2" width="50"/> </mx:FormItem> <mx:FormItem label="Result"> <mx:Label id="ti3" width="100" text="{answer}"/> </mx:FormItem> <mx:Button id="b1" label="Compute" click="addNumbers()"/> </mx:Form> </s:Application>
在这个例子中,你其实应该创建一个在ActionScript中扩展ModuleBase类而不是MXML中扩展Module类的模块。这是因为示例模块没有任何视觉元素,只包含一个计算并返回一个值方法。一个扩展了ModuleBase类的模块将比扩展了Module类的模块更轻量化。有关编写扩展ModuleBase类的ActionScript基础模块的文章,请看创建ActionScript基础模块。