어떤 분이 Flex 3.4환경에서 MenuBar에 색을 입히려하다가 잘 안되서 본인의 블로그에 질문을 해주셨다.


 <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:Style>
		MenuBar { 
			font-weight:bold; 
			color:#FFFFFF;
			fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
			fill-alphas:1.0,1.0;
		}
		MenuBarItem {
			theme-color:#0000FF;
		}
		Menu { 
			alternatingItemColors:#ffffff,#eff4fa;
			color:#000000;
			top:4px;
			bottom:2px;
			horizontalGap:2px;	
			roll-over-color:#00ff00;
		}
	</mx:Style>
	<mx:MenuBar id="myMenuBar" labelField="@label" >
		<mx:XMLList>
			<menuitem label="MenuItem A">
				<menuitem label="SubMenuItem A-1" enabled="false"/>
				<menuitem label="SubMenuItem A-2"/>
			</menuitem>
			<menuitem label="MenuItem B"/>
			<menuitem label="MenuItem C"/>
			<menuitem label="MenuItem D">
				<menuitem label="SubMenuItem D-1" 
						  type="radio" groupName="one"/>
				<menuitem label="SubMenuItem D-2" 
						  type="radio" groupName="one"
						  selected="true"/>
				<menuitem label="SubMenuItem D-3" 
						  type="radio" groupName="one"/>
			</menuitem>
		</mx:XMLList>
	</mx:MenuBar>
</mx:Application>



질문은 MenuBarItem의 themeColor를 #0000FF로 지정했는데 저렇게 나오냐는 것이였다. 참고로 MenuBar는 저 파란색의 바를 말하는 것이고 MenuBarItem은 MenuItem A, MenuItem B...등으로 표시된 것이다. 이들은 모두 UIComponent를 상속했다. 마지막으로 Menu는 서버메뉴로써 List를 확장한 것이다.

질문처럼 직관적으로 봐도 문제가 있어 보인다. 이렇게 나오는 이유를 알기 위해서는 스킨부분을 찾아봐야 한다.

(아래내용이 약간 복잡할 수 있으나 천천히 따라오면 그리 복잡한 것도 아니다. 원래 이런걸 말로 설명해야 빠른 건데 글로 설명하려 하니 어렵다. ^^;)


원인 찾기

MenuBar의 아이템렌더러는 menuBarItemRenderer속성에 지정된다. 기본적으로 MenuBarItem 클래스가 된다. 쉽게 이야기해 위 그림에서 MenuItem A, MenuItem B... 등은 다 MenuBarItem으로 생성되어 그려진 것이다. 만약 MenuBarItem을 다른 것으로 대체하고 싶다면 IMenuBarItemRenderer 인터페이스를 구현해서 다른 MenuBarItem을 만들면 되겠다. 아래는 MenuBar의 menuBarItemRenderer 속성을 설명하고 있다.

(문제의 원인을 파악하기 위해 가장 먼저 찾아야 할 것은 API 문서이다.)



MenuBarItem의 스킨은 무엇일까? 아래 MenuBar에서 Style부분을 보자. MenubarItem의 스킨은 바로 ActivatorSkin 클래스라는 것을 알 수 있다. 결국 스킨도 내 마음대로 바꿀 수 있는 것이다.

Flex 3.4에서 스킨은 크게 그래픽기반 스킨과 프로그램적 스킨(Programmatic Skin)이 있다. 그래픽기반 스킨은 이미지를 생각하면 된다. 하지만 프로그램적 스킨은 ActionScript 3.0 코드이다. MenuBar의 itemSkin 스타일 속성으로 mx.skins.halo.ActivatorSkin을 썼다는 것은 바로 프로그램적 스킨을 사용한다는 이야기이다.

(문서를 봐도 해결의 실마리가 안보인다면 SDK를 뜯어보자!)

그럼 이들이 어떻게 동작하는 것일까? MenuBar부터 시작해 MenuBarItem, ActivatorSkin으로 자연스럽게 찾아보면 된다. Flex 3.4 SDK 소스는 공개되어 있으므로 아래 그림처럼 Flash builder 4에서 쉽게 찾아볼 수 있다. Flex Builder 3를 사용한다면 이 기능은 사용할 수 없다.

Flex SDK 클래스를 보려면 framework.swc를 펼쳐보면 된다.



MenuBar 소스에서 mouseOverHandler, mouseUpHandler, mouseDownHandler 메소드들을 보자. 이들 메소드는 마우스 이벤트를 처리하는데 잘 보면 이런 코드들이 있다.

item.menuBarItemState = "itemDownSkin";


여기서 item은 IMenuBarItemRenderer 인터페이스의 참조이다. 그리고 MenuBarItem 클래스는 이 인터페이스를 구현한 것이다. 결국 MenuBarItem의 menuBarItemState 속성에 itemDownSkin이라고 지정한 것이다.

그럼 MenuBarItem 소스의 menuBarItemState 부분을 보자.


    public function set menuBarItemState(value:String):void
    {
        _menuBarItemState = value;
        viewSkin(_menuBarItemState);
    }   

    private function viewSkin(state:String):void
    {  	
    	var newSkinClass:Class = Class(getStyle(state));
    	var newSkin:IFlexDisplayObject;
    	var stateName:String = "";
    	    	
    	if (!newSkinClass)
    	{
    		// Try the default skin
    		newSkinClass = Class(getStyle(skinName)); 		
    		    	
    		if (state == "itemDownSkin")
    			stateName = "down";
    		else if (state == "itemOverSkin")
    			stateName = "over";
    		else if (state == "itemUpSkin")
    			stateName = "up";
    		
    		// If we are using the default skin, then 
    		if (defaultSkinUsesStates)
    			state = skinName;
    		
     		if (!checkedDefaultSkin && newSkinClass)
    		{
	    		newSkin = IFlexDisplayObject(new newSkinClass());
	    		// Check if the skin class is a state client or a programmatic skin
	    		if (!(newSkin is IProgrammaticSkin) && newSkin is IStateClient)
	    		{
	    			defaultSkinUsesStates = true;
	    			state = skinName;
	    		}
	    		
	    		if (newSkin)
	    		{
		    		checkedDefaultSkin = true;
		    	}
    		}
    	}
    	
      	newSkin = IFlexDisplayObject(getChildByName(state));

        if (!newSkin)
        {
            if (newSkinClass)
            {
                newSkin = new newSkinClass();

                DisplayObject(newSkin).name = state;

                if (newSkin is ISimpleStyleClient)
                    ISimpleStyleClient(newSkin).styleName = this;

                addChildAt(DisplayObject(newSkin), 0);
            }
        }

        newSkin.setActualSize(unscaledWidth, unscaledHeight);

        if (currentSkin)
            currentSkin.visible = false;

        if (newSkin)
            newSkin.visible = true;

        currentSkin = newSkin;
        
        // Update the state of the skin if it accepts states and it implements the IStateClient interface.
		if (defaultSkinUsesStates && currentSkin is IStateClient)
		{
			IStateClient(currentSkin).currentState = stateName;
		}
    }

MenuBarItem의 menuBarItemState에 상태변화 값을 넣어주면 MenuBarItem의 viewSkin() 메소드에서 각 상태값에 따라서 스킨을 만들어준다. viewSkin 메소드에서 newSkin = IFlexDisplayObject(new newSkinClass()); 은 새로운 스킨을 만드는 부분이 newSkinClass가 바로 ActivatorSkin 클래스가 된다. 그리고 newSkin = IFlexDisplayObject(getChildByName(state));  부분은 이전에 만든 스킨이 있는지 확인하는 부분이다. viewSkin() 메소드는 그래픽적 스킨, 프로그램적 스킨등에 모두 대응되며 게으른(lazy) 스킨 생성을 한다. 여기서 게으른 생성이라는 것은 가령, 애플리케이션이 실행 당시에 마우스 Over스킨을 생성하는 것이 아니라 마우스Over시에 스킨은 바로 그때 생성해준다는 것이다. viewSkin() 메소드가 결국 ActivatorSkin 클래스로 하여금 스킨을 만들어주는 것이다.

