본문 바로가기

Blog/WYSIWYG Tool 개발

코드 생성기(1) - 이벤트 코드 생성 방법

MXML 구문에서 이벤트 처리 방식

MXML에서 이벤트 설정은 다음과 같은 형식으로 삽입된다. 먼저 호출될 메서드를 태그안에 정의하고,

  <mx:Script>
    <![CDATA[
        
import mx.events.Event;
        
private function treeChange (event:Event):void
        {
            // 이벤트 처리 구문
        }
    ]]>
</mx:Script>

인스턴스의 이벤트 속성 설정에서 메서드를 호출하는 코드를 삽입한다.

 <mx:Tree id="treeComp" change="treeChange(event);" />

여기까지는 사용자가 작성해주는 부분이고, 다음으로 Flex builder는 다음 작업을 자동으로 처리해 준다. 컴파일이 실행되기 전 Flex Builder 는 해당 Application 클래스를 동적으로 생성하고, 그 클래스에는 다음과 같은 메서드가 자동으로 생성되어 추가된다.

 public function __treeComp_change(event : mx.events.ListEvent):void
{
    // 이 부분은 MXML의 이벤트 Attribute 부분(위에서 @change)의 내용이 복사된 것임.
    treeChanged(event);
}

<mx:Script> 노드의 스크립트 내용이 클래스 내에 그대로 복사 된다. 그리고 Tree의 선택 항목이 바뀌면(change 이벤트 발생) __treeComp_change() 메서드가 호출되는 구조이다. 참고로 자동 생성된 클래스의 _documentDescriptor_ 에는 다음과 같이 treeComp 인스턴스를 정의하고 있다.

new mx.core.UIComponentDescriptor(
    {
        type: mx.controls.Tree,
        id: "treeComp",
        events: {
            change: "__treeComp_change"
        },

        stylesFactory: function():void {
            this.bottom = "40"; this.top = "0"; this.left = "0"; this.right = "0";
        },

        propertiesFactory: function():Object {
            return {labelField: "@label", showRoot: true    }
        }
    }
)

addEventListener에 의해 추가된 이벤트는 어차피 코드의 흐름 속에 포함되어 있고 초기화와는 관련이 없기 때문에 여기서는 신경 쓰지 않아도 된다. MXML 구문으로 태그 내에 이벤트로 설정된(Attribute) 부분이 어떻게 <mx:Script> 노드의 AS3.0 코드와 내부적으로 연결되어 호출되는지 살펴보기만 하면 된다.

코드 생성기에서 이벤트 처리 방법 연구

이벤트 코드 생성기를 통해 얻고자 하는 목표는 결국 다음과 같은 형식의 MXML 코드를 자동으로 생성할 수 있도록 도와주는 유틸을 만드는 것이다.

생성 코드 패턴 :

// 이벤트 호출 부분
<mx:Tree id="treeComp" change="_change_id_ (event);" />

// 이벤트 정의 부분
<mx:Script>
    <![CDATA[
        import mx.events.Event;
        // 이벤트 코드 정의 부분
        private function _change_id_ (event:Event):void
        {
            // 인스턴스의 API 호출 1
            // 인스턴스의 API 호출 2
            // 인스턴스의 API 호출 3
}
    ]]>
</mx:Script>

한가지 더 생각해야 할 부분은 <mx:Script>의 각 메서드와 인스턴스 노드의 이벤트 설정 Attribute이 서로 참조 가능하도록 하는 정보를 담고 있어야 한다. 왜냐하면 이미 설정된 이벤트 내용을 사용자에게 보여줄 때 이벤트에 대하여 작성된 코드(메서드)를 파싱하여 인스턴스의 API 호출 1, 2, 3 구문을 알아내고 이를 리스트화 하여 보여주어야 하는데, 이때 서로 연관된 관계를 매칭시켜 줘야 하는 부분이 필요하기 때문이다. 따라서 위와 같은 결과를 내기 위해선 메서드 이름을 인스턴스 아이디와 이벤트 네임의 조합으로 구성하여 이런 부분을 해결할 수 있다. 또한 실제 메서드 호출 부분과 메서드 정의 부분이 따로 분리되어 있기 때문에 다음과 같은 사항도 주의 깊게 살펴보아야 한다.

  • 인스턴스의 ID 또는 이벤트를 바꾸면 메서드를 정의하는 이름도 함께 업데이트 되어야 한다.
  • 수동으로 코드를 편집하는 상황을 고려한다. (너무 많은 예외사항이 발생될수 있으므로 여기에서는 수동편집 기능은 제외한다.)
  • API가 추가되거나 삭제될 때 코드 정의부분을 찾아 편집한다.
  • API가 삭제되어 이벤트가 비게 될 때 Attribute과 함께 해당 메서드도 함께 삭제한다.
  • 인스턴스가 삭제될 때 관련 코드도 함께 삭제한다.

 

