现在的位置: 首页 > 综合 > 正文

Parsley 开发指南 9 动态视图装配

2013年08月17日 ⁄ 综合 ⁄ 共 19548字 ⁄ 字号 评论关闭
文章目录

9 动态视图装配

到目前为止所有”live”在Parsley上下文中的对象,用MXML,XML或者Actionscript定义的,详细查看3 配置和初始化。这些机制为Flash应用程序通常是足够的,因为很有可能你可以方便地在XML或ActionScript中定义所有托管对象——包括视图元素。对于Flex应用程序这种方法并不理想,因为你会更喜欢在你的MXML文件中声明你的具有组件层次结构的组件,而不是在一个单独的Parsley上下文MXML配置类中。所以我们需要一种不同的机制来把这些用MXML定义的组件与Parsley配置文件中的对象连接起来。Parsley对这种情况提供的解决方案将在本章介绍。

9.1 初始化视图装配支持(Initializing View Wiring Support)

对于视图装配,每个上下文需要一个或多个"view root",view root是一个DisplayObject对象,用来监听来相关上下文中组件的事件。你可以用MXML标签初始化上下文或通过编程实现。

用MXML标签初始化上下文

在FLEX应用程序中,你可以使用ContextBuilder标签(version 2.2版本引入)。这将用自动在viewRoot上使用文档对象(document object)。

<parsley:ContextBuilder config="{BookStoreConfig}"/>

你自己想直接指定view root的情况很罕见,可以如下配置:

     

<parsley:ContextBuilder config="{BookStoreConfig}" viewRoot="{someOtherDisplayObject}"/>

上下文编码初始化

如果你通过编程方式初始化框架,视图根必须显式指定:

XML配置

var viewRoot:DisplayObject = ...;

XmlContextBuilder.build("bookStoreConfig.xml" viewRoot);

多个配置机制

var viewRoot:DisplayObject = ...;

ContextBuilder.newSetup()

    .viewRoot(viewRoot)

    .newBuilder()

        .config(FlexConfig.forClass(BookStoreConfig))

        .config(XmlConfig.forFile("logging.xml"))

.build();

9.2 显式组件装配

如果你想将一个组件直接连接到组件自身所在的上下文中,你可以有二个选择。第一,你可以使用<Configure>标签:

<s:Panel 

    xmlns:fx="http://ns.adobe.com/mxml/2009"

    xmlns:s="library://ns.adobe.com/flex/spark"

    xmlns:parsley="http://www.spicefactory.org/parsley"

    >

    

    <fx:Declarations>

    

        <parsley:Configure/>

    

    </fx:Declarations>

    

    <!-- ... -->

    

</s:Panel>

在FLEX4中,这将需要使用 <fx:Declarations> 标签。例如上例组件Panel将连接到上下文。你可以指定被连接的对象为 <Configure> 标签的孩子,这种情况下这个对象甚至可以不是组件:

<s:Panel 

    xmlns:fx="http://ns.adobe.com/mxml/2009"

    xmlns:s="library://ns.adobe.com/flex/spark"

    xmlns:parsley="http://www.spicefactory.org/parsley"

    xmlns:view="myproject.view.*"

    >

    

    <fx:Declarations>

    

        <parsley:Configure>

             <view:MyPanelPM id="model"/>

        </parsley:Configure>

        

    </fx:Declarations>

    

    <!-- ... -->

    

</s:Panel>

本例中,我们声明一个表示层model,然后指示框架将这个model连接到上下文中。你可以添加多个子标签连接一个对象。有一个或多个子标签的组件不会被容器管理,所以你就不能在该视图组件内设置任何parsley元数据标签。这个模式主要用于性能优化。参见下一节的第二部分将重点谈谈如何避免大型组件类的过多反射.

在ActionScript 组件中连接

当你用ActionScript创建一个组件或者当你创建一个纯Flash程序的时候你不能使用 <Configure>MXML标签。这种情况下你可以使用一个相应的API:

public class MyView extends Sprite {

    function MyView () {

        Configure.view(this).execute();

    }

    

    /* ... */

    

}

