반응형

출처 : http://igna84.blogspot.kr/2010/03/flex-datagrid-itemrenderer-2-%EC%A0%84%EB%A9%B4%EC%A0%84.html


Flex DataGrid ItemRenderer #2 - 전면전

경력사원임에도 불구하고 나이가 어려서 그런지 신입사원 연수를 다녀오라는 지시에 지난 목요일 금요일 다녀오는 바람에 ItemRenderer 이야기가 늦어졌습니다. 기다리신 분 계신가요?? 죄송합니다. 본의아니게;;

아무튼 DataGrid ItemRenderer 라는 뜨거운 감자를 삼키기위해 어떤 모양의 ItemRenderer를 만들지 한번 생각해보도록 할까요.

 

처음이니 간단하게 CheckBox 아이템 렌더러를 만들도록 해봅시다.

 

일단 메인 어플리케이션을 만들어 놓습니다.

 

[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
    <![CDATA[
    import mx.collections.ArrayCollection;

    [Bindable]
    private var ac:ArrayCollection = new ArrayCollection([
                                     {index:1, description:"test1", toggle:"Y"},
                                     {index:2, description:"test2", toggle:"N"},
                                     {index:3, description:"test3", toggle:"N"},
                                     {index:4, description:"test4", toggle:"Y"},
                                     {index:5, description:"test5", toggle:"N"},
                                     {index:6, description:"test6", toggle:"N"},
                                     {index:7, description:"test7", toggle:"Y"},
                                     {index:8, description:"test8", toggle:"N"},
                                     {index:9, description:"test9", toggle:"N"},
                                     {index:10, description:"test10", toggle:"N"},
                                     {index:11, description:"test11", toggle:"N"},
                                     {index:12, description:"test12", toggle:"N"}
                                     ]);
    ]]>
</mx:Script>
<mx:DataGrid dataProvider="{ac}">
    <mx:columns>
        <mx:DataGridColumn headerText="index" dataField="index" />
        <mx:DataGridColumn headerText="description" dataField="description" />
        <mx:DataGridColumn headerText="check" dataField="toggle" />
    </mx:columns>
</mx:DataGrid>
</mx:Application>
[/code]

 

위와같이 코딩하고 컴파일하게되면 아래와 같이 산출물이 나오게 됩니다.

 

 

그럼 우리는 ratio라고 적혀있는 곳에 ItemRenderer를 적용할겁니다.

 

CheckBox를 상속받아 작업할겁니다. 왜 "UIComponent 안쓰고!"라고 말씀하신다면

그 이유는 UIComponent를 상속받은 클래스이기도 하고 지난시간 말했던 IDataRenderer, IDropInListitemRenderer, IListItemRenderer 이렇게 세 인터페이스가 이미 구현되어있으며 우리가 원하는 아이템 렌더러는 CheckBox아이템 렌더러이기 때문이지요.

 

[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
//  Import
//
//-----------------------------------------------------------------------------
import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.events.FlexEvent;
public class CheckBoxRenderer extends CheckBox
{
    //-----------------------------------------------------------------------------
    //
    //  Constructor
    //
    //-----------------------------------------------------------------------------
    /**
     *  Constructor.
     */
    public function CheckBoxRenderer()
    {
        super();
    }

    //-----------------------------------------------------------------------------
    //
    //  Variables
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     *  데이터 변경 확인 플래그
     */
    private var dataChanged:Boolean = false;

    //-----------------------------------------------------------------------------
    //
    //  Override Methods
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     */
    override protected function createChildren():void
    {
        super.createChildren();
    }

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
    }
}
}
[/code]

초벌로 createChildren, commitProperties, updateDisplayList도 확장해 두고 아이템 랜더러로 사용하기 위한 준비를 마쳤습니다.

그리고 메인 어플리케이션은 아래와 같이 수정하고 컴파일 합니다.

[code xml]
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
    <![CDATA[
    import mx.collections.ArrayCollection;

    [Bindable]
    private var ac:ArrayCollection = new ArrayCollection([
                                        {index:1, description:"test1", toggle:"Y"},
                                        {index:2, description:"test2", toggle:"N"},
                                        {index:3, description:"test3", toggle:"N"},
                                        {index:4, description:"test4", toggle:"Y"},
                                        {index:5, description:"test5", toggle:"N"},
                                        {index:6, description:"test6", toggle:"N"},
                                        {index:7, description:"test7", toggle:"Y"},
                                        {index:8, description:"test8", toggle:"N"},
                                        {index:9, description:"test9", toggle:"N"},
                                        {index:10, description:"test10", toggle:"N"},
                                        {index:11, description:"test11", toggle:"N"},
                                        {index:12, description:"test12", toggle:"N"}
                                        ]);
    ]]>
</mx:Script>
<mx:DataGrid dataProvider="{ac}">
    <mx:columns>
        <mx:DataGridColumn headerText="index" dataField="index" />
        <mx:DataGridColumn headerText="description" dataField="description" />
        <mx:DataGridColumn headerText="radio" dataField="toggle" itemRenderer="classes.controls.renderers.CheckBoxRenderer" />
    </mx:columns>
</mx:DataGrid>
</mx:Application>
[/code]

일단 아래와 같은 결과가 나오면 성공입니다.

 

CheckBox 가 "Y"냐 "N"이냐에 따라 selected가 처리되는 로직이 필요합니다. 그러기 위해서는 그 코드를 어디에 넣는것이 좋을지 고민해봐야하는데 아까도 말했듯 모든 속성처리는 commitProperties에서 하게 됩니다. commitProperties를 호출하기 위해서는 저번에 이야기 했었던 대로 invalidateProperties() 메서드를 호출하면 됩니다. 그렇다면 코드를 아래와 같이 수정합니다.

 