1.메서드의 리스트로 작성하는 방식

 아마 undo, redo 상황까지 발생한다면 더욱 복잡해 질 수도 있겠다. 그래서 생성될 코드의 패턴을 인라인 방식으로 다음과 같이 변경하기로 한다.
<mx:Tree id="treeComp" change="인스턴스의 API 호출 1; 인스턴스의 API 호출 2; 인스턴스의 API 호출 3;" />

이렇게 생성할 구문의 형식을 바꾸면 아마 다음과 같은 이득이 있을 것 같다.

  • 각 인스턴스의 API호출은 인스턴스 노드와 묶여있게 되므로 별도의 매칭 작업이 사라진다.
  • ";" 를 구분자로 사용하여 호출되는 API 리스트 관리 및 호출 순서 변경 등의 작업이 편리해진다.
  • API 호출을 위한 코드관리 및 인스턴스 노드의 편집과 이벤트 코드의 편집작업이 분리되어 있지 않다. 즉 인스턴스가 삭제되면 자연스럽게 이벤트 내용도 함께 삭제된다.

 

다만 결과 코드에 대한 가시성이 좀 떨어지겠지만 얻는 이득에 비해 충분히 감수할 수 있을 것 같다. 그러면 여기에 API 호출을 위한 유효성 검사 코드를 넣어 생성될 코드의 패턴을 구성해 보면 다음과 같다.

생성 코드 패턴 :

<mx:Tree id="treeComp" change="
callAPIValidator(event, 인스턴스, "API", [parameter]…);
callAPIValidator(event, 인스턴스, "API", [parameter]…);
callAPIValidator(event, 인스턴스, "API", [parameter]…);
" />

// 고정적으로 삽입될 스크립트 코드

<mx:Script>

    <![CDATA[

import mx.events.Event;
// 유효성 검사 후 API 호출
private function callAPIValidator (event:Event, apiOwner:UIComponent, method:String, ..args):void
{
    if(!apiOwner) return;
    try{
        apiOwner[method].call(apiOwner, args)
    }
catch(error:Error){
        
trace("[CALL API ERROR]", event.type, apiOwner, method);
        
trace(method, "(", args, ")");
        
trace(error.message);
    }
finally{
    }
}

]]>
</mx:Script>

이제 우리는 ( change="인스턴스의 API 호출 1; 인스턴스의 API 호출 2; 인스턴스의 API 호출 3;" ) 이 부분에만 집중할 수 있게 되었다. 나머지는 서두에 언급했듯이 컴파일러가 클래스를 생성하여 이벤트 호출 메서드를 자동으로 어쩌구 저쩌구… 알아서 처리해 줄 것이다.

 

2.사용자가 작성한 코드를 함께 삽입하는 방식

 AS3는 중첩함수를 허용하므로 다음 같은 형식으로 이벤트를 설정 가능 하다.

 <mx:Button label="-" width="50"
    click="
    var a:Function =
function():void{
        
trace('aaa');
    };
ß 세미콜론 반드시 붙여 주어야 함
    a();
ß 반드시 function 정의 뒤에 호출
    "
/>


 <mx:Button label="-" width="50"
    click="
    a();
ß 호출 위치 상관 없음
    function a():void{
        
trace('aaa');
    }
ß 세미콜론 없어도 됨
    "
/>

물론 function 코드 내에는 아이디로 객체를 호출하는 등의 일반 함수와 같이 작성하면 된다. 이를 적절히 응용하면 사용자가 직접 작성한 코드를 적용(호출)할 수 있는 편집기도 제작이 가능할 것 같다. 위 코드에서 왜 세미콜론이 유독 중요시 되는가는 다음 코드를 보면 알 수 있다. 이벤트 Attribute의 내용이 클래스에 삽입될 때 줄 바꿈 없이 한 줄로 복사되어 들어가므로 세미콜론이 꼭 필요하게 된다. 마지막 샘플 코드가 컴파일 과정을 통해 클래스에 삽입되는 모습은 다음과 같다. 만약 세미콜론이 없다면 컴파일러는 "a()" 메서드 호출 부분와 "function" 키워드를 파싱 하지 못하고 에러를 발생시킬 것이다.

public function ___EventEditor_Button4_click(event:flash.events.MouseEvent):void
{
    a();
function a():void{ trace('aaa'); };
}

이와 같은 방식으로 이벤트를 설정하면 사용자가 직접 작성한 코드를 사용할 수 있다는 장점이 생긴다. 대신 코드 작성은 숙련된 사용자에 의해 엄격히 체크되어야 하며 다양한 예외사항(에러)에 대해 대처할 수 있는 방법도 생각해 보아야 한다.