上述例子中MyView 会自己添加到上下文中,但是API有很多其他选项(包括一些相当低级钩子)。比如<Configure>标签,你可以指定一个不同的对象来连接。

var someHelper:Object = ...;

Configure.view(this).target(someHelper).execute();

但是你需要意识到,这个操作可能是异步的,所以你不能依赖被配置的对象,比如第二行代码。托管视图的初始化逻辑最好在一个[Init]方法中.

关于API的其他选项请查看Configure 类的 ASDoc。

不创建Parsley的依赖显式地连接

对于非常简单的用例,不需要指定特殊的选项,你可以选择使用configureView 类型派发一个普通的事件,设置冒泡属性为true:

public class MyView extends Sprite {

    function MyView () {

        addEventListener(Event.ADDED_TO_STAGE configure);

    }

    

    private function configure () : void {

        dispatchEvent(new Event("configureView" true));

    }

    

    /* ... */

    

}

现在这个组件甚至能够不在Parsley中运行也能工作。9.4 自动组件装配 是另一种避免把你的组件绑在Parsley的方式。

Flash 省略

9.3 无反射组件连接(Component Wiring without Reflection)

Parsley对所有管理对象和组件反射操作代价很大,主要是由于其属性、方法和事件太多。一个组件可能花费30毫秒。Parsley维护一个内部反射缓存,如果你使用了大多的不同组件,缓存的意义就不大了。一个小的应用程序,这种花费很少,但是对于大型系统,就要重点关注了。本节内容通过一些案例让你避免性能大幅下降。

9.3.1 The FastInject MXML Tag

为了允许性能优化,Parsley 2.2引入标签FastInject 

<s:Panel 

    xmlns:fx="http://ns.adobe.com/mxml/2009"

    xmlns:s="library://ns.adobe.com/flex/spark"

    xmlns:parsley="http://www.spicefactory.org/parsley"

    xmlns:view="myproject.view.*"

    >

    

    <fx:Script>

        <![CDATA[

            import com.bookstore.model.MyPanelPM;

            

            public var model:MyPanelPM;

        ]]>

    </fx:Script>

    

    <parsley:FastInject property="model" type="{MyPanelPM}" /> 

    

    <!-- ... -->

    

</s:Panel>

这有点类似与<Configure>标签和它的target属性的例子。这个模型是连接到上下文的。但不同的是,在前一个例子中,模型是由组件内的标签创建,然后由框架配置这个模型实例。在这个例子中,容器会创建模型对象(它必须被相对应的上下文配置了),然后注入到组件中。这种方法的主要优点是,该模型可以在组件之间共享,而前例不行。只有当你关注性能和你想避免组件自己被连接到上下文才使用这种机制。

<FastInject> 也允许使用子标签来定义多个注入:

<parsley:FastInject injectionComplete="init();">

    <parsley:Inject property="model" type="{MyPanelPM}"/> 

<parsley:Inject property="status" type="{MyApplicationStatus}"/> 

</parsley:FastInject>

如果你想在所有注入完成的时候执行一些动作,可以把他们结合到一个单独的父标签内。如果你使用单独的标签,这是很麻烦的,淫威你需要手动检查这些这些注入是否已经完成。在上面的例子中,init方法会是肯定会在所有注入完成后才会执行。

<FastInject>标签有类似于原始组件的事件方法,比如creationComplete 和addedToStage 之类的,但是不同的是这些事件只有当对应的组件派发了事件并且注入已经完成后才会派发,便于找到合适的时刻来初始化该组件。

9.3.2 The FastInject API for ActionScript Components

从2.4版开始,也提供了一个跟<FastInject>标签相同功能的API。

public class MyView extends UIComponent {

    public var pm:MyViewPM;

    

    function MyView () {

        FastInject

            .view(this)

            .property("pm")

            .type(MyViewPM)

            .complete(injectionComplete)

            .execute();

    }

    

    private function injectionComplete () : void {

         /* ... */

    }

    

    /* ... */

    

}

当然这个API也在Flash应用程序中工作。

9.3.3 Custom ViewProcessors for Autowiring