ActivatorSkin 클래스의 일부분을 보자.

	private static function calcDerivedStyles(themeColor:uint,  fillColor0:uint, fillColor1:uint):Object
	{
		var key:String = HaloColors.getCacheKey(themeColor,fillColor0, fillColor1);
				
		if (!cache[key])
		{
			var o:Object = cache[key] = {};
			
			// Cross-component styles.
			HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1);
		}
		
		return cache[key];
	}

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

		if (!getStyle("translucent"))
			drawHaloRect(w, h);
		else
			drawTranslucentHaloRect(w, h);
	}

	/**
	 *  @private
	 */
	private function drawHaloRect(w:Number, h:Number):void
	{
		var fillAlphas:Array = getStyle("fillAlphas");
		var fillColors:Array = getStyle("fillColors");
		var highlightAlphas:Array = getStyle("highlightAlphas");				
		var themeColor:uint = getStyle("themeColor");

		var themeColorDrk1:Number =
			ColorUtil.adjustBrightness2(themeColor, -25);

		// Derivative styles.
		var derStyles:Object = calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);
												 
		graphics.clear();

		switch (name)
		{
			case "itemUpSkin": // up/disabled
			{
				// invisible hit area
				drawRoundRect(
					x, y, w, h, 0,
					0xFFFFFF, 0);
				break;
			}

			case "itemDownSkin":
			{
				// face
				drawRoundRect(
					x + 1, y + 1, w - 2, h - 2, 0,
					[ derStyles.fillColorPress1, derStyles.fillColorPress2], 1,
					verticalGradientMatrix(0, 0, w, h )); 
									
				// highlight
				drawRoundRect(
					x + 1, y + 1, w - 2, h - 2 / 2, 0,
					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
					verticalGradientMatrix(0, 0, w - 2, h - 2));

				break;
			}

			case "itemOverSkin":
			{
				var overFillColors:Array;
				if (fillColors.length > 2)
					overFillColors = [ fillColors[2], fillColors[3] ];
				else
					overFillColors = [ fillColors[0], fillColors[1] ];

				var overFillAlphas:Array;
				if (fillAlphas.length > 2)
					overFillAlphas = [ fillAlphas[2], fillAlphas[3] ];
  				else
					overFillAlphas = [ fillAlphas[0], fillAlphas[1] ];

				// face
				drawRoundRect(
					x + 1, y + 1, w - 2, h - 2, 0,
					overFillColors, overFillAlphas,
					verticalGradientMatrix(0, 0, w, h )); 

				// highlight
				drawRoundRect(
					x + 1, y + 1, w - 2, h - 2 / 2, 0,
					[ 0xFFFFFF, 0xFFFFFF ], highlightAlphas,
					verticalGradientMatrix(0, 0, w - 2, h - 2));
				
				break;
			}
		}

		filters = [ new BlurFilter(2, 0) ];
	}


그림을 그릴때 drawHaloRect() 메소드를 호출해서 각 상태값에 따라서 그림을 그려준다. 우리가 처음 themeColor를 지정했음에도 불구하고 다른 색으로 보였던 부분을 알기 위해서는 drawHaloRect()에 switch분기점에서 "itemDownSkin"을 보면 된다. derStyles.fillColorPress1, derStyles.fillColorPress2 색을 사용하는 것을 볼 수 있으며 코드에서 derStyles는 calcDerivedStyles(themeColor, fillColors[0], fillColors[1]);에 의해 만들어진다. calcDerivedStyles() 메소드를 보면 결국 HaloColors.addHaloColors(o, themeColor, fillColor0, fillColor1); 으로 색이 결정됨을 볼 수있다. 여기서 HaloColors 클래스내에서 addHaloColors() 정적 메소드를 보면 다음과 같다.

public static function addHaloColors(colors:Object,  themeColor:uint,  fillColor0:uint, fillColor1:uint):void
{
	var key:String = getCacheKey(themeColor, fillColor0, fillColor1); 
	var o:Object = cache[key];
	
	if (!o)
	{
		o = cache[key] = {};
		
		// Cross-component styles
		o.themeColLgt = ColorUtil.adjustBrightness(themeColor, 100);
		o.themeColDrk1 = ColorUtil.adjustBrightness(themeColor, -75);
		o.themeColDrk2 = ColorUtil.adjustBrightness(themeColor, -25);
		o.fillColorBright1 = ColorUtil.adjustBrightness2(fillColor0, 15);
		o.fillColorBright2 = ColorUtil.adjustBrightness2(fillColor1, 15);
		o.fillColorPress1 = ColorUtil.adjustBrightness2(themeColor, 85);
		o.fillColorPress2 = ColorUtil.adjustBrightness2(themeColor, 60);
		o.bevelHighlight1 = ColorUtil.adjustBrightness2(fillColor0, 40);
		o.bevelHighlight2 = ColorUtil.adjustBrightness2(fillColor1, 40);
	}
	
	colors.themeColLgt = o.themeColLgt;
	colors.themeColDrk1 = o.themeColDrk1;
	colors.themeColDrk2 = o.themeColDrk2;
	colors.fillColorBright1 = o.fillColorBright1;
	colors.fillColorBright2 = o.fillColorBright2;
	colors.fillColorPress1 = o.fillColorPress1;
	colors.fillColorPress2 = o.fillColorPress2;
	colors.bevelHighlight1 = o.bevelHighlight1;
	colors.bevelHighlight2 = o.bevelHighlight2;
}

위에서 우리가 주목할 점은 o.fillColorPress1와 o.fillColorPress2를 설정할 때 ColorUtil.adjustBrightness2(themeColor, 85);와 같은 방법으로 새로 색을 설정하는 부분이다.

결국 무엇인가? 지금까지 MenuBar, MenuBarItem, ActivatorSkin 클래스를 쭉 따라가며 themaColor 스타일값이 원하는대로 보여지지 않은 것은 바로 HaloColors 클래스의 addHaloColors() 부분에서 찾을 수 있는 것이다.


해결하기

위처럼 themeColor가 원하는대로 적용되지 않은 것 처럼 보인 이유를 분명히 알았다. Flex 3.4가 매우 잘 만들어졌다고 할지라도 이것도 결국 사람이 만든거라 완벽히 원하는대로 기능을 쓸 수 없는 경우가 생긴다. 그러면서 발생하는 문제를 해결하기 위해 단지 피상적으로 문서만 의지해서 해결하려고 하면 절대 답을 못찾을 수 있다. 표면상 문제를 해결할 수 없다면 문제의 부분을 찾기위해 SDK 소스를 위와 같이 찾아보는 스킬을 길러야 한다.
 
어떤 문제인지 파악했으니 이제 해결해보자. 일단 기본적으로 CSS로 themeColor 와 같은 설정값으로 해결못하는 것을 알았으니 MenuBarItem에 새로운 ActivatorSkin 와 비슷한 MyActivatorSkin 을 만들고 더불어 HaloColors도 동일하게 MyHaloColors를 만들자. 이들은 기존 ActivatorSkin.as와 HaloColors.as 소스를 복사해서 만들면 된다. 물론 package부분 설정 및 include부분 설정으로 인한 에러는 제거해주자.

MyHaloColors 클래스내에 o.fillColorPress1, o.fillColorPress2에서 ColorUtil.adjustBrightness2(themeColor, 85); 처럼 된 것을 ColorUtil.adjustBrightness2(themeColor, 0);으로 바꾸자.

