2008. 8. 17. 21:51

[Flex3] Flex 컴포넌트 / Working with Flex Components

Reference : http://livedocs.adobe.com/flex/3/html/usingas_3.html#199261

Flex 어플리케이션에서 ActionScript 는 주로 어플리케이션의 시각적 컨트롤과 컨테이너를 위해 사용된다. Flex 는 이를 위한 몇 가지 기술(ActionScript 에서 Flex 컨트롤을 참조하기, 컨트롤과 컨테이너의 instantiation 동안 속성 조작하기 등)을 제공한다.

Referring to Flex components
Flex 컴포넌트 참조하기

ActionScript 에서 컴포넌트를 사용하려면, MXML 태그로 작성된 컴포넌트의 id 속성을 정의해야 한다. 예를 들어, 아래의 코드는 Button 컨트롤의 id 속성을 "myButton" 으로 정한다:

<mx:Button id="myButton" label="Click Me"/>

ActionScript 로 컴포넌트에 접근할 하지 않는다면 이 속성을 정하지 않아도 된다.

이 코드는 MXML 컴파일러가 자동으로 Button 인스턴스를 참조하는 myButton 라는 퍼블릭 변수를 생성하도록 한다. 자동으로 생성된 변수를 통해 ActionScript 에서 컴포넌트 인스턴스에 접근할 수 있다. 다른 ActionScript 클래스나 스크립트 블럭에서 Button 컨트롤의 인스턴스에 id 를 통해 접근할 수 있다. 컴포넌트 인스턴스를 참조하여 속성을 변경하거나, 메쏘드를 호출할 수 있다.

예를 들어 아래의 ActioinScript 블럭은 사용자가 버튼을 클릭할 때 Button 컨트롤의 label 속성을 변경한다:

<?xml version="1.0"?> <!-- usingas/ButtonExample.mxml --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script><![CDATA[ private function setLabel():void { if (myButton.label=="Click Me") { myButton.label = "Clicked"; } else { myButton.label = "Click Me"; } } ]]></mx:Script> <mx:Button id="myButton" label="Click Me" click="setLabel();"/> </mx:Application>



이 코드를 실행하면 아래와 같다:









MXML 컴포넌트에 있는 모든 태그의 ID는 컴포넌트의 퍼블릭 변수로 생성된다. 그래서 모든 id 속성은 문서에서 유일해야 한다. 이는 컴포넌트 인스턴스에 ID 를 명시했다면 함수나, 외부 클래스 파일, 임포트된 ActionScript 파일, 인라인 스크립트 등 어플리케이션의 어디에서도 컴포넌트에 접근할 수 있다는 것을 의미한다.

id 속성이 없는 Flex 컴포넌트는 getChildAt() 이나 getChildByName() 같은 컴포넌트 컨테이너의 메쏘드를 사용하여 참조할 수 있다.

this 키워드를 사용하여 현재 문서나 현재 오브젝트를 참조할 수 있다.

이름에 대응되는 String 이 있어도 컴포넌트로의 참조를 얻을 수 있다. To access an object on the application, you use the this keyword, followed by square brackets, with the String inside the square brackets. The result is a reference to the objects whose name matches the String.

아래는 오브젝트로의 참조를 얻기 위해 String 을 혼합하여 각 Button 컨트롤의 스타일 속성을 변경시킨다:

<?xml version="1.0"?>
<!-- usingas/FlexComponents.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script><![CDATA[
    private var newFontStyle:String;
    private var newFontSize:int;
 
    public function changeLabel(s:String):void {
        s = "myButton" + s;
       
        if (this[s].getStyle("fontStyle")=="normal") {
            newFontStyle = "italic";
            newFontSize = 18;
        } else {
            newFontStyle = "normal";
            newFontSize = 10;    
        }
    
        this[s].setStyle("fontStyle",newFontStyle);
        this[s].setStyle("fontSize",newFontSize);
    }
    ]]></mx:Script>

    <mx:Button id="myButton1"
        click="changeLabel('2')"
        label="Change Other Button's Styles"
    />
    <mx:Button id="myButton2"
        click="changeLabel('1')"
        label="Change Other Button's Styles"
    />