这部分的所有例子都避免在UIComponents上使用反射,框架为每个需要注入组件指定一个标签是很有代价的。一些开发人员更喜欢纯粹一点并且用非侵入性的方式使用IOC容器。对于这些需要自动装配特性可以解释在 9.4自动组件连接。但是自动装配的默认处理器添加每个视图,它传递ViewAutowireFilter到上下文中,这引发了组件的反射。由于这一章是关于显示避免反射的方式,,我们将演示另一种方式做自动装配。因为添加对象到上下文是唯一明显的选择来处理一个autowired的视图,没有其他的选项。通过一些其他方法自动装配通常是用于特定应用程序的, 所以你需要创建你自己的ViewProcessor。

创建一个自定义的避免反射的自动装配机制需要以下步骤:

l 创建一个定制的ViewProcessor 进行视图的实际处理(比如执行注入)

l 创建一个定制的ViewAutowireFilter ,知道哪个视图应该得到处理。默认的过滤器是更适合于你可以添加视图自身到上下文中的这种用例。

l 用ContextBuilder注册处理器和过滤器

下面部分给这些步骤给了一个简单的例子

The View Component

我们假设你想使用一种模式,所有视图属于一个已知的包,声明了显示层模型变量名叫做model ,并且自动把他们的model添加到上下文中。这个是一个廉价的操作,因为一个不继承于UIComponent 的显示层模型通常反射很快。这只是一个例子,如果你不喜欢这种模式就创建自己的适合你的应用程序的要求。

这是一个简单视图组件的一部分:

<s:Panel ...>

    <fx:Declarations>

        <view:MyPanelPM id="model"/>

    </fx:Declarations>

    

    <!-- ... -->

    

</s:Panel>

The Custom ViewProcessor

ViewProcessor添加model到上下文。你可以另外检查模型的类名称是否匹配一个特定的模式,如经常使用的< ViewClassName > PM。

public class MyViewProcessor implements ViewProcessor {

    

    private var dynamicObject:DynamicObject;

    

    public function init (config:ViewConfiguration context:Context) : void {

        var target:Object = config.view["model"];

        dynamicObject = context.addDynamicObject(target);

    }

    

    public function destroy () : void {

        dynamicObject.remove();

    }

    

}