그런 다음 메인소스에 CSS에서 MenuBar 셀렉터에 itemSkin을 다음처럼 추가한다.

MenuBar { 
	font-weight:bold; 
	color:#FFFFFF;
	fill-colors:#0D0A92, #1B16F4, #37920A, #59EB12;
	fill-alphas:1.0,1.0;
	itemSkin: ClassReference("skins.MyActivatorSkin");
}


문제가 해결되었다.


결론
나는 개인적으로 Flex 프레임워크가 완벽하다고 보지 않는다. 그래도 전체적으로 잘 구조가 잘 잡혀있는 만큼 구조를 이해하고 잘 따라가보면 표면상으로만 해결하지 못한 부분을 해결할 수 있다. 너무 책과 문서에만 의존하지 말고 개발자라면 Flex 소스도 조금씩 분석해보길 바란다.

참고로 몇가지 덧붙여 말하면...
사실 본인은 Flex 3를 손놓은지 꽤 되었다. Flex 4도 지금 입문단계정도이다. 기본적으로 실무에서 Flex 프레임워크로 개발한다기 보다 애플리케이션의 퍼포먼스나 개발적 스타일의 문제로 ActionScript 3.0기반으로 개발한다. Flex 는 너무 유연하게 만들려고 노력해서 그런지 전체적으로 무겁다는 것을 지울 수 없어 간단히 또 빠르고 사용자에게 불편함이 없는 그런 개발이 필요할때는 Flex를 이용하지만 속도,메모리가 중요한 개발은 무조건 ActionScript 3.0으로 개발한다.

Flex는 ActionScript 3.0으로 만들어진 프레임워크 일종이며 mxml이라는 것은 이 Flex 프레임워크를 손쉽게 쓰기 위해 도입된 xml이다. 이를 파싱해서 ActionScript 3.0으로 해석해 다시 swf로 만드는 컴파일러가 compc, mxmlc인 것이다. Flex에 대해서 이러한 점을 알고 공부하고 개발하신다면 여러가지로 도움이 될 것이다. 


참고글

[Flex] 자식 컴포넌트에 CSS를 적용시키는 통상적인 방법 소개
[Flex] 스킨(Skin)을 이용하여 확장가능한 게이지 컴포넌트 제작


글쓴이 : 지돌스타(http://blog.jidolstar.com/627)
Flex 4에 대해서 자료를 모으고 공부하는 중에 매우 괜찮은 블로그를 찾았다.

http://www.hulstkamp.com/

이 사람 블로그에 가면 Flex 4기반 커스텀 컴포넌트를 만든 예제들이 정말 많다. 한동안 이 사람 블로그와 친해져야 겠다는 생각이 들었다. ㅎㅎ

올라온 자료중에 Gumbo(4.0.0.4932) 버전으로 만든 Knob Button 예제가 있었는데 Flex 4 SDK Beta버전이 정식으로 나오기 전이라 바로 실행할 수 없었다. 그래서 소스를 보면서 마이그레이션 작업을 해보았다. 아래 실행 예제는 바로 이 작업의 결과물이다.

위 프로그램은 Knob Button에 대한 데모이다. 마우스로 돌려볼 수 있다.


마이그레이션 하면서 Spark 기반 커스텀 컴포넌트를 제작하는 법에 더욱 익숙해질 수 있었다.

Flex 3의 Halo 기반 컴포넌트는 스킨이 그래픽 기반만 지원했는데 Flex 4의 Spark기반 컴포넌트들은 스킨이 그래픽적 요소 뿐 아니라, 상태변화(state), 다른 컴포넌트 배치, 데이타 표현등이 된다. 또한 FXG를 지원하고 MXML형태로 스킨을 만들 수 있기 때문에 그 확장력이 무궁무진하게 되었다. Halo 컴포넌트의 경우에는 상태변화 바꾸거나 내부 컴포넌트 배치만 달라져도 컴포넌트 자체를 확장해서 다시 만들어야 하는 불편함이 있었지만 Spark컴포넌트는 큰 변경없이 커스텀 스킨만 제작하는 것만으로도 충분히 해결할 수 있게 되었다. 이 내용은 여기에 올려놓은 소스 또는 "Spark DropDownList 사용하기"를 보면 알 수 있을 것이다.

Spark 컨테이너의 경우에는 Layout까지 동적으로 변경할 수 있도록 만들어져서 언제든지 다른 Layout으로 바꿀 수 있고 또는 커스텀 Layout를 만들어 쓸 수도 있다. 반면 Halo 기반 컨테이너는 컨테이너의 Layout을 바꾸기 위해 기존 컨테이너를 다시 확장하거나 새로 만들어야만 했다. 이 내용에 대해서는 "Spark 컨테이너의 Layout, Scrolling, Viewport 소개"를 읽어보길 바란다.

CSS는 정말 획기적으로 많이 추가 되었다. 기존 Class, Type Selector밖에 없었는데 Flex 4로 넘어오면서 ID, Descendant, Pseudo, Mutiple Class등 다양한 Selector가 추가되었다. 이에 대한 글은 "Flex 4의 CSS"를 참고하길 바란다.

아래는 위 프로그램의 소스이다. 참고바란다.


개발 환경은 Flash Builder 4 Beta 1 이다. Flash Builder는 아래 링크에서 다운 받을 수 있다.

 

일전에 Flex 4 CSS에 대한 글을 올렸습니다. Flex 4의 CSS는 Flex 3까지의 CSS의 한계를 극복하도록 만들어졌죠. 그런데 이 CSS가 기능이 확장된 계기가 있었습니다. 갑자기 궁금해지지 않나요? Flex 개발에 관련된 Adobe 개발자들이 머리를 짜서 하자고 했을까요? 아니면 어떤 요청이 있었을까요? 답은 Adobe Bug reporting 시스템에 있었습니다.

 

Adobe에서는 Adobe Bug Reporting 시스템을 운영하고 있습니다. 들어가보면 알겠지만 Flex, BlazeDS, Flash Player, ActionScript Compiler 주제로 개설되어 있고 개발자들의 요구사항 및 버그를 이곳에서 전부 받고 같은 요구사항이 있는 사람들이 투표하는 방식으로 진행되어 투표수가 많으면 Adobe에서 우선순위를 가려서 개발착수에 들어가지요.

Adobe Bug 리포팅 시스템 첫화면

 

 

Flex 4의 고급 CSS도 이런 과정을 통해 탄생된 겁니다.

 

https://bugs.adobe.com/jira/browse/SDK-14385

 

Flex CSS 지원해달라는 요구사항 페이지

 

Jacob Wright라는 사람이 리포팅을 했고 투표수가 75입니다. 그만큼 CSS의 기능이 더욱 확장되었으면 좋겠다는 사람의 수가 많은 거지요. 투표수가 많으니 Adobe 측에서도 무시할 수 없게 된 것이고 이번 Flex 4에서 지원하게 된 것입니다.

 

 

 

버그 리포팅에 참여하자.

 

그럼 우리도 참여할 수 있을까요?

때론 그럴겁니다 영어 못하기 때문에 참여 못한다고....

하지만 꼭 그렇지 않습니다. 자주 들어가 내용을 살펴보고 투표로써 작은 관심을 가져주는 것만으로도 충분합니다. 개발자중에서도 영어 잘하는 사람이 이런 글을 올립니다. 그럼 그 사람의 요청이 있으면 가서 투표해주면 되는 겁니다. 절대 어려운 일이 아닙니다. 아래에 버그 리포팅에서 추천(투표)의 중요성에 대해 알 수 있을 겁니다.

 

[열이아빠]플렉스 버그 리포팅에서 추천의 중요성

 

Adobe Flex/AIR 관련되어 한글문제가 꽤 있습니다. 이 한글문제를 해결하기 위해서도 이 버그 리포트 시스템을 이용하면 됩니다. 이미 Flash Platform 한글문제 공동대응팀이 생겼고 지속적으로 이 문제를 해결하기 위해 다양한 각도로 일하고 있습니다. 대응팀 총괄을 맡고 있는 이희덕씨 블로그에 관련 글이 많습니다.

 

[희희덕덕]한글문제 이슈 관련글

 

아래글은 열이아빠님이 쓰신 플렉스 버그 검색하는 방법입니다..

 

[열이아빠]플렉스 버그 검색해보기

 

투표에 참여해서 Flex/AIR/Flash 한글문제를 해결하는 방법입니다.

 

[희희덕덕]여러분의 참여로 한글 문제를 함께 해결해 봅시다!

 

 

앞으로 Flash Platform 한글문제 공동대응팀은 관련 자료를 종합하고 관리할 것입니다.

 

 

정리하며

 

여러분도 Adobe RIA 기술에 대한 불만 또는 원하는 사항들이 있을겁니다. 앞에서 설명드린데로 잘 정리해서 Adobe Bug Reporting 시스템에 올리시거나 투표를 적극적으로 참여함으로써 성취할 수 있는 겁니다. 한국 개발자들은 유독 영어 울렁증이 있다고들 호소하는데(저를 비롯) 사실 핑계라고 생각합니다. 필요하면 공부하면 되고요. 이런 일들은 꼭 영어를 잘해서 하는 일도 아니거든요. 한국 Adobe RIA 기술에 관련된 시장은 일본에 비해 거의 8배 정도 부족합니다.  그리고 적극성도 일본에 비해 많이 뒤쳐지며 나오는 컨텐츠의 창의력도 훨씬 뒤지는 편입니다. 왜 유독 일본어만 Flex Livedocs가 있을까요? Adobe는 시장성이 없는 한국에 한글문서 작성 인력을 투자하기에는 그들도 아깝다는 생각을 하는겁니다. 물론 Adobe는 그런 마음을 가지면 안되는 것이지만 또한 적극적이지 못한 우리도 반성해야될 일이라고 생각합니다. 우리가 안으로만 숨지않고 적극적으로 활동하면 Adobe에서 한국시장을 무시 못하게 될겁니다. 그런 날이 오길 반드시 바랍니다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/549)