</mx:Application>

이 예제를 실행하면 아래의 화면처럼 보인다:

이 기술은 Repeater 컨트롤을 사용할 때 유용하다. This technique is especially useful if you use a Repeater control or when you create objects in ActionScript and do not necessarily know the names of the objects you want to refer to prior to run time. However, when you instantiate an object in ActionScript, to add that object to the properties array, you must declare the variable as public and declare it in the class's scope, not inside a function.

아래의 예제는 ActionScript 를 사용하여 두 개의 Label 컨트롤을 어플리케이션에 선언한다. 초기화 동안 레이블은 레이블 인스턴스가 만들어지고, text 속성이 결정된다. 그리고 사용자가 Button 컨트롤을 클릭하면 Label 컨트롤의 passed-in 변수를 첨부하여 레퍼런스를 얻는다:

<?xml version="1.0"?>
<!-- usingas/ASLabels.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initLabels()">
  <mx:Script><![CDATA[
  import mx.controls.Label;

  public var label1:Label;
  public var label2:Label;

  // 오브젝트는 반드시 어플리케이션 단위에서 선언되어야 한다.
  // 이름을 어플리케이션의 속성 배열에 추가하라.

  public function initLabels():void {
     label1 = new Label();
     label1.text = "Change Me";

     label2 = new Label();
     label2.text = "Change Me";
    
     addChild(label1);
     addChild(label2);
  }

  public function changeLabel(s:String):void {
     // Label 컨트롤의 이름에 매치되는 스트링 생성
     s = "label" + s;
     // 어플리케이션의 속성 배열을 사용하여 레이블 컨트롤의 레퍼런스를 받음
     this[s].text = "Changed";
  }
  ]]></mx:Script>

  <mx:Button id="b1" click="changeLabel('2')" label="Change Other Label"/>
  <mx:Button id="b2" click="changeLabel('1')" label="Change Other Label"/>
</mx:Application>

실행하면 아래와 같다:
 

Calling component methods
컴포넌트 메쏘드 호출

Flex 어플리케이션에서 .을 사용하여 컴포넌트 인스턴스의 퍼블릭 메쏘드를 호출할 수 있다:

componentInstance.method([parameters]);

아래의 예제는 사용자가 버튼을 클릭하면 HSlider 컨트롤의 퍼블릭 setThumbValueAt() 메쏘드를 호출하는 adjustThumb() 메쏘드를 호출한다:

<?xml version="1.0"?>
<!-- usingas/ComponentMethods.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script><![CDATA[
        public function adjustThumb(s:HSlider):void {
            var randomNumber:int = (Math.floor(Math.random() * 10));
            s.setThumbValueAt(0, randomNumber);
        }
    ]]></mx:Script>

    <mx:HSlider id="slider1" tickInterval="1"
        labels="[1,2,3,4,5,6,7,8,9,10]"
        width="282"
    />

    <mx:Button id="myButton"
        label="Change Thumb Position"
        click="adjustThumb(slider1);"
    />   
</mx:Application>

아래는 실행한 SWF 파일이다:

하위 문서(사용자가 만든 MXML 컴포넌트 같은 것)에서 메쏘드를 호출하기 위해, parentApplication, parentDocument, 또는 Application.application 속성을 사용할 수 있다. 더 자세한 정보는 Application Container 에서 볼 수 있다.

Note: Because Flex invokes the initialize event before drawing the component, you cannot access size and position information of that component from within the initialize event handler unless you use the creationComplete event handler. For more information on the order of initialization events, see About startup order.

Creating visual Flex components in ActionScript
ActionScript 에서 시각 Flex 컴포넌트 생성