处理器添加PM到上下文中并且在视图声明周期结束的时候删除它们。销毁方法的调用是由生命周期处理器触发的(你也可以写一个自定义的如果你需要的话, 查看 9.7.7 自定义生命周期处理器

The Custom ViewAutowireFilter

我们的自动装配过滤器只会检查包的名称是否匹配一个特定的模式和对象是否拥有一个名字叫做model的属性。对于效率,我们在prefilter 方法中已经做过了。在多个上下文的环境中总是调用根上下文的过滤器。为每个对象传递一个预过滤器,框架会派发一个冒泡事件,会被视图层级中高于目标最近的上下文捕获。然后目标上下文的filter 方法会在对象传递到目标上文的处理器之前作出最后的决定。

所以你只需要在最后一个filter 方法中写逻辑,当逻辑与目标上下文使用的不同的时候。由模块创建的不同的子上下文甚至会安装他们自己的ViewAutowireFilter。 这种情况下大部分的逻辑应该驻留在根上下文的filter 方法和prefilter 方法中,应该只会用于性能优化(例如当我们知道只这是一个皮肤的时候节约冒泡事件)。但在我们的案例中,最后的prefilter 方法总是会返回ViewAutowireMode.ALWAYS。

public class DefaultViewAutowireFilter extends AbstractViewAutowireFilter {

    

    private var packageFilter:RegExp = /^com\.mycompany\..*\.view\..*/;

    

    public override function prefilter (object:DisplayObject) : Boolean {

        return packageFilter.test(getQualifiedClassName(object)) && object.hasOwnProperty("model");

    }

    

    public override function filter (object:DisplayObject) : ViewAutowireMode {

        return ViewAutowireMode.ALWAYS;

    }

    

}

Installing the Custom Processor and Filter

处理器和过滤器可以用ContextBuilder 标签的子标签来安装

<parsley:ContextBuilderTag config="">

    <parsley:ViewSettingsTag autowireFilter="{new MyViewAutowireFilter()}"/>

<parsley:ViewProcessorTag type="{MyViewProcessor}"/>

</parsley:ContextBuilderTag>

过滤器是一个实例,会被用于所有视图组件,而我们只是为处理器指定一个类因为框架会为每个视图创建一个实例。这是很方便的,因为它允许我们保持状态,就像上面例子中的处理器一样。

9.4 自动组件装配

从2.2版开始,你还可以通过自动视图装配,避免框架为单个组件类添加具体配置。但是parsley存在多上下文环境,自动注入会导致不知道哪些上下文中。目前只能基于他们所处的视图层次的位置。一个组件被注入到最接近的上下文中。

自动装配必须显式地激活:

MXML

<parsley:ContextBuilder>

    <parsley:ViewSettings autowireComponents="true"/>

    <parsley:FlexConfig type="{MyConfig}"/>

</parsley:ContextBuilder>

ActionScript

BootstrapDefaults.config.viewSettings.autowireComponents = true;

在第一个上下文创建之前,上一行语句必须执行。正如你所期望的,autowireFilter可是选 的。你可以实现自己的选择逻辑,告诉框架哪个组件应该注入。默认仅注入MXML或XML文件中<view>标签的组件。这种方式真正做到了统一配置。你在一个配置文件中就可以看到托管的视图和对象。一个示例配置片段可能是这样的:

<View type="{MainView}"/>

<Object type="{MainViewPM}"/>

<View type="{StatusBar}"/>

<Object type="{StatusBarPM}"/>

<View type="{ImageTileList}"/>

<Object type="{ImageTileListPM}"/>

<View type="{ImagePreviewWindow}"/>

<Object type="{ImagePreviewWindowPM}"/>

这里我们成对的把视图和他们的显示层模型列出了。只有列出的视图会被连接到上下文。在这个示例中,我们只使用空的View 标签,但是你可能在里面嵌套标签就像Object标签一样。详细查看 9.6 MXML 和 XML 配置

正如你在上面示例看到的,我们使用<View>标签声明自定义的视图,特定于应用程序的。Flex SDK 中的 spark.* 或者 mx.*,或者Flash  Player  API(flash.* packages)的内置组件,是预过滤的来提高性能,所以他们不能配置为<View>标签,如果你使用默认设置。这通常不是一个好主意,配置它们时你有很多他们和匹配他们的配置,这可能成为挑战。所以通常你会连接你自定义的panel, windows, popups, tabs 和boxes,然后传递他们到容器包含的组件中。

但是这只是默认机制。如果你需要更多的细粒度的控制那些组件,这些组件可以很容易的实现你自己的自动装配过滤器,比如用一个机制,基于他们所属于的包来选择组件,就像其他框架处理这个一样。最简单的方法是扩展DefaultViewAutowireFilter 覆盖filter 方法

public class MyAutowireFilter extends DefaultViewAutowireFilter {

    public override function filter (view:DisplayObject) : ViewAutowireMode {

        if (... someCondition ...) {

            return ViewAutowireMode.ALWAYS;

        }

        else {

            return ViewAutowireMode.NEVER;

        }

}

}

最后安装这个filter:

<parsley:ContextBuilder>

    <parsley:ViewSettings 

        autowireComponents="true" 

        autowireFilter="{new MyAutowireFilter()}"

    />

<parsley:FlexConfig type="{MyConfig}"/>

</parsley:ContextBuilder>

9.5 元数据配置 Metadata Configuration

很多时候元数据就够用了,并且是最方便的配置机制。组件上的元数据标签与容器创建的对象具有相同的效果:

<s:Panel 

    xmlns:fx="http://ns.adobe.com/mxml/2009"

    xmlns:s="library://ns.adobe.com/flex/spark"

    xmlns:parsley="http://www.spicefactory.org/parsley"

    addedToStage="dispatchEvent(new Event('configureView' true));"

    >

    

    <fx:Script>

        <![CDATA[

            import com.bookstore.events.*;

            import com.bookstore.model.*;

            

            [Bindable]

            private var user:User;

            

            [Inject]

            public var model:LoginPanelPM;    

            

            [MessageHandler]

            public function handleLogin (event:LoginEvent) : void {

                this.user = event.user;            

            }

        ]]>

    </fx:Script>

    

    <s:Label text="Current User: {user.name}"/>

    

    <!-- some more components ... -->

    

</s:Panel>

许多开发人员更喜欢避免添加太多的逻辑带他们的组件中。在这些情况下唯一用于组件的Parsley标签通常是[inject]标签在表示模型封装组件所需的所有逻辑和数据。

9.6 MXML 和 XML 配置

自Version2.2,你可以在MXML或XML上配置视图。在两种情况下会很方便:一、你可以在不同的场景中使用相同的组件类,因此不能使用元数据配置这些标签,因为这些标签只能应用于类级,不能用于实例级。二、你可能想使用配置机制来指定组件自动装配 如同 9.4 自动组件装配

    这个功能已用在了Flicc中,当它被注入到上下文中,容器将配置信息作用到视图上。作用于已连接组件的配置信息将根据ID或type匹配:

<View id="myPanel" type="{MyPanel}">

    <Property name="url" value="http://www.somewhere.com/"/>

<MessageHandler method="execute"/>

</View>

这看上去象一般对象的配置信息。使用View标签指示Parsley不试图创建这个类的实例,而是等到任何匹配的组件被动态连接到上下文,然后应用配置。

Parsley首先根据ID匹配。ID既可以是FLEX组件的名字,也可以是配置标签的ID

<parsley:Configure configId="someId"/>

如果根据ID没有匹配到,框架将根据类型匹配。如果定义含糊,则抛出错误。如果没有匹配类型,parsley将使用传统机制去处理元数据。

9.7 组件生命周期

因为Flex组件“临时”的连接到IOC容器,组件的生命周期不同于直接在容器定义的对象。Parsley 提供了几个配置选项来控制生命周期。

9.7.1 控制组件的生命周期

默认的行为是,一个组件被移除从上下文时自动从舞台被移除。但这可以通过ViewSettings标签的一个属性改变。

<parsley:ContextBuilder>

    <parsley:ViewSettings autoremoveComponents="false"/>

<parsley:FlexConfig type="{MyConfig}"/>

</parsley:ContextBuilder>

上面的组件现在必须派发一个自定义的“removeView”时间,当他希望被移除的时候。注意,当你使用 <FastInject> 的时候该选项没有任何影响,因为这种情况下,模板组件不被托管,并且只接受一次性注入。

9.7.2 控制视图根的生命周期

生命周期密切相关的是所谓的视图根。如果你使用<Configure>标签,这个标签发送了一个冒泡事件表示它希望连接到最近的上下文。其他视图连接选项如自动装配以类似的方式工作。一个视图根是捕获这些冒泡事件的DisplayObject。当你使用 <ContextBuilder>标签,有这个标签的组件将自动变成视图根。下一届在popups 和AIR windows 显示有时候你需要给上下文添加额外的视图根。

对于这些视图根,默认行为是控制他们的生命周期以类似的方式,就像连接组件一样。这意味着一个视图根当它被从舞台移除它就被从上下文移除,因此它不再侦听事件冒泡。最重要的是当最后一个视图根被移除的时候,相关的Conetxt会被摧毁。这是非常有用的,比如这个上下文在一个弹出窗口中。你不需要显示的摧毁它,当用户关闭弹出窗口的时候这是自动发生的。如果你想阻止这个行为,视图根还有另外一个标签。

<parsley:ContextBuilder>

    <parsley:ViewSettings autoremoveViewRoots="false"/>

<parsley:FlexConfig type="{MyConfig}"/>

</parsley:ContextBuilder>

9.7.3 为单个组件设置

前面两个部分显示全局控制组件和视图根的生命周期的开关。但还有一种方法可以更改为单个组件设置:

Configure

Configure MXML 标签和 API都有一个autoremove 属性:

<parsley:Configure autoremove="false"/>

FastInject

同样,FastInject MXML标签和API都有一个autoremove 属性:

<parsley:FastInject autoremove="false" property="pm" type="{MyPM}"/>

Autowiring

当使用默认的ViewAutowireFilter的时候,支持在已连接组件上使用Metadata标签表示autoremove是否应该应用。

<fx:Metadata>

[Autoremove("false")]

</fx:Metadata>

9.7.4 谨防内存泄漏

如果你使用一个如上所示配置选项,关掉自动生命周期管理,你必须小心不要导致内存泄漏。一个组件(或任何类型的对象)不能得到垃圾收集,只要它是由Parsley托管的。所以,如果你关掉生命周期管理,你必须确保在那个上下文中所有已连接组件有下列条件之一:

l 组件无论如何都不会被从舞台移除。

l 组件偶尔被移除的阶段,但实例总是保存在内存中,以备重用

l 组件可能会远离舞台,不是为了被重用,但分派定制removeView事件,以确保它正确被断开连接。

9.7.5 小心组件初始化逻辑

许多开发人员都想初始化一个托管组件的表示模型如下:

<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009" 

         xmlns:s="library://ns.adobe.com/flex/spark" 

         xmlns:parsley="http://www.spicefactory.org/parsley"

         width="400" height="300"

         addedToStage="init()" 

    >

    

    <fx:Script>

        <![CDATA[

            

            [Inject]

            public var pm:SomePM;

            

            private function init () : void {

                pm.init();

            }

            

        ]]>

    </fx:Script>

    <fx:Declarations>

        <parsley:Configure/>

    </fx:Declarations>

    

</s:Panel>

有两个主要的问题,这种方法都经常导致bug,表面在一个应用程序开发生命周期晚期的,通常很难缩小:

l 没有保证在ADDED_TO_STAGE事件发生的时候,注入已经执行完毕。第一,不可能同步Flex组件的生命周期和Parsley,因为事件顺序通常存在微妙的区别。比如当INITIALIZE 事件可能在ADDED_TO_STAGE事件之前或者之后发生,Parsley必须在执行任何注入之前等到这两个事件。第二,相关的上下文可能以异步方式进行初始化,例如如果它包含XML配置,须首先装载或任何标注[AsyncInit]的对象。这种情况下注入将会在上下文完成初始化后执行。

l ADDED_TO_STAGE事件可能会在想不到的时候再次发生。这个问题甚至跟Parsley无关,但是已经被忽略。 舞台事件是低级基础设施事件,当你依赖他们来进行应用程序声明周期管理,你的代码会变得非常脆弱。当用户调整应用程序,比如Flex可能需要引入一个滚动条或者突然重新定义一个组件。这将导致一个临时的REMOVED_FROM_STAGE后紧跟着一个ADDED_TO_STAGE 事件,从而如上所示引发再次初始化。使用这种方法的应用程序可能会突然失去他们的视图状态。这尤其危险,因为测试调整经常在QA被忽视。因此建议避免这种模式,不管你使用的是不是Parsley。对于其内部生命周期管理,Parsley有逻辑过滤这些临时事件。

正确的做法应该是这样

使用<Configure>或者自动装配

<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009" 

         xmlns:s="library://ns.adobe.com/flex/spark" 

         xmlns:parsley="http://www.spicefactory.org/parsley"

         width="400" height="300"

    >

    

    <fx:Script>

        <![CDATA[

            

            [Inject]

            public var pm:SomePM;

            

            [Init]

            public function init () : void {

                pm.init();

            }

            

        ]]>

    </fx:Script>

    <fx:Declarations>

        <parsley:Configure/>

    </fx:Declarations>

    

</s:Panel>

Using <FastInject>

<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009" 

         xmlns:s="library://ns.adobe.com/flex/spark" 

         xmlns:parsley="http://www.spicefactory.org/parsley"

         width="400" height="300"

         creationComplete="init()" 

    >

    

    <fx:Script>

        <![CDATA[

            

            public var pm:SomePM;

            

            private function init () : void {

                pm.init();

            }

            

        ]]>

    </fx:Script>

    <fx:Declarations>

        <parsley:FastInject 

            property="pm"

            type="{SomePM}"

            injectionComplete="init()"

        />

</fx:Declarations>

</s:Panel>

如上所示在这两个例子中,是保证注入执行后,init方法才会被执行。

当使用< FastInject >你不能使用[Init]或[Destroy] 标签,组件不是被托管的,元数据不会被处理。已连接视图的这两种元数据标签的行为,将在以下部分解释。

9.7.6 生命周期方法

管理视图支持相同的 [Init] 和 [Destroy]方法,像普通Parsley配置类或者文件中声明的托管对象一样,但是托管视图的生命周期是不一样的。

Methods annotated with [Init]

对于一个直接声明在Parsley配置文件中的对象, 这些方法会在容器实例化和配置对象后执行。对于一个动态连接的Flex组件,它会在容器捕获了组件派发的配置事件和所有注入完成后调用。

Methods annotated with [Destroy]

对于一个直接声明在Parsley配置文件中的对象, 这些方法会在容器调用Context.destroy()或者(除了动态对象)对象从上下文移除的时候执行。对于一个动态连接的Flex组件,每个默认的摧毁方法会在组件从舞台移除后调用。当然这意味着相同的实例可以有多个调用它的Init和Destroy方法,以防它被移除并重新添加到舞台。

9.7.7 自定义生命周期处理器

托管视图的生命周期的区别通常取决于对于上下文的ViewSettings ,在 9.7.1 控制组件的生命周期 描述。因此默认Parsley有ViewLifecycle接口的两种实现,一个基于舞台时间来控制组件生命周期,一个基于组件派发的自定义事件(configureView和removeView)。

但是你可以为特定的视图类(和他们各自的子类型)安装自定义生命周期处理器,如果需要更底层的控制。下面是一个例子,一个生命周期处理器,简单的监听组件的事件:

public class MyPanelLifecycle extends EventDispatcher implements ViewLifecycle {

    

    private static const DESTROY:String = "destroy";

    private static const INIT:String = "init";

    

    private var config:ViewConfiguration;

    

    public function start (config:ViewConfiguration context:Context) : void {

        this.config = config;

        config.view.addEventListener(DESTROY destroyView);

        config.view.addEventListener(INIT initView);

    }

    

    public function stop () : void {

        config.view.removeEventListener(DESTROY destroyView);

        config.view.removeEventListener(INIT initView);

        config = null;

    }

    

    private function removeView (event:Event) : void {

        dispatchEvent(new ViewLifecycleEvent(ViewLifecycleEvent.DESTROY_VIEW config));

    }

    

    private function configureView (event:Event) : void {

        dispatchEvent(new ViewLifecycleEvent(ViewLifecycleEvent.INIT_VIEW config));

    }

    

}

生命周期处理器的唯一目的是当组件的声明周期开始或者结束(红色突出显示)的时候派发一个消息。上面的类是一个非常简单的例子,该处理程序主要是“转化”一些事件匹配框架知道的事件。但它可以包含任何自定义的逻辑。

这样一个处理器可以安装在上下文和它所有的孩子中:

<parsley:ContextBuilder config="...">

<parsley:ViewLifecycle viewType="{MyPanel}" lifecycle="{MyPanelLifecycle}"/>

</parsley:ContextBuilder>

对于每个MyPanel类型的托管视图或者它的子类型,将使用指定的生命周期处理器。框架会为每个视图实例创建一个生命周期处理器实例,所以你就可以在处理器类中保存他们的状态。

9.8 Flex Popups 和 Native AIR Windows

对于Flex Popups 和 Native AIR Windows 需要一些额外的步骤来告诉框架关于他们。这是因为这些视图与视图层次断开,低于主要应用程序组件。 Flex Popup通常在SystemManager下面,一个Native AIR Window甚至有它自己的SystemManager。所以你必须手动连接到一个Parsley ViewManager ,如果你想在popups 和windows中使用视图连接。下面几节将向你展示如何做到这一点。

Flex Popup

下面的代码片段,假设它是已连接MXML组件的一部分,或者其他类型的Parsley托管对象。这样注入实际上执行:

[Inject]

public var context:Context;

private function showPopup () : void {

    var win:TitleWindow = new TitleWindow();

    // set properties

    context.viewManager.addViewRoot(win);

PopUpManager.addPopUp(win this);

}

声明性弹出窗口

自2.3版以来,你现在还可以在MXML组件中声明性地指定弹出窗口。这个特性是基于Cairngorm 3弹出库,所以为了使用它,你需要包含相应的SWC。Parsley下载包含了这个库的Flex 3和 Flex4 版本 在libs 文件夹下。

主要添加了Cairngorm标签支持的特性集是透明和自动连接到Parsley上下文。所以在上面的例子相比,你不必显式地照顾连接弹出到Parsley上下文。你所要做的就是用Parsley MXML namespace 使用<PopUp>标签代替<PopUpWrapper>Cairngorm namespace中的标签。否则Parsley只支持Cairngorm 的相同属性。

[Bindable]

private var popupOpen:Boolean;

[...]

<parsley:PopUp open="{popupOpen}" center="true">

<myNS:SomePopUp/>

</parsley:PopUp>

AIR Window

这类似于Flex Popup连接

[Inject]

public var context:Context;

private function openWindow () : void {

    var win:Window = new Window();

    // set properties

    context.viewManager.addViewRoot(win);

win.open();

}

Autoremoval of Contexts in AIR Windows

Parsley应用程序中的视图根的默认行为是一旦视图从舞台移除,那么视图根就从上下文移除。对于这个工作Parsley必须监听舞台事件。这可以可靠地为Flex Popups工作,不需要任何额外的管道,但不幸的是native AIR windows 有一个有点古怪的行为在这方面。所以,如果你在 AIR window内创建一个单独的上下文,需要更多的工作。

首先为你在 AIR window中创建的上下文关掉autoremoval特性.

<parsley:ContextBuilder config="...">

<parsley:ViewSettings autremoveViewRoots="false"/>

</parsley:ContextBuilder>

这样Parsley不会监听window的舞台事件,而是监听一个自定义的“removeView”事件。所以你必须增强前面部分中AIR window的装配来派发这个自定义事件。

[Inject]

public var context:Context;

private function openWindow () : void {

    var win:Window = new Window();

    // set properties

    context.viewManager.addViewRoot(win);

    win.addEventListener(Event.CLOSE function (event:Event) : void {

        win.dispatchEvent(new Event("removeView"));

    });

win.open();

}

在当前的版本是没有办法教框架直接监听CLOSE 事件,所以上面所示的“翻译”步骤是必需的。未来的版本将很有可能对于这个场景提供一个快捷方式。

9.9 视图装配在模块化的应用程序

下面的图表说明了视图装配是如何在模块化应用程序中工作的,和为什么它对手动连接到Flex Popups 和 Native AIR Windows 到 上下文来说是很重要的。

包含<Configure/>标签的Flex组件派发一个冒泡事件,被组件层级中比这个组件高层级的下一个ViewManager 的监听器捕获。这样每个组件找到其匹配的上下文,这是很重要的,因为一个组件加载到一个模块通常也需要访问模块的配置。如果它会连接到根上下文,它只会“看见”该上下文中的对象。

在图中的上下文C,你会看到两个视图根被连接到上下文:一个用于加载到模块的根组件,一个用于通过模块打开一个弹出窗口。 很明显现在为什么这个手动连接到弹出是必要的:如果一个组件之下的某个弹出窗口会派发一个冒泡事件,它会在SystemManager结束,从未达到根模块组件中的ViewManager,如果我们在不在弹出窗口本身本身放第二个监听器。

最后这个机制还为我们处理ApplicationDomains:Parsley框架内部的ModuleManager在根模块组件上方监听器,捕获ContextBuilders 派发的冒泡事件,然后告诉他们使用哪个ApplicationDomain。

极少数情况下你可能想要加载一个Flex模块,这个模块只包含几个你想要连接到上下文的MXML视图类,但是没有控制器、命令、表示层或域模型。你可能在这种情况下会试图跳过上下文创建,但这可能导致组件将被连接到根上下文,但在类上面反射的时候使用不同的ApplicationDomain。所以你仍然应该创建一个上下文根组件模块,即使它是空的。这可以简单地通过把一个空的<Parsley:ContextBuilder / >标签到根组件。

抱歉!评论已关闭.