views:

3227

answers:

3

Let us say that I have a Flex 3 mxml component, call it A. A has a get/set attribute called 'b'. Within A I have another internal component C, which is specified using mxml. When "instantiating" component A within mxml, I can specify the value of b at declaration, and everything works fine. However, when I initialize the component using Actionscript, I must first add the component to a rendered container before I can set the attribute (in this case 'b') of said component. This happens when the setter for attribute 'b' somehow accesses C within A.

So, this fails at runtime (it says that C is null)...

var a:A = new A();
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"
this.addChild(a);

On the other hand, either of the following will work

<customNamespace:A b="woopy"/>

or

var a:A = new A();
this.addChild(a);
a.b = "woopy"; //Sets the Label (declared in mxml) withn A to "woopy"

As shown, no runtime error message is thrown when a attribute is set after a component is added to a container. Ok, this makes sense, I suppose the internals of the component are not actually created until the component is added to a container. Still, this is kind of annoying. Is there any way to guarantee that the component internals are fully rendered without adding it to a container? I don't like the way it feels different when I am using actionscript vs mxml. I want a solution so that basically declaring A in mxml with no attribute "arguments" is equivalent to declaring A using the new operator in AS. At least, in terms of the internal state of A.

+5  A: 

To force a control to create its child controls you have to call the initialize method.

i.e. this should work :

var a:A = new A();
a.initialize();
a.b = "woopy";
this.addChild(a);

However, what I've been doing so far when declaring mxml controls is binding the internal controls to public variables declared in a script block. e.g.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"&gt;
    <mx:Script>
     <![CDATA[
      [Bindable]
      public var labelText:String = "[Default]";
     ]]>
    </mx:Script>
    <mx:Label text="{labelText}"/>
</mx:Canvas>

This way you can set your parameters without having to worry about whether the controls have been created or not.

inferis
+4  A: 

That's right -- if B's setter acts on C, you'll have problems, because when A's constructed, C's definitely not there yet, even if you've declared C in A's MXML.

About there being any way to guarantee a component and its children are fully rendered and usable without adding it to a container, the answer is no -- the framework won't perform its creation and rendering magic on a component until it's somehow added to the display list, either by way of MXML or addChild().

Of course, you could use the visible or includeInLayout properties (setting both to false on your A component, for example) to get around actually displaying the component, or if you had to do the instantiation in script, you could listen for either A's initialize or creationComplete events (both of which indicate A's children have been created and are ready to be acted upon), and just wait to set B until you receive that notification. As a general rule, though, I wouldn't advise calling the initialize() method directly; it's a framework method that gets called automatically just after addChild() anyway, and in general it's better to let the framework do its thing, rather than work around it.

var a:A = new A();
a.addEventListener(FlexEvent.INITIALIZE, a_initialize);
addChild(a);

private function a_initialize(event:FlexEvent):void
{
    a.b = "woopy";
    // ... and so on
}

So it goes, if you want to be sure.

Deepa Subramaniam, an engineer on the Flex team, recently posted an excellent video on her site covering the Flex component model in granular, step-by-step detail; I attended the talk at MAX where she recorded it, it was easily one of the best of the conference. Worth watching (and re-watching, and then watching again) for its detail and comprehensiveness. She actually addresses your question at several times during the talk. It's great stuff.

Best of luck!

Christian Nunciato
+2  A: 

To answer your main question, no, you don't have to add an AS3-instantiated component to the display list if you want to set its properties. There's no difference between creating it in MXML versus creating it in AS3... unless, of course, the component wasn't built properly.

The Flex team at Adobe (formerly Macromedia) spent many years refining optimizations for the Flex component architecture. There are two important parts of that design that are related to your problem:

  1. They designed system of invalidation and validation so that you can set many properties at once, but the effects of the changes don't happen until you're done making all your changes.

  2. When a component is first instantiated, it's children are not created right away. There's an optimal time to do it, and that's after the component has been added to the display list.

Basically, when you have a difference in behavior between an MXML instantiated component and an AS3 instantiated component, it's because the component was built without these two features in mind.

The component that is behaving improperly probably does something like this:

private var label:Label;

public function get b():String
{
    return this.label.text;
}

public function set b(value:String):void
{
    this.label.text = value;
}

The problem is that the component developer didn't take into account that the Label sub-component may not have been created yet! The best practice is to save the value in a variable and invalidate to pass it to the sub-component later (the validation cycle doesn't happen until after the component is initialized and the children are created).

private var label:Label;

private var _b:String;

public function get b():String
{
    return this._b;
}

public function set b(value:String):void
{
    this._b = value;
    this.invalidateProperties();
}

override protected function commitProperties():void
{
    super.commitProperties();
    this.label.text = this._b;
}

Alternatively, if you build an MXML component, you can do something similar, but it's often easier to use binding instead of the validation system:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"&gt;
    <mx:Label text="{this.b}"/>
    <mx:Script><![CDATA[

    private var _b:String;

    [Bindable]
    public function get b():String
    {
        return this._b;
    }

    public function set b(value:String):void
    {
        this._b = value;
    }

    ]]></mx:Script>
</mx:Application>
joshtynjala