ActionScript 클래스의 인스턴스를 생성하듯 new 연산자를 사용하여 프로그램으로 시각 Flex 컴포넌트를 생성할 수 있다. 생성된 컴포넌트의 속성에 기본값이 정해져 있지만, 아직 상위 컴포넌트나 내부 컴포넌트(모든 종류의 내부 DisplayObject)가 정해지지 않았고, Flash Player 나 Adobe® AIR™ 의 디스플레이 리스트도 등록되지 않았기 때문에 볼 수 없다. 컴포넌트 생성 후 기본값이 적절하지 않은 모든 속성은 반드시 표준 대입문을 사용해서 변경해야 한다.

마지막으로 컨테이너의 addChild() 나 addChildAt() 메쏘드를 사용하여 컨테이너에 새로운 컴포넌트를 추가해야 한다. 그래야 Flex 어플리케이션의 시각 계층의 일부가 된다. 컨테이너에 추가되면 컴포넌트의 내부 컴포넌트가 생성된다. 내부 컴포넌트는 늦게 생성되기 때문에 내부 컴포넌트에 영향을 주는 속성 변경은 내부 컴포넌트가 생성된 후에 가능하다.

시각 컴포넌트를 생성할 때, 반드시 적절한 패키지를 임포트해야 한다. 대체로 mx.controls 패키지가 이에 해당한다.

아래의 예제는 HBox 내부에 Button 컨트롤을 생성한다:

<?xml version="1.0"?>
<!-- usingas/ASVisualComponent.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script><![CDATA[
     import mx.controls.Button;
     public var button2:Button;

     public function createObject():void {
        button2 = new Button();
        button2.label = "Click Me";
        hb1.addChild(button2);
     }
  ]]></mx:Script>
  <mx:HBox id="hb1">
     <mx:Button label="Create Object" click="createObject()"/>
  </mx:HBox>
</mx:Application>

이 코드를 실행한 파일은 아래와 같다:

Flex 는 새로운 내부 컴포넌트를 컨테이너의 마지막 내부 컴포넌트로 생성한다. 새로운 내부 컴포넌트가 마지막이 아니길 원한다면 순서를 변경하기 위해 addChildAt() 메쏘드를 사용하라. the setChildIndex() 메쏘드를 addChild() 메쏘드 호출 후에 사용할 수 있지만 비효율적이다.

동적으로 생성된 컴포넌트의 인스턴스 변수를 반드시 선언해야 하고, id 속성을 정했을 때 MXML 컴파일러가 하듯, 새롭게 생성된 컴포넌트의 레퍼런스를 저장해야 한다. 그러면 MXML 에서 선언적으로 생성한 컴포넌트와 같은 방식으로 동적으로 생성한 컴포넌트에 접근할 수 있다.

컨트롤을 프로그램으로 삭제하기 위해 removeChild() 또는 removeChildAt() 메쏘드를 사용할 수 있다. 컨테이너에서 모든 하위 컨트롤을 삭제하기 위해 removeAllChildren() 메쏘드도 사용할 수 있다. 이 함수가 호출된다고 실제로 오브젝트가 삭제되지는 않는다. 내부 컴포넌트에 대한 어떤 참조도 없다면, Flash Player가 가비지 컬렉션에 포함시킨다. 하지만 어떤 객체에서 참조를 저장하고 있다면 메모리에서 삭제되지 않는다.

어떤 경우에는 MXML 태그로 컴포넌트를 선언적으로 정의한다. 컨테이너 내에서 컨트롤의 생성을 지연하기 위해 컴포넌트의 컨테이너에서 creationPolicy 속성을 none 으로 정할 수 있다. 그러면 선언되었지만 인스턴스화되지 않은 컴포넌트를 생성하기 위해 createComponentFromDescriptor() 와 createComponentsFromDescriptors() 메쏘드를 사용한다. 이 함수는 컴포넌트를 선언적이 아닌 프로그램으로 생성하게 한다. creationPolicy 사용에 관한 정보는 Improving Startup Performance 에서 볼 수 있다.