[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
//  Import
//
//-----------------------------------------------------------------------------
import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.events.FlexEvent;
public class CheckBoxRenderer extends CheckBox
{
    //-----------------------------------------------------------------------------
    //
    //  Constructor
    //
    //-----------------------------------------------------------------------------
    /**
     *  Constructor.
     */
    public function CheckBoxRenderer()
    {
        super();
    }

    //-----------------------------------------------------------------------------
    //
    //  Variables
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     *  데이터 변경 확인 플래그
     */
    private var dataChanged:Boolean = false;
    //-----------------------------------------------------------------------------
    //
    //  Override Properties
    //
    //-----------------------------------------------------------------------------
    override public function set data(value:Object):void
    {
        super.data = value;

        dataChanged = true;

        invalidateProperties();
    }

    //-----------------------------------------------------------------------------
    //
    //  Override Methods
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     */
    override protected function createChildren():void
    {
        super.createChildren();
    }

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
        if(dataChanged)
        {
            dataChanged = false;
            updateProperties();
        }
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
    }

    //-----------------------------------------------------------------------------
    //
    //  Methods
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     *  CheckBox의 속성을 변경
     */
    private function updateProperties():void
    {
        if(listData.label == "Y")
        {
            selected = true;
        }
        else
        {
            selected = false;
        }

        invalidateDisplayList();
    }
}
}
[/code]

위와 같이 코딩하게 되면 아래와 같은 산출물이 나오게 됩니다.

 

 

썩 잘나오는거 같습니다. 몇가지 마음에 안 드는 것이 있는데 일단 CheckBox가 셀의 중앙에 갔으면 좋겠고 CheckBox를 클릭하고 휠을 돌려보면 Check가 풀려버리는 버그가 있다는 것이지요.

 

CheckBox를 중앙에 놓으려면 CheckBox의 너비를 알아야하는데 그것은 CheckBox는 기본적으로 14픽셀의 넓이를 갖습니다. 그것을 어떻게 아냐구요?

measuredWidth를 trace 해보면 간단하게 알 수 있습니다. 아, measuredWidth는 컨포넌트 객체의 기본 너비를 이야기 합니다.

 

위의 두가지 문제점을 해결하기 위해 아래와 같이 코드를 수정합니다.

 

[code as3]
package classes.controls.renderers
{
//-----------------------------------------------------------------------------
//
//  Import
//
//-----------------------------------------------------------------------------
import flash.events.MouseEvent;

import mx.core.IDataRenderer;
import mx.controls.CheckBox;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.mx_internal;
import mx.events.FlexEvent;

use namespace mx_internal;

public class CheckBoxRenderer extends CheckBox
{
    //-----------------------------------------------------------------------------
    //
    //  Constructor
    //
    //-----------------------------------------------------------------------------
    /**
     *  Constructor.
     */
    public function CheckBoxRenderer()
    {
        super();
    }

    //-----------------------------------------------------------------------------
    //
    //  Variables
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     *  데이터 변경 확인 플래그
     */
    private var dataChanged:Boolean = false;

    //-----------------------------------------------------------------------------
    //
    //  Override Properties
    //
    //-----------------------------------------------------------------------------
    override public function set data(value:Object):void
    {
        super.data = value;

        dataChanged = true;

        invalidateProperties();
    }

    //-----------------------------------------------------------------------------
    //
    //  Override Methods
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     */
    override protected function createChildren():void
    {
        super.createChildren();
    }

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();
        if(dataChanged)
        {
            dataChanged = false;

            updateProperties();
        }
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);

        currentIcon.x = (unscaledWidth - currentIcon.width) / 2;
    }

    //-----------------------------------------------------------------------------
    //
    //  Methods
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     *  CheckBox의 속성을 변경
     */
    private function updateProperties():void
    {
        if(listData.label == "Y")
        {
            selected = true;
        }
        else
        {
            selected = false;
        }

        invalidateDisplayList();
    }

    //-----------------------------------------------------------------------------
    //
    //  Override EventHandler
    //
    //-----------------------------------------------------------------------------
    /**
     *  @private
     */
    override protected function clickHandler(event:MouseEvent):void
    {
        super.clickHandler(event);

        //data[DataGridListData(listData).dataField] = selected ? "Y" : "N";

        if(selected)
        {
            data[DataGridListData(listData).dataField] = "Y";
        }
        else
        {
            data[DataGridListData(listData).dataField] = "N";
        }
    }
}
}
[/code]

 

currentIcon이라는 생소한 속성을 위에서 보실 수 있습니다. 그것은 체크박스에서 체크되는 아이콘을 지칭합니다. 그 currentIcon이란 녀석은 mx_internal이라는 접근자를 이용하고 있고 LanguageReference에는 표시되지 않고 있어서 그 존재를 모르는 사람이 많습니다.

예전에 저도 그것을 몰라서 HBox에다가 넣고 horizontalAlign="center"를 주는 바보같은 짓을 일삼았는데 이 사실을 알고 난뒤에 Container를 사용하지 않고 좀더 가볍게 itemRenderer를 만들어 낼 수 있었지요.


그래서 updateDisplayList() 메서드에서 좌표를 잡아서 셀의 중앙에 놓고 clickHandler라는 메서드를 확장하여 변경된 값을 data에 반영하게 합니다. 그렇게 되면 아래와 같은 결과물이 나오게 되지요.

 

 

오. 그럴싸하게 작동하는 것 같습니다만 몇가지 아쉬운점이 있습니다.

 

1. "Y", "N" 값으로만 작동하게 되어있다.

2. CheckBox를 클릭하면 뭔가 다른 행동을 하고 싶을땐 어떻게 하나.

 

이렇게 두가지 사항인데요.

이 부분에 대해서는 다음시간에 알아보도록 해요~


Posted by 1010