Flex 4는 Flex 3를 완전히 갈아 엎었다는 느낌이 든다. 기존 컴포넌트에서 없어진 것도 있고 새로 추가 된 것도 상당히 많다. 익숙해지려면 적지 않은 시간투자가 필요할 것 같다.

 

CSS에 네임스페이스 추가

 

Flex 4부터 CSS에 네임스페이스가 추가가 되었다. 이러한 구분이 필요한 것은 컴포넌트가 기존 halo 컴포넌트 외에 Spark 컴포넌트가 새로 추가가 되면서 필요해진 것이다. 즉 두개의 컴퍼넌트가 다른 네임스페이스를 사용하고 있기 때문에 CSS도 다른 네임스페이스를 쓰게 하는 것이다. 다음 소스를 보면 이해가 쉽겠다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <mx:DateChooser id="main_calendar" x="20" y="20"/>
    <s:Button label="submit" x="220" y="20"/>
	<fx:Style>
	 
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
	 
	    s|Button {
	        color: #FF0000;
	    }
	 
	    mx|DateChooser {
	        color: #0000FF;
	    }
	 
	</fx:Style>    
</s:Application>

 

 

<fx:Style>내에 @namespace를 정의하고 네임스페이스명|컴포넌트 의 형태로 CSS를 정의할 수 있도록 되었다. 그다지 어려운 사실은 아니다. ^^

 

이런 구조로 만들어진데에는 Flex 4가 Flash Catalyst와 연동이 되면서 디자이너와 개발자의 협업에 충실해지기 위한 거라고 한다. 예를 들어 Slider의 경우 liveDragging속성이 CSS쪽에서는 제어할 수 없던 반면 이번 Flex 4부터는 할 수 있게 되어 개발자는 코드상에서 디자이너는 CSS쪽에서 접근이 가능해져 협업이 되는 것이다. 디자이너의 경우 CSS를 직접 건드리지 않고 Flash Catalyst를 통해서 작업하기 때문에 CSS에 대해서는 몰라도 되지만 그 결과물은 CSS에 속성이 정의 되어 있음을 확인할 수 있을 것이다. 이러한 점이 개발자와 디자이너간에 협업을 가능하게 한 것이라고 생각한다.

 

 

고급 CSS (Advanced CSS)

 

Flex 3의 CSS에서 Selector는 단지 2개 였다. Class SelectorsType Selectors가 그것이다. 하지만 Flex 4부터는 이 Selector 종류가 크게 늘어났다. 이는 더욱 세밀하게 CSS 작업을 할 수 있다는 것을 의미한다. 이제 거의 HTML CSS와 맞먹을 정도인 것 같다. ^^ (CSS는 개인적으로 Flex의 강점중에 하나라고 생각한다. 기존 Flex 3 만으로도 강력한 CSS기능은 나로 하여금 Flex 매력에 푹 빠지게 했다.)

 

참고로 Class Selector와 Type Selector는 다음과 같다.

 

Type Selector

 