addChild() 메쏘드로 전달할 수 있는 유일한 컴포넌트는 UIComponent 다. 다시 말해 mx.core.UIComponent 를 상속받은 클래스가 아닌 새로운 객체를 생성했다면 컨테이너에 넣기 전에 UIComponent 로 한 번 포장해야 한다. 아래의 예제는 UIComponent 를 상속받지 않는 새로운 Sprite 객체를 생성하고, Panel 컨테이너에 추가하기 전에 UIComponent 의 하위 클래스로 추가한다:

<?xml version="1.0"?>
<!-- usingas/AddingChildrenAsUIComponents.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script><![CDATA[
        import flash.display.Sprite;
        import mx.core.UIComponent;

        private var xLoc:int = 20;
        private var yLoc:int = 20;
        private var circleColor:Number = 0xFFCC00;

        private function addChildToPanel():void {

            var circle:Sprite = new Sprite();
            circle.graphics.beginFill(circleColor);
            circle.graphics.drawCircle(xLoc, yLoc, 15);

            var c:UIComponent = new UIComponent();
            c.addChild(circle);
            panel1.addChild(c);
           
            xLoc = xLoc + 5;
            yLoc = yLoc + 1;
            circleColor = circleColor + 20;
        }
    ]]></mx:Script>

    <mx:Panel id="panel1" height="250" width="300" verticalScrollPolicy="off"/>

    <mx:Button id="myButton" label="Click Me" click="addChildToPanel();"/>
   
</mx:Application>

이를 실행한 SWF 파일은 아래와 같다:

About scope

Scoping in ActionScript is largely a description of what the this keyword refers to at a particular point. In your application's core MXML file, you can access the Application object by using the this keyword. In a file defining an MXML component, this is a reference to the current instance of that component.

In an ActionScript class file, the this keyword refers to the instance of that class. In the following example, the this keyword refers to an instance of myClass. Because this is implicit, you do not have to include it, but it is shown here to illustrate its meaning.

class myClass {
    var _x:Number = 3;
    function get x():Number {
        return this._x;
    }
    function set x(y:Number):void { 
        if (y > 0) {
            this._x = y;
        } else {
            this._x = 0; 
        }
    }
}

However, in custom ActionScript and MXML components or external ActionScript class files, Flex executes in the context of those objects and classes, and the this keyword refers to the current scope and not the Application object scope.

Flex includes an Application.application property that you can use to access the root application. You can also use the parentDocument property to access the next level up in the document chain of a Flex application, or the parentApplication property to access the next level up in the application chain when one Application object uses a SWFLoader component to load another Application object.

If you write ActionScript in a component's event listener, the scope is not the component but rather the application. For example, the following code changes the label of the Button control to "Clicked" once the Button control is pressed:

<?xml version="1.0"?>
<!-- usingas/ButtonScope.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Panel width="250" height="100" layout="absolute" x="65" y="24">
        <mx:Button id="myButton" 
            label="Click Me" 
            click="myButton.label='Clicked'" 
            x="79.5" 
            y="20"
        />
    </mx:Panel>
    <mx:Button label="Reset" 
        x="158" 
        y="149" 
        click="myButton.label='Click Me'"
    />    
</mx:Application>

The executing SWF file for the previous example is shown below:

Contrast the previous example with the following code:

<?xml version="1.0"?>
<!-- usingas/AppScope.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <!-- The following does nothing because the app level scope does
         not have a label to set -->
    <mx:Button id="myButton" label="Click Me" click="label='Clicked'"/>

</mx:Application>

The executing SWF file for the previous example is shown below:

This code does not work because when an event listener executes, the this keyword does not refer to the Button instance; the this keyword refers to the Application or other top-level component instance. The second example attempts to set the label property of the Application object, not the label property of the Button.

Variables declared within a function are locally scoped to that function. These variables can share the same name as variables in outer scopes, and they do not affect the outer-scoped variable. If a variable is just used temporarily by a single method, make it a local variable of that method rather than an instance variable. Use instance variables only for storing the state of an instance, because each instance variable will take up memory for the entire lifetime of the instance. You can refer to the outer-scoped variable with the this. prefix.