같은 종류의 컴포넌트에 적용된다. 다음과 같은 경우 spark.components.Button 및 이를 확장한 컴포넌트에 적용된다.

 

  s|Button { color: #CC0000; }

 

Class Selector

 

컴포넌트의 styleName을 지정한 컴포넌트라면 종류에 상관없이 적용이 된다.

 

  .header { background-color: #CCCCCC; }

 

 

아래부터는 Flex 4에서 추가된 CSS selector에 대해 예제와 함께 살펴본다.

 

1. ID Selectors

예전부터 컴포넌트 ID별로 CSS를 줄 수 있으면 좋겠다는 생각을 가졌다. Flex 4에 부터 적용되니 기쁘다. ID별로 CSS를 적용할 수 있기 때문에 같은 계열의 컴포넌트라도 하나의 객체로 생성된 유일한 1개의 컴포넌트에만 CSS를 적용할 수 있게 된다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <s:Button id="btnTest" x="41" y="24" label="ID 적용됨"/>
    <s:Button x="128" y="24" label="ID 적용 안됨" />
	<fx:Style>
		#btnTest{
			color: #0000ff;
		} 
	</fx:Style>    
</s:Application>

 

ID가 btnTest로 지정된 것만 Label색이 파란색으로 지정된다. ID를 이용하기 때문에 네임스페이스 구분이 필요없다. 이제 컴포넌트 끼리 CSS 사생활 침해를 금지 시킬 수 있겠구나~ (농담)

 

 

2. Multiple class selectors

 

이는 Class selector가 확장된 개념이다. Class Selector는 중첩해서 사용할 수 있다. 아래 예제를 보면 쉽게 이해할 수 있을 것이다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
	<mx:Label styleName="class1 class2" text="중첩 Class Selector 사용" x="8" y="40"/>
	<fx:Style>
		.class1 
		{
			color : #ff0000;
		}
		
		.class2
		{
			fontFamily : "궁서";
		}
	</fx:Style>    
</s:Application>

위 예제에서 Label에 styleName을 설정할 때 class selector를 2개 썼다. class1, class2는 각각 폰트색과 종류를 설정했는데 이 2개의 selector를 사용한 Label은 두개의 class selector에 정의가 적용된다. 결국 Label의 글자색은 빨강, 폰트는 궁서로 보이게 된다.

 

이렇게 class selector를 중첩이 가능하게 됨에 따라 중복되는 selector가 없도록 깔끔하게 CSS구성을 할 수 있게 되었다.

 

 

3. Descendant Selectors

이 selector는 자식/부모 관계에 있는 컴포넌트에 해당한다. Panel위에 Button을 놓았다고 하자. 이때 관계에서 Panel이 부모, Button은 자식이 된다. 특정 부모는 자식에게 어떤 것을 바랄 수 있게 된다. 그래서 사과를 줄수도 있고 바나나도 줄 수 있다. 여기에 특정 부모라는 말이 중요하다. 아무 부모나 자식에게 그렇게 주는 것을 금지한다. 이 관계를 이용한 것이 바로 Descendant selector이다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
    <s:Panel x="14" y="42" width="250" height="200">
        <s:Button x="12" y="12" label="Button"/>
    </s:Panel>
    <s:Group x="33" y="393" width="200" height="200">
        <s:Button x="33" y="29" label="Button"/>
    </s:Group>
	<fx:Style>
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
		s|Panel s|Button{
			color: #304F6B;
			baseColor: #5387B7;
		} 
	</fx:Style>    
</s:Application>

 

위 코드에서 볼 수 있듯이 Panel에 자식 Button만 CSS를 적용하도록 되어 있기 때문에 Group위에 올라간 Button에는 CSS적용이 안된다.

 

 

4. Pseudo selectors

 

Flex 4에서 상태변화에도 CSS를 적용할 수 있게 되었다. Flex 4에서는 Skin을 ProgrammaticSkin 기반이 아닌 SparkSkin기반으로 MXML형태로 제작할 수 있도록 되어  있다. flex4.swc에 spark.skins.*쪽을 훑어보기 바란다. Button의 경우 ButtonSkin.mxml이 존재하는데 아래와 같이 상태별로 다른 스킨을 줄 수 있도록 되어 있다.

<s:states>
	<s:State name="up" />
	<s:State name="over" />
	<s:State name="down" />
	<s:State name="disabled" />
</s:states>

 

이 상태에 따라서 CSS를 적용할 수 있도록 한것이 바로 Pseudo selector이다. 아래 예제에서 볼 수 있듯이 :를 이용해 컴포넌트의 상태에 따라 CSS를 주는 것을 확인할 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768">
	<s:Button label="Test"/>
	<fx:Style>
	    @namespace s "library://ns.adobe.com/flex/spark";
	    @namespace mx "library://ns.adobe.com/flex/halo";
	 
		s|Button:up{
			color: #304F6B;
			baseColor: #5387B7;
		} 
		
		s|Button:down 
		{
			baseColor: #917541;
			color: #B8A553;
		}
		
		s|Button:over 
		{
			baseColor: #B8A553;
			color: #6B5630;
		}
	</fx:Style>    
</s:Application>

 

 

CSS Selector 사용하기

 

위에서 설명한 Selector 들은 독립적으로 사용하는 것외에도 다른 Selector와 함께 사용할 수 있다.

 

Flex 3에서는 아래와 같이 단순한 Type Selector와 범용적으로 사용되는 Class Selector만 사용할 수 있었다.

 

Button { color:#00FF00; }     /* Simple type selector */
.special { color:#FF0000; }     /* Universal class selector */

하지만 Flex 4부터는 새로운 Selector와 함께 섞어서 사용할 수 있게 되었다. 아래 CSS 적용 예시를 보자.

 

Button { color:#00FF00; }     /* 단순 type selector */
.special { color:#FF0000; }     /* 범용 class selector */
Button.special { color:#FF0000; }     /* class selector와 함께 사용된 Type selector  */
Button#b13 { color:#0000FF; }     /* ID selector와 함께 사용된 Type selector */
#b13 { color:#FF9933; }     /* 범용 id selector */
Panel VBox Button { color:#990000; }     /* Descendant selector */
Button:up { color:#FF9900; }     /* pseudo selector와 함께 사용된 Type selector  */
:up { color:#FF9933; }     /* 범용 pseudo selector */

 

위 내용을 좀더 새부적으로 이해하기 위해 예를 하나 들어보겠다.

 

Panel Button { color:#DD0000; }

 

위처럼 CSS Selector를 중첩한 경우 경우 다음과 같은 상황에 있는 컴포넌트에 color가 설정된다.

 

- Panel 위에 Button

- Panel을 확장한 컴포넌트 위에 Button

- Panel또는 Panel을 확장한 컴포넌트 위에 Button을 확장한 CheckBox와 같은 커스텀 Button

 

 

정리하며

 

Flex 4 CSS의 Selector를 외우기 쉽게 하기 위해 아래를 꼭 기억하자.

  • local name : type selector - 컴포넌트 종류
  • styleName : class selector - 컴포넌트 스타일명
  • identity : id selector - 컴포넌트 ID
  • state : pseudo selector - 컴포넌트 상태
  • display list : descendant selector - 컴포넌트 디스플레이 리스트 부모/자식 관계

 

참고글

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/548)

Adobe Flex에서 Visual Component에 스타일을 입히기 위해 MXML형태의 <mx:Style></mx:Style>을 자주 사용할 것이다.

 

<mx:Style>을 이해하기 앞서 먼저 Flex의 컴파일 과정에 대해서 언급하겠다. 알다시피 Flex의 결과물은 Flash 컨텐츠인 SWF파일이다. Flex로 만들어진 결과물은 컴파일 단계에서 모두 ActionScript 3.0으로 변환(Generate)되고 변환된 ActionScript 3.0 파일을 가지고 SWF가 만들어진다. 즉, Flex에서 유용하게 사용되는 MXML은 UI 디자인의 편의성을 제공하기 위해 사용되는 것이지 실제 컴파일 단계에서는 직접적으로 사용되지 않는 언어이다. 

 

<mx:Style> 변환에 대한 이해

 

MXML에서 <mx:Style>로 싸여진 것도 ActionScript 3.0 으로 변환한다는 것을 이제 짐작할 수 있을 것이다. 실제로 어떻게 ActionScript 3.0으로 변환되는지 확인하기 위해 Flex Builder에서 Flex Project를 만들고 생성된 프로젝트의 컴파일러 옵션에 –keep-generated-actionscript를 넣어주자. 이 옵션은 Flex 컴파일시 사용되는 mxmlc에 지정하는 옵션이다. 더 많은 옵션은 “About the application compiler options”를 참고하면 되겠다.

 

 

 

위 옵션대로 넣어주면 프로젝트의 src폴더에 generated 폴더가 생성된다.

 

 

 

generated 폴더를 살펴보면 아래 그림처럼 ActionScript 3.0 파일이 생성된 것을 확인할 수 있다.

 

 

 

아래 코드 처럼 Application에 어떤 visual component도 삽입하지 않은 상태에서 GenerateCSSTest-generated.as 파일을 살펴보자. (프로젝트명을 GenerateCSSTest로 했기 대문에 GenerateCSSTest-generated.as 가 된 것이다.)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
</mx:Application>

 

아래 코드는 MXML로 만들어진 Application을 ActionScript 3.0으로 변환된 GenerateCSSTest 클래스이다.(GenerateCSSTest-generated.as). 우리가 집중적으로 살펴보아야 할 부분은 <mx:Style>이 어떻게 사용되는가 알아내는 것이므로 코드상에서 _GenerateCSSTest_StylesInit() 부분에서 관련성을 찾아낼 수 있을 것이다. 이 애플리케이션이 실행하게 되면 GenerateCSSTest 생성자에서 _GenerateCSSTest_StylesInit()를 호출하므로 <mx:Style>에서 적용한 CSS가 여기서 적용될 것이다라는 것을 예측할 수 있다.

public class GenerateCSSTest
    extends mx.core.Application
{

    //    instance variables

    //    type-import dummies

    //    Container document descriptor
private var _documentDescriptor_ : mx.core.UIComponentDescriptor =
new mx.core.UIComponentDescriptor({
  type: mx.core.Application
})

    //    constructor (Flex display object)
    /**
     * @private
     **/
    public function GenerateCSSTest()
    {
        super();

        mx_internal::_document = this;

        //    our style settings

        //    ambient styles
        mx_internal::_GenerateCSSTest_StylesInit();

        //    properties
        this.layout = "vertical";

        //    events

    }

    //    initialize()
    /**
     * @private
     **/
    override public function initialize():void
    {
         mx_internal::setDocumentDescriptor(_documentDescriptor_);

        super.initialize();
    }

    //    scripts
    //    end scripts

    //    supporting function definitions for properties, events, styles, effects

    //    initialize style defs for GenerateCSSTest

    mx_internal static var _GenerateCSSTest_StylesInit_done:Boolean = false;

    mx_internal function _GenerateCSSTest_StylesInit():void
    {
        //    only add our style defs to the StyleManager once
        if (mx_internal::_GenerateCSSTest_StylesInit_done)
            return;
        else
            mx_internal::_GenerateCSSTest_StylesInit_done = true;

        var style:CSSStyleDeclaration;
        var effects:Array;

        StyleManager.mx_internal::initProtoChainRoots();
    }

    //    embed carrier vars
    //    end embed carrier vars

//    end class def
}

 

이제 아래 코드처럼 Application안에 Button과 TextArea 컴포넌트를 삽입하고 각각에 대해서 Type Selector와 Class Selector로 CSS를 <mx:Style> 태그안에 적용해보자.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Button label="버튼1"/>
    <mx:TextArea text="Flex가 쪼아~" styleName="myTextArea"/>
    <mx:Style>
        Button
        {
            fontSize:12pt;
        }
        .myTextArea
        {
            fontSize:14pt;
            color:red;
        }
    </mx:Style>
</mx:Application>

 

아래코드는 위처럼 Application에 2가지 컴포넌트와 스타일을 적용했을 때 만들어진 GenerateCSSText 클래스 ActionScript 3.0 코드이다. 파란색 부분은 아무것도 안넣을 때와 비교할 때 추가된 부분이다. MXML로 컴포넌트를 삽입하면 상단 파란색 부분처럼 UIComponentDescriptor의 인스턴스인 _documentDescriptor_에 자동으로 등록시켜준다. 각 컴포넌트에 설정된 속성인 label, text, styleName도 추가되어 있는 것을 볼 수 있다. _documentDescriptor_는 initialize()함수 실행시 적용된다.

 

또한 그 아래로 가보면 우리가 관심을 가지고 있는 <mx:Style>에 작성된 CSS가 어떻게 적용되었나 알 수 있다. 

 

여기 있는 코드는 2개의 CSS Selector를 등록하고 있다.

 

첫번째로 Class Selector인 “.myTextArea” 이다. StyleManager.getStyleDeclaration()을 통해 기존에 “.myTextArea”가 등록되어 있는지 조사한다. 만약 등록이 안되어 있다면 StyleManager.setStyleDeclaration()을 통해 CSSStyleDeclaration의 인스턴스형으로 “.myTextArea”를 등록한다. 그런 다음 <mx:Style>에 지정한 fontSize와 color를 적용하기 위해 CSSStyleDeclaration의 factory함수를 정의한다. 이렇게 해두면 자동적으로 StyleManager에 등록된 Class Selector인 “.myTextArea”를 생성하게 되며 StyleManager는 styleName이 “.myTextArea”로 적용된 컴포넌트는 전부 찾아 적용해준다.

 

같은 방법으로 Type Selector인 “Button”을 등록한다. StyleManager은 Button 컴포넌트로 만들어진 인스턴스를 찾아 fontSize:12pt를 적용해준다.

이처럼 <mx:Style></mx:Style>안에 적용된 CSS는 ActionScript 3.0으로 만들어져 StyleManager 클래스가 담당하도록 Flex 컴파일러가 변경시켜준다. 이것이 핵심이다.

public class GenerateCSSTest
    extends mx.core.Application
{

    //    instance variables

    //    type-import dummies

    //    Container document descriptor
private var _documentDescriptor_ : mx.core.UIComponentDescriptor =
new mx.core.UIComponentDescriptor({
  type: mx.core.Application
  ,
  propertiesFactory: function():Object { return {
    childDescriptors: [
      new mx.core.UIComponentDescriptor({
        type: mx.controls.Button
        ,
        propertiesFactory: function():Object { return {
          label: "버튼1"
        }}
      })
    ,
      new mx.core.UIComponentDescriptor({
        type: mx.controls.TextArea
        ,
        propertiesFactory: function():Object { return {
          text: "Flex가 쪼아~",
          styleName: "myTextArea"
        }}
      })
    ]
  }}
})

    //    constructor (Flex display object)
    /**
     * @private
     **/
    public function GenerateCSSTest()
    {
        super();

        mx_internal::_document = this;

        //    our style settings

        //    ambient styles
        mx_internal::_GenerateCSSTest_StylesInit();

        //    properties
        this.layout = "vertical";

        //    events

    }

    //    initialize()
    /**
     * @private
     **/
    override public function initialize():void
    {
         mx_internal::setDocumentDescriptor(_documentDescriptor_);

        super.initialize();
    }

    //    scripts
    //    end scripts

    //    supporting function definitions for properties, events, styles, effects

    //    initialize style defs for GenerateCSSTest

    mx_internal static var _GenerateCSSTest_StylesInit_done:Boolean = false;

    mx_internal function _GenerateCSSTest_StylesInit():void
    {
        //    only add our style defs to the StyleManager once
        if (mx_internal::_GenerateCSSTest_StylesInit_done)
            return;
        else
            mx_internal::_GenerateCSSTest_StylesInit_done = true;

        var style:CSSStyleDeclaration;
        var effects:Array;

        // myTextArea
        style = StyleManager.getStyleDeclaration(".myTextArea");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration(".myTextArea", style, false);
        }
        if (style.factory == null)
        {
            style.factory = function():void
            {
                this.color = 0xFF0000;
                this.fontSize = 14;
            };
        }
        // Button
        style = StyleManager.getStyleDeclaration("Button");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration("Button", style, false);
        }
        if (style.factory == null)
        {
            style.factory = function():void
            {
                this.fontSize = 12;
            };
        }

        StyleManager.mx_internal::initProtoChainRoots();
    }

    //    embed carrier vars
    //    end embed carrier vars

//    end class def
}

 

본문에서는 StyleManager 자체의 구조를 설명하는 것은 너무 방대할 것이므로 생략한다. 개인적으로 Flex의 스타일 적용 방식은 매우 유연하고 사용하기 편하다고 생각한다. 그만큼 Flex SDK에 있는 StyleManager가 잘 만들어져 있다는 것을 의미하기도 한다. 시간이 허락된다면 StyleManager를 분석해서 배치된 Visual Component에 어떻게 스타일이 적용되는지 알아보는 것도 흥미롭고 유익한 일일 것이다.

 

컴파일러 옵션인 –keep-generated-actionscript은 MXML을 ActionScript 3.0으로 변환한 코드를 가시적으로 볼 수 있게 함으로써 Flex가 어떻게 동작하는가 아는데 중요한 가이드 역할을 하고 있는 셈이다.

 

컴포넌트의 기본 스타일은 어떻게 적용되는가?

 

이쯤되면 궁금한 점이 또 생긴다.

 

<mx:Style>로 적용하지 않은 컴포넌트들은 어떻게 CSS가 먹히는 것일까? 본인은 처음에 해당 컴포넌트 클래스에 StyleManager를 이용하여 CSS를 만들었을 것이라 생각했다. 하지만 그게 아니였다. Flex SDK에 있는 Button.as를 살펴보길 바란다.(Window XP에 Flex Builder 3 Professonal을 설치한 경우에는 C:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\frameworks\projects\framework\src\mx\controls에 Button.as 가 있다.) 직접 보면 알겠지만 Button.as안에는 StyleManager에 관련된 내용이 전혀 존재하지 않는다. 그럼 Flex 컴포넌트들의 기본 스타일은 도대체 어떻게 적용되는 것인가?

답은 generated 폴더 안에 있다.

 

generated 폴더 내부에는 Button에 기본 스타일을 적용을 정의한 _ButtonStyle.as 뿐 아니라 _gloabalStyle.as, _ApplicationStyle.as, _dateGridStylesStyle.as 등이 있다.

아래는 _ButtonStyle.as이다. Button의 스킨인 mx.skins.halo.ButtonSkin이 여기서 적용된다. 또한 textAlign도 여기서 적용되는 것을 볼 수 있다. 즉 사용한 컴포넌트에 CSS를 적용하지 않더라도 기본 스타일이 이들 코드를 통해 적용됨을 알 수 있다.

 

앞선 예제에서 Application에서 <mx:Style>에 “Button” Type Selector로 fontSize=12pt가 적용되었으므로 이 로직이 실행이 되고 난 다음에 fontSize=12pt가 적용됨을 감각적으로 알 수 있을 것이다.

public class _ButtonStyle
{

    public static function init(fbs:IFlexModuleFactory):void
    {
        var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration("Button");
        if (!style)
        {
            style = new CSSStyleDeclaration();
            StyleManager.setStyleDeclaration("Button", style, false);
        }
        if (style.defaultFactory == null)
        {
            style.defaultFactory = function():void
            {
                this.paddingTop = 2;
                this.textAlign = "center";
                this.skin = mx.skins.halo.ButtonSkin;
                this.paddingLeft = 10;
                this.fontWeight = "bold";
                this.cornerRadius = 4;
                this.paddingRight = 10;
                this.verticalGap = 2;
                this.horizontalGap = 2;
                this.paddingBottom = 2;
            };
        }
    }
}

 

방금 언급한 Style관련 ActionScript 3.0 코드들(_ButtonStyle.as, _gloabalStyle.as, _ApplicationStyle.as, _dateGridStylesStyle.as 등)은 Flex SDK의 SystemManager 클래스에 등록되어 사용된다. generated 폴더를 살펴보면 SystemManager 클래스를 확장한 _GenerateCSSTest_mx_managers_SystemManager 클래스의 ActionScript 3.0 코드가 존재한다. 아래 코드를 살펴보자.

public class _GenerateCSSTest_mx_managers_SystemManager
    extends mx.managers.SystemManager
    implements IFlexModuleFactory
{
    public function _GenerateCSSTest_mx_managers_SystemManager()
    {

        super();
    }

    override     public function create(… params):Object
    {
        if (params.length > 0 && !(params[0] is String))
            return super.create.apply(this, params);

        var mainClassName:String = params.length == 0 ? "GenerateCSSTest" : String(params[0]);
        var mainClass:Class = Class(getDefinitionByName(mainClassName));
        if (!mainClass)
            return null;

        var instance:Object = new mainClass();
        if (instance is IFlexModule)
            (IFlexModule(instance)).moduleFactory = this;
        return instance;
    }

    override    public function info():Object
    {
        return {
            compiledLocales: [ "en_US" ],
            compiledResourceBundleNames: [ "containers", "core", "effects", "skins", "styles" ],
            currentDomain: ApplicationDomain.currentDomain,
            layout: "vertical",
            mainClassName: "GenerateCSSTest",
            mixins: [ "_GenerateCSSTest_FlexInit", "_alertButtonStyleStyle", "_ScrollBarStyle", "_activeTabStyleStyle", "_textAreaHScrollBarStyleStyle", "_ToolTipStyle", "_advancedDataGridStylesStyle", "_comboDropdownStyle", "_ContainerStyle", "_textAreaVScrollBarStyleStyle", "_linkButtonStyleStyle", "_globalStyle", "_windowStatusStyle", "_windowStylesStyle", "_activeButtonStyleStyle", "_errorTipStyle", "_richTextEditorTextAreaStyleStyle", "_CursorManagerStyle", "_todayStyleStyle", "_dateFieldPopupStyle", "_plainStyle", "_dataGridStylesStyle", "_ApplicationStyle", "_headerDateTextStyle", "_ButtonStyle", "_popUpMenuStyle", "_swatchPanelTextFieldStyle", "_opaquePanelStyle", "_weekDayStyleStyle", "_headerDragProxyStyleStyle" ]

        }
    }
}

info() 메소드를 override해서 mixins에 CSS를 적용한 클래스의 이름들을 전부 Array형태로 담고 있다. SystemManger는 이 mixins에 등록된 클래스들을 전부 스캔하며 CSS를 적용한다.

 

아래코드는 StyleManager 내부의 일부 코드이다. 앞서 설명한 SystemManager를 확장한_GenerateCSSTest_mx_managers_SystemManager 클래스는 info()를 override했기 때문에 해당 mixins값을 전부 얻어올 수 있다. 이 값의 길이만큼 loop를 돌면서 getDefinitionByName()메소드를 통해 해당 클래스 정의되어 있는지 알아내어 그 클래스의 init()함수를 호출한다. _ButtonStyle.as에 init()메소드에 StyleManager를 가지고 Button의 Type Selector를 등록했다는 것을 확인할 수 있었다. 결국 StyleManager로 Style을 적용한 클래스들의 init()함수를 모두 호출함으로써 기본 CSS가 애플리케이션에 적용되는 것이다.

        var mixinList:Array = info()["mixins"];
        if (mixinList && mixinList.length > 0)
        {
            var n:int = mixinList.length;
            for (var i:int = 0; i < n; ++i)
            {
                // trace("initializing mixin " + mixinList[i]);
                var c:Class = Class(getDefinitionByName(mixinList[i]));
                c["init"](this);
            }
        }

 

 

상속되는 CSS 속성은 어떻게 적용될까?

 

CSS 속성중에는 상속되는 것과 상속되지 않는 것이 있다. 가령 fontSize, color, fontFamily 등은 상속이 된다. 한 예로 아래코드처럼 Application에 color를 blue로 지정해보자. color는 상속되는 CSS속성이기 때문에 Button에 color가 지정되어 있지 않음에도 불구하고 파란색 글자색이 적용된다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Button label="버튼1"/>
    <mx:TextArea text="Flex가 쪼아~" styleName="myTextArea"/>
    <mx:Style>
        Application
        {
            color:blue;
        }

        Button
        {
            fontSize:12pt;
        }
        .myTextArea
        {
            fontSize:14pt;
            color:red;
        }
    </mx:Style>
</mx:Application>

 

 

Button에는 파란색이 적용된 반면 TextArea에는 그대로 빨간색이 적용된 이유는 CSS적용 우선순위 때문이다. 부모(Application)에 적용한 CSS속성값에 자식(TextArea)에서 다시 적용하면 부모에서 적용한 것은 무시된다. 다른 이야기지만 이러한 일을 전부 StyleManager에서 하고 있다는게 대단해보인다. Flex SDK의 강력함이 엿보인다. 만약 Flex SDK를 사용하지 않고 순수하게 ActionScript 3.0으로 코딩해야하는 경우에 CSS를 적용해야한다면… 앞이 깜깜할 뿐이다. 다 만들어야하니 말이다. ^^;

 

이처럼 Flex의 CSS는 상속이 되는 속성들이 있는데 이들 속성도 generated 폴더에 살펴보면 어떻게 등록되어 사용되는지 살펴볼 수 있다. _GenerateCSSTest_FlexInit.as를 살펴보자.

public class _GenerateCSSTest_FlexInit
{
   public function _GenerateCSSTest_FlexInit()
   {
       super();
   }
   public static function init(fbs:IFlexModuleFactory):void
   {
      EffectManager.mx_internal::registerEffectTrigger("addedEffect", "added");
      EffectManager.mx_internal::registerEffectTrigger("creationCompleteEffect", "creationComplete");
      EffectManager.mx_internal::registerEffectTrigger("focusInEffect", "focusIn");
      EffectManager.mx_internal::registerEffectTrigger("focusOutEffect", "focusOut");
      EffectManager.mx_internal::registerEffectTrigger("hideEffect", "hide");
      EffectManager.mx_internal::registerEffectTrigger("mouseDownEffect", "mouseDown");
      EffectManager.mx_internal::registerEffectTrigger("mouseUpEffect", "mouseUp");
      EffectManager.mx_internal::registerEffectTrigger("moveEffect", "move");
      EffectManager.mx_internal::registerEffectTrigger("removedEffect", "removed");
      EffectManager.mx_internal::registerEffectTrigger("resizeEffect", "resize");
      EffectManager.mx_internal::registerEffectTrigger("rollOutEffect", "rollOut");
      EffectManager.mx_internal::registerEffectTrigger("rollOverEffect", "rollOver");
      EffectManager.mx_internal::registerEffectTrigger("showEffect", "show");
      var styleNames:Array = ["fontWeight", "modalTransparencyBlur", "textRollOverColor", "backgroundDisabledColor", "textIndent", "barColor", "fontSize", "kerning", "textAlign", "fontStyle", "modalTransparencyDuration", "textSelectedColor", "modalTransparency", "fontGridFitType", "disabledColor", "fontAntiAliasType", "modalTransparencyColor", "leading", "dropShadowColor", "themeColor", "letterSpacing", "fontFamily", "color", "fontThickness", "errorColor", "fontSharpness", "textDecoration"];

      import mx.styles.StyleManager;

      for (var i:int = 0; i < styleNames.length; i++)
      {
         StyleManager.registerInheritingStyle(styleNames[i]);
      }
   }
}  // FlexInit

앞의 코드에서 볼 수 있듯이 상속되는 Style속성들은 StyleManager의 registerInheritingStyle() 메소드를 통해 등록된다. 아하! 라고 외쳤다면 여러분도 Flex SDK의 StyleManager의 강력함에 찬사를 보내고 있는 것일지도 모르겠다. ^^

참고로 _GenerateCSSTest_FlexInit 클래스는 _GenerateCSSTest_mx_managers_SystemManager의 info() 메소드의 mixins에 등록되어 있어서 SystemManager에서 _GenerateCSSTest_FlexInit 클래스의 info() 메소드를 자동으로 호출하게 된다.

 

 

나만의 Visual Component에 기본 CSS는 어떻게 적용할까?

 

Flex SDK에서 제공하는 Button이나 CheckBox와 같은 컴포넌트는 컴파일시 기본 CSS를 만들어주는 것을 앞서 확인할 수 있었다. 그럼 UIComponent를 확장해서 만든 Visual Component는 어떻게 기본 CSS를 적용할 수 있을까? 생각보다 어렵지 않다. 다음 코드를 보자.

package
{
import com.jidolstar.ui.frames.skins.FrameSkin;
import mx.core.UIComponent;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;

[Style(name="frameSkin", type="Class", inherit="no")]
[Style(name="backgroundColor",type="Number",format="Color",inherit="no")]
[Style(name="backgroundAlpha",type="Number",inherit="no")]
[Style(name="borderColor",type="Number",format="Color",inherit="no")]
[Style(name="borderAlpha",type="Number",inherit="no")]
[Style(name="borderThickness",type="Number",nherit="no")]
[Style(name="paddingLeft",type="Number",nherit="no")]
[Style(name="paddingRight",type="Number",nherit="no")]
[Style(name="paddingTop",type="Number",nherit="no")]
[Style(name="paddingBottom",type="Number",nherit="no")]
[Style(name="cornerRadius",type="Number",format="Color",inherit="no")]

public class MyComponent extends UIComponent
{
    private static var classConstructed:Boolean = classConstruct();
    private static function classConstruct():Boolean
    {
        // 기존에 TagBar이름의 type selector
        // 스타일 선언에 대한 인스턴스를 가져온다.
        var styleDeclaration:CSSStyleDeclaration
            = StyleManager.getStyleDeclaration("MyComponent ");

        // 이전에 스타일 선언이 되어있지 않은 경우라면
        // 스타일선언 인스턴스를 새로 하나 만든다.
        if (!styleDeclaration)
            styleDeclaration = new CSSStyleDeclaration();

        // defaultFactory를 이용해 스타일을 정의한다.
        styleDeclaration.defaultFactory = function ():void
        {
            this.frameSkin = FrameSkin;
            this.backgroundColor = 0xcccccc;
            this.backgroundAlpha = 1;
            this.borderColor = 0×009999;
            this.borderAlpha = 1;
            this.borderThickness = 4;
            this.paddingLeft = 10;
            this.paddingRight = 10;
            this.paddingTop = 10;
            this.paddingBottom = 10;
            this.cornerRadius = 10;
        }

        //선언된 스타일을 적용시킨다.
        StyleManager.setStyleDeclaration("MyComponent",
                  styleDeclaration, false);
        return true;
    }

    override public function styleChanged(styleProp:String):void
    {
        super.styleChanged( styleProp );
        invalidateProperties();
        invalidateDisplayList();
    }
}
}

 

위 코드는 구동되지 않는다. 하지만 어떻게 기본 CSS를 줄 수 있을지 해답을 제공하고 있다. 앞서 설명한 _ButtonStyle.as 에서 적용한 방법대로 private static 코드를 위처럼 만들면 된다. UIComponent를 확장한 MyComponent의 static 값인 classConstructed:Boolean = classConstruct() 부분이 처음 MyComponent가 엑세스 될 때 실행되므로 딱 한번만 CSS적용하는 static으로 정의된 classConstruct() 메소드가 실행된다. 이런 방법으로 기본 CSS를 컴포넌트에 적용시킬 수 있다. 이보다 더 좋은 방법이나 다른 방법이 있다면 소개해주길 바란다.

 

정리하며

 

지금까지 <mx:Style>에 적용한 CSS가 어떻게 ActionScript 3.0으로 변환되어 적용되는가 알아보았다. 여기서는 <mx:Style>만 짚고 넘어갔지만 Flex가 전체적으로 어떻게 돌아가는가 알기 위해서 generated 폴더에 변환된 ActionScript 3.0 파일을 전부 살펴보면서 분석해 봐야 한다. 중요하게 이해하고 넘어가야 하는 것은 Flex의 MXML은 쉽고 빠른 설계를 위해 사용되는 언어라는 점과 MXML은 모두 ActionScript 3.0으로 변환되어 컴파일 된다는 점이다. 그러므로 Flex는 MXML을 통해 ActionScript 3.0의 불편한 하드 코딩을 줄여주고 쉽게 스타일링, 데이터바인딩, 구조설계 등을 할 수 있게 해준 일종의 프레임워크로 이해해야 한다. C나 C++, Java등을 하고 Flex를 처음 접하는 사람들은 MXML과 ActionScript 3.0의 차이점을 알고 어떤 방식으로 Flex 애플리케이션이 돌아가는지 이해하는 과정이 필요한 시점이 반드시 올 것이다. Visual Component들이 어떻게 CSS가 적용되고 어떤 방식으로 Application이 구동되는지 어느 정도 이해해야 한 단계 높은 Flex 프로그래밍을 할 수 있다. 이것이 제대로 된 Flex Component를 설계하기 위한 필요조건이 아닐까?

 

참고글

 

+ Recent posts