ServerSocket이라는 것이 Adobe AIR 2에서 생겼는데요.
뭐 특별히 제약사항같은 것은 없는 것 같군요. 그냥 서버소켓 만들어서 쓰면 됩니다.

우선 Adobe AIR 2 셋팅Flash Builder를 다운로드

서버소켓 생성하는 방법은
new ServerSocket();
serverSocket.bind(포트, "아이피");
serverSocket.listen();
하면 서버소켓이 생성됩니다.

Event.Connect를 이벤트 추가하면 상대방이 연결해왔을 때 호출이 됩니다.
보통 Java에서는 Thread를 통해서 하게 되는데, Actionscript3는 특성상 이벤트기반이기에... 그냥 여러개가 연결이 되도 이벤트가 발생하고, 그 발생한 이벤트객체에서 socket이 들어있어서 그걸 이용하면 되구요.

그 소켓에 다시 이벤트를 걸어주면 됩니다. 그 소켓은 client와 연결된 소켓! 기존 client소켓처럼 쓰면 됩니다^^
데이터는 read, write로 주고 받으면 되죠.

아래는 간단한 예제를....-_-
ServerSocketTest.mxml
[code]<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       applicationComplete="windowedapplication_applicationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import flash.events.Event;
            import flash.events.IOErrorEvent;
            import flash.events.ProgressEvent;
            import flash.events.ServerSocketConnectEvent;
            import flash.net.ServerSocket;
            import flash.net.Socket;
           
            import mx.events.FlexEvent;
           
            private var socketPolicyUtil:SocketPolicyUtil;
           
            private var serverSocket:ServerSocket;
            private var listSocket:Vector.<Socket>;
           
            protected function windowedapplication_applicationCompleteHandler(event:FlexEvent):void
            {
                socketPolicyUtil = new SocketPolicyUtil();
               
                // 소켓리스트 초기화
                listSocket = new Vector.<Socket>;
               
                serverSocket = new ServerSocket();
               
                serverSocket.addEventListener(Event.CONNECT, connectHandler);
                serverSocket.addEventListener(Event.CLOSE, onClose);
       
                serverSocket.bind(10000, "127.0.0.1");
               
                serverSocket.listen();
                trace("Listening on " + serverSocket.port);
            }

            private function connectHandler(event:ServerSocketConnectEvent):void
            {
                // The socket is provided by the event object
                var socket:Socket = event.socket as Socket;
                listSocket.push(socket);
               
                socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
                socket.addEventListener(Event.CLOSE, onClientClose);
                socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
               
                socket.writeUTFBytes("Connected.");
                socket.flush();
               
                trace("Sending connect message");
            }
           
            private function socketDataHandler(event:ProgressEvent):void
            {
                var socket:Socket = event.target as Socket;
               
                // Read the message from the socket
                var message:String = socket.readUTFBytes(socket.bytesAvailable);
                trace("Received: " + message);
               
                // 등록된 소켓에 모두 전송
                for each (var s:Socket in listSocket)
                {
                    s.writeUTFBytes(message);
                    s.flush();
                }
            }
           
            private function onClientClose(event:Event):void
            {
                trace("Connection to client closed");
                var socket:Socket = event.target as Socket;
               
                // 등록된 소켓 삭제
                var i:int = 0;
                for each(var s:Socket in listSocket)
                {
                    if (s == socket)
                    {
                        trace("같음");
                        listSocket.splice(i, 1);
                        return;
                    }
                    i++;
                }
            }
           
            private function onIOError(event:IOErrorEvent):void
            {
                trace("IOError: " + event.text);   
            }
           
            private function onClose(event:Event):void
            {
                trace("Server socket closed by OS.");       
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
</s:WindowedApplication>
[/code]
그냥 코드만 봐도 그리 어렵지 않아요.
서버소켓을 생성하고, 연결되는 소켓들은 list(Vector)에 추가해서 관리하게 되고, 끊어지면 list에서 빼버리고, 대화요청이 들어오면 받아서 모두에게 뿌려주면 돼요.

여기서 제가 만든 클래스가 하나 있는데요. SocketPolicyUtil클래스인데, 크로스 도메인을 위한 클래스입니다. 아래에서 설명을....-_-


크로스 도메인 설정 클래스
여기서 제가 보안샌드박스를 위한 클래스를 하나 만들었는데요. 크로스도메인에서 socket요청이 들어온 경우에는 crossdomain.xml을 넘겨줘야하는데, 그것도 air에서 다 할 수 있습니다.
타 도메인에서 요청하는 경우, 클라이언트 측에서는 843포트를 통해 crossdomain.xml을 요청하게 되어있습니다. 물론 클라이언트에서 843이 아닌 다른 포트로 요청을 원하면 바꿀 수 있지요.
 클라이언트 측에 이런 코드를 넣으면 되죠. 그러면 10001로 포트를 요청하죠.
[code]Security.loadPolicyFile("xmlsocket://127.0.0.1:10001");[/code]
SocketPolicyUtil.as
[code]package
{
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.ServerSocketConnectEvent;
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.net.ServerSocket;
    import flash.net.Socket;

    public class SocketPolicyUtil
    {
        private var serverSocket:ServerSocket;
       
        public function SocketPolicyUtil(port:int = 843)
        {
            serverSocket = new ServerSocket();
            serverSocket.bind(port, "127.0.0.1");
            serverSocket.listen();
            serverSocket.addEventListener(Event.CONNECT, connectHandler);
            serverSocket.addEventListener(Event.CLOSE, onClose);
        }
       
        private function connectHandler(event:ServerSocketConnectEvent):void
        {
            var socket:Socket = event.socket as Socket;
           
            socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
            socket.addEventListener(Event.CLOSE, onClientClose);
            socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
           
            trace("Policy Connected.");
        }
       
        private function socketDataHandler(event:ProgressEvent):void
        {
            var socket:Socket = event.target as Socket;
           
            var message:String = socket.readUTFBytes(socket.bytesAvailable);
            trace("Policy received : " + message);
           
            var file:File = new File(File.applicationDirectory.nativePath + File.separator + "policy.xml");
            var stream:FileStream = new FileStream();
            stream.open(file, FileMode.READ);
            var data:String = stream.readUTFBytes(stream.bytesAvailable);
            trace("policy data = " + data);
            socket.writeUTFBytes(data);
            socket.writeByte(0);
            socket.flush();
        }
       
        private function onClientClose(event:Event):void
        {
            trace("Policy close");
            removeClientSocketEvent(event.target as Socket);
        }
       
        private function onIOError(event:IOErrorEvent):void
        {
            trace("ioerror = " + event.text);   
        }
       
        private function onClose(event:Event):void
        {
            trace("Server socket closed by OS.");
        }
       
        private function removeClientSocketEvent(socket:Socket):void
        {
            socket.removeEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
            socket.removeEventListener(Event.CLOSE, onClientClose);
            socket.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);   
        }
    }
}[/code]
중요한 점은 policy.xml파일 날릴 때 마지막에 writeByte(0)으로 끝을 맺어줘야한다는...
이 클래스를 보면 policy.xml파일은 File.applicationDirectory에서 찾고 있으니 이걸 src폴더에다가 넣어버리고 패키지할 때 같이 묶어버리면 됩니다. 그리고 그냥 843포트로 열어서 crossdomain.xml파일을 그냥 전송해주기만 하면 되죠. *는 권장사항이 아니니...-_- 해당 도메인이랑 포트번호를 정확히 입력하는게...-_-
policy.xml
[code]<?xml version='1.0' encoding='UTF-8'?>
<cross-domain-policy>
    <allow-access-from domain='*' to-ports='*'/>
</cross-domain-policy>[/code]

아래는 클라이언트 코드
ClientSocketTest.mxml
[code]<?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"
               applicationComplete="application1_applicationCompleteHandler(event)">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;
            import mx.events.FlexEvent;

            private var socket:Socket;
           
            protected function application1_applicationCompleteHandler(event:FlexEvent):void
            {
                //Security.loadPolicyFile("xmlsocket://127.0.0.1:10001");
            }

            protected function btnDisconnect_clickHandler(event:MouseEvent):void
            {
                if (inputId.text.length < 1)
                {
                    Alert.show("아이디를 입력하세요");
                    return;
                }
                socket = new Socket("127.0.0.1", 10000);
                socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
                currentState = "connect";
                labelId.text = inputId.text;
            }
           
            protected function btnConenct_clickHandler(event:MouseEvent):void
            {
                socket.close();
                currentState = "disconnect";
            }
           
           
            private function socketDataHandler(event:ProgressEvent):void
            {
                var message:String = socket.readUTFBytes(socket.bytesAvailable);
                trace("message = " + message);
                taChat.text += message + "\n";
            }

            protected function inputChat_enterHandler(event:FlexEvent):void
            {
                if (inputChat.text.length < 1)
                {
                    return;
                }
                socket.writeUTFBytes("[" + labelId.text + "] : " + inputChat.text);
                socket.flush();
                inputChat.text = "";
            }

            protected function btnChat_clickHandler(event:MouseEvent):void
            {
                if (inputChat.text.length < 1)
                {
                    return;
                }
                socket.writeUTFBytes("[" + labelId.text + "] : " + inputChat.text);
                socket.flush();
                inputChat.text = "";
            }
        ]]>
    </fx:Script>
    <s:states>
        <s:State name="disconnect"/>
        <s:State name="connect"/>
    </s:states>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:TextInput id="inputId" x="231" y="10" includeIn="disconnect"/>
    <s:Button id="btnConenctClose" x="367" y="11" label="접속" label.connect="종료"
              click.connect="btnConenct_clickHandler(event)" click.disconnect="btnDisconnect_clickHandler(event)"/>
    <s:Label id="labelId" includeIn="connect" x="211" y="10" width="159" height="21"/>
   
    <s:TextArea id="taChat" includeIn="connect" x="10" y="49" width="621" height="402"/>
    <s:TextInput id="inputChat" includeIn="connect" x="10" y="459" width="507" enter="inputChat_enterHandler(event)"/>
    <s:Button id="btnChat" includeIn="connect" x="526" y="459" label="Button" click="btnChat_clickHandler(event)"/>
</s:Application>[/code]

아래 프로그램 설치하고 실행시켜놓은 뒤, 아래사이트를 접속해서 테스트할 수 있어요. 아이피는 127.0.0.1로 하드코딩되어있기 때문에 자기 pc에서 띄워놓고 자기가 접속한 클라이언트에서만 가능해요^^
클라이언트 접속 http://mudchobo.tomeii.com/swf/ClientSocketTest.swf


 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 지돌스타 2009.11.26 02:08  댓글주소  수정/삭제  댓글쓰기

    멋지고 좋은 글입니다. 잘보고 갑니다.

  2. 푸카 2009.11.29 07:37  댓글주소  수정/삭제  댓글쓰기

    빠른 좋은글 감사합니다^^. 잘보고 갑니다.

  3. z 2010.02.18 15:55  댓글주소  수정/삭제  댓글쓰기

    zzzzzzzzzzzzzzzzzzzzzz

  4. 싸부 2010.06.21 18:39  댓글주소  수정/삭제  댓글쓰기

    이게 air로 서버소켓이 리눅스나 유닉스쪽에서도 가능해진건가요?

    • 머드초보 2010.06.26 15:27  댓글주소  수정/삭제

      음....air에서 소켓은 무조건 gui가 있어야 하기 때문에....
      리눅스쪽에서 가능해지긴 합니다만, gui같은 게 있어야 합니다.
      그냥 air에서 서버가 필요한 이유는 뭐 p2p같은 게 가능하고, 더욱 다양한 어플개발이 가능하기 때문입니다^^

  5. 2010.11.03 18:56  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2010.11.05 21:23  댓글주소  수정/삭제

      음 air에서 그냥serversocket하나 열어놓으시고
      나머지 swf파일들로 거기에 접속하면 될 것 같은데요^^
      이거 Client에서 서버쪽 진짜 아이피로 접속하면 됩니다^^

  6. S 2010.12.02 17:49  댓글주소  수정/삭제  댓글쓰기

    좋은 글 잘 보고 갑니다 ^^

 
Adobe AIR 2에서는 제한적으로 프로세스를 실행할 수 있는 기능이 추가되었습니다. NativeProcess클래스인데요. 이 기능을 제공하는 이유는 그런 것 같습니다. AIR에서 안되는 거 C로 짠거랑 데이터 통신할 수 있는 게 주 목적인 듯.
Java처럼 그냥 exe파일 실행하는 것은 안되구요. 샌드박스에 위배되지 않는 파일만 실행할 수 있습니다.
즉, document폴더나 application폴더, 또는 applicationstorage 등과 같은 폴더에 있는 것은 자동으로 실행시킬 수 있지만, 그 외에 파일들은 사용자가 선택한 것만 실행할 수 있습니다. 즉, 열 수 있는 파일과 샌드박스안에 있는 파일만 실행할 수 있다는 얘기죠^^
안그럼 이런 에러가 뜨는 것 같습니다.
NativeProcessStartupInfo.executable does not specify a valid executable file.

뭐 사용자가 exe파일 선택만 해준다면야 모든 실행파일을 실행할 수 있네요.

우선 플래시 빌더를 다운로드를..ㅠㅠ
Flash Builder4 beta2 다운로드

아래 예제는 그냥 실행파일 선택하면 그 실행파일을 실행해주는 예제입니다.
[code]<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/halo">
    <fx:Script>
        <![CDATA[
            import flash.events.Event;
            import flash.events.MouseEvent;
            import flash.net.FileFilter;
           
            import mx.controls.Alert;
           
            private var nativeProcess:NativeProcess;
           
            protected function btnRun_clickHandler(event:MouseEvent):void
            {
                if (NativeProcess.isSupported)
                {
                    var file:File = new File();
                    file.addEventListener(Event.SELECT, selectHandler);
                    file.browseForOpen("실행파일을 선택하세요", [new FileFilter("exe파일", "*.exe")]);
                }
                else
                {
                    Alert.show("NativeProcess를 지원하지 않음.");                       
                }
            }
           
            private function selectHandler(event:Event):void
            {
                var file:File = event.currentTarget as File;
               
                var nativeProcessStartupInfo:NativeProcessStartupInfo = new NativeProcessStartupInfo();
                //var args:Vector.<String> = new Vector.<String>;
                //args.push("/root,c:\\");
                //nativeProcessStartupInfo.arguments = args;
                nativeProcessStartupInfo.executable = file;
                nativeProcess = new NativeProcess();
                nativeProcess.start(nativeProcessStartupInfo);
               
            }
        ]]>
    </fx:Script>
   
    <s:Button id="btnRun" label="실행파일 선택"  click="btnRun_clickHandler(event)"/>
</s:WindowedApplication>[/code]
예제의 핵심은 NativeProcess클래스랑 NativeProcessStartupInfo클래스입니다.
NativeProcessStartupInfo클래스에 해당 file이랑 arguments를 넣어주고, NativeProcess로 실행하면 됩니다^^
그리고 Descriptor파일에 한줄 추가해줘야 하는 것 같은데요.
[code]<supportedProfiles>extendedDesktop</supportedProfiles>[/code]
왜 추가 안해도 잘 되지-_- 깜빡하고 추가안했는데, 잘 돼요. 이거 추가안하면 통신이 안되나...암튼 잘 모르겠네요.
근데, 저거 추가하고, Flash Builder에서 제공하는 빌더로 export하면 에러납니다-_-
[code]Error creating AIR file: NativeProcessTest-app.xml: error 306: Descriptor must support the profile desktop, mobileDevice, or extendedMobileDevice.[/code]
이거 웬지 Builder에서 제공하는 것은 1.5로 패키징하는 것 같음-_-
암튼, 저거 빼고 해서 air파일을 만들어도 실행하면 설치가 안됩니다. 이런 에러메세지가 나는군요.
[code]This application has been designed for a different device profile and will not run on your system. Contact the application author to see if a compatible version is available.[/code]
그래서 adt를 이용해서 command line으로 직접 패키징을 해야합니다. 아.....난 콘솔이 싫은데.....

우선 amxmlc로 컴파일합니다.
[code]amxmlc NativeProcess.mxml[/code]
그러면 NativeProcess.swf파일이 생김.
이제 패키징을 하면 됩니다.
[code]adt -package -storetype pkcs12 -keystore c:\Users\mudchobo\Documents\mudchobo.p12 -target native NativeProcessTest.exe NativeProcessTest-app.xml NativeProcessTest.swf[/code]
-keystore값에는 자기인증서 경로를 적구요. -target은 native로 하면 exe파일로 생성됩니다.
그리고 포함할 파일을 적으면 되구요.
그리고 빌더로 만든 *-app.xml파일을 보시면 <content>태그가
[code]<content>[This value will be overwritten by Flash Builder in the output app.xml]</content>[/code]로 되어있는데, 이거 swf파일 경로 적어 줘야합니다.
[code]<content>NativeProcess.swf</content>[/code]
비밀번호 입력하면 NativeProcessTest.exe파일이 생성되는데, 그거 실행하면 설치화면이 뜹니다.
이 설치파일은 기존 air파일로 패키징한거랑 틀리게 배경이 흰색이네요-_-

사용자 삽입 이미지
암튼, 설치하고 나면 실행 잘 됩니다.

※참고로 제가 탐섹히를 실행하려고 해봤는데, 이런 기능이 필요한 이유가 어떤 다운로드프로그램에서 사용자가 받은 파일이 있는 폴더를 손쉽게 열 수 있게 하는 것이 목표인데, 그럴려면 탐색기 실행이 필요합니다.
그래서 그냥 패키징할 때 탐색기 프로그램을 같이 패키징해버리면 보안샌드박스에 있기 때문에 실행할 수 있습니다-_-
c:\Windows\explorer.exe파일을 복사해서 패키징해버리고, 실행하면 탐색히가 뜹니다.
해당 폴더를 여는건 explorer.exe /root,c:\\로 하면 된다는.......-_-

마지막으로 다시한번 플래시빌더를 다운로드를-_-

Flash Builder4 다운로드

참고자료
http://www.adobe.com/devnet/air/flex/quickstart/interacting_with_native_process.html
 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon sewonist 2009.11.25 00:51  댓글주소  수정/삭제  댓글쓰기

    와우~ AIR2 에서 달라진게 뭐가 있는 두리번 거리고 있었는데 이건 조금 대박이네요. 결국 샌드박스 안에만 있으면 .exe 파일을 실행 시킬 수 있다는 의미인거죠? 당장 내일 테스트 해봐야겠습니다.

    • 머드초보 2009.11.25 11:29  댓글주소  수정/삭제

      네 자기가 c나 다른 언어로 짠 것을 air에 포함시켜서 그 프로그램과 통신도 할 수 있죠.
      방문해주셔서 감사해요~ ^^

  2. flex초보 2010.03.11 14:08  댓글주소  수정/삭제  댓글쓰기

    근데요 꼭 c로 짠 exe만 실행할 수 있나요???
    C# 으로 짠 exe는 상호 통신할 수 없나요???
    혹 있으시면 알려주세요 ㅠ

  3. BlogIcon grimtong 2010.04.05 16:01  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 우선 정보 감사합니다.
    Flex3에서 NativeProcess관련해서 설명해주신대로 수동 컴파일까지는 성공했습니다.
    컴파일된 exe파이를 설치를 하고 실행까지는 잘 동작합니다.

    그런데 nativeProcess관련하여 commandLine 명령어를 지원하는 exe 프로그램이 AIR에서 start를 하면
    커맨트창이 잠깐 보였다가 실행되고 꺼집니다...
    이창을 숨기거나 안나오게 하는 방법이 있는지요?

    ^____________^

    • 머드초보 2010.04.09 10:19  댓글주소  수정/삭제

      안녕하세요~
      저도....NativeProcess그냥 나왔을 때 잠깐 해본거라ㅠㅠ
      잘 모르겠습니다 ㅠㅠ

  4. 2010.07.27 16:42  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 머드초보 2010.07.28 20:47  댓글주소  수정/삭제

      음...혹시...프로젝트이름을 NativeProcess로 하셨나요?
      몬가 충돌이 일어나는 것 같아보이는데....

  5. 플래시 2010.09.24 13:52  댓글주소  수정/삭제  댓글쓰기

    머드초보님..
    저는 플렉스는 모르지만 플래시는 아는데요.
    Flash CS5로 그냥 텍스트툴을 사용해서 t라는 텍스트필드를 만들고
    t.text = NativeProcess.isSupported.toString();
    이렇게 액션을 준 후 app xml에는 다음과 같이 주었습니다.
    <supportedprofiles>extendedDesktop desktop</supportedprofiles>
    그리고 플래시 속성창의 Air2 설정의 [편집]버튼을 눌러서 제작 버튼을 누르면 air파일은 만들어지는데
    <supportedprofiles>노드가 없어져 버립니다.
    그래서 air파일을 실행해서 보면 텍스트필드에도 NativeProcess.isSupported가 false로 나옵니다.

    <supportedprofiles>extendedDesktop</supportedprofiles> 이렇게만 줘도 없어져 버리거든요.
    어떻게 하면 <supportedprofiles>노드를 없애지 않을 수 있을까요?

    • 머드초보 2010.11.05 19:21  댓글주소  수정/삭제

      음...이거 베타 때 한것이고, Flash에서 해보질 않아서 잘 모르겠네요ㅠㅠ
      뭔가 정책이 바뀐 것 같은데, adobe 문서를 확인해보시면ㅠㅠ

 
문서를 보니..... 향상된 것인지 아님 기능이 더 추가되고 복잡해진 것인지 모르겠군요-_- 익숙해지면 편하겠지만, 지금은 현재 Flex3방식의 state가 더 편해보이네요-_-

예전에 어도비에서 RIA CAMP할 때 STATE기능의 향상이 있을 것이라고 해서 찾아보니 완전히 바뀌었네요.

우선 Flash Builder 4 Beta 2를 다운로드 해서 설치를.....ㅠㅠ

기존 Flex3 방식은 <mx:states> 태그안에 <mx:State>태그가 있는데, 이 안에서 AddChild나 SetProperty태그를 이용해서 해당 state로 변경이 되면 컴포넌트를 삭제하고 추가하거나 값을 변경하는 코드가 들어갔었죠.

Flex4 방식은 위와 같은 방식에서 완전히 바뀌었습니다(사실..flex4로 오면서 거의다 바뀌었다는....).
<s:states>태그안에 <s:State>태그가 들어가는 것은 같지만, 여기서 컴포넌트를 추가하거나 변경하는 코드는 들어가지 않습니다. 그냥 다음에 xml코드에서 해당 컴포넌트들에게 적용을 하게 됩니다. 컴포넌트 태그에 해당 state값에 맞는 코드를 중복으로 선언할 수 있습니다.
해당 State의  Name들이 default, register라면 <s:Panel title="제목" title.register="등록제목" /> 이런형태로 써버리면 currentState가 register로 바뀌면 자동으로 Panel의 title이 title.register값으로 변경이 되게 됩니다.

상세한 기능 및 설명은 Flex 4문서를 보시는 게 더 빠를 듯 합니다.
http://help.adobe.com/en_US/Flex/4.0/UsingSDK/WS2db454920e96a9e51e63e3d11c0bf69084-7fb4.html
http://www.artima.com/articles/flex_4_states.html

1. 디자인모드에서 state설정
그럼 Flash Builder를 실행해서 여기서 State를 설정해봅시다.
Flex로 프로젝트를 하나 만듭니다.
Design모드로 전환!
오른쪽 States View가 있는데, 거기에서 New State를 선택해서 추가합니다.
addbutton이라는 state를 추가합니다. 그리고 기존에 있던 State1은 default로 변경합니다.
그럼 states를 addbutton으로 맞춰놓고, 버튼을 드래그해서 놓습니다.
그리고 Source뷰로 바꾸면 이런 코드가 완성이 되네요^^
[code]<?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:states>
        <s:State name="default"/>
        <s:State name="addbutton"/>
    </s:states>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Button includeIn="addbutton" x="308" y="231" label="Button"/>
</s:Application>[/code]
Button에 includeIn이라는 attribute값이 들어가있고 거기에 state값이 있네요. 이 버튼은 addbutton일 때만 보이겠다는 것이죠. 아마 이 새로 바뀐 state형태는 기존 ui를 구성하는 mxml코드를 최대한 방해받지 않게 하려고 만든 것 같습니다.
그리고 재미있는 것은 편집 상단에 Show state: 부분이 있는데, state를 default로 바꾸면 button태그부분이 회색으로 dim처리가 되어버립니다-_- 와 멋지네요. UI작성에 방해받지 않게 하려는 배려인 듯.
사용자 삽입 이미지

dim처리...-_-

2. STATE별 이벤트 추가
state별 이벤트 뿐만 아니라 뭐 다양하게 추가할 수 있어요.
Design모드에서 그냥 addbutton state선택하고, 아까 추가한 버튼을 누르면 Properties를 설정할 수 있는데 In states를 클릭해서 All States로 바꿔줍시다^^
그러면 default에서도 나오는데, 버튼선택해서 오른쪽버튼 누르면 generate Service Call이라는 것이 있는데, 그걸 누르면 자동으로 click이벤트 함수를 만들어줍니다.
두개의 state에서 동시에 만들어버리면 이렇게 나오는군요.
사용자 삽입 이미지

신기함-_- 다른 state함수만 dim처리됨-_-

그 외의 자세한 기능은 문서를 참조하면 돼요. 문서에 샘플예제가 참 잘 나와 있습니다. Flex의 장점은 잘 된 문서화(?)가 아닐까합니다. 한글화가 안되어있는 것이 아쉽긴 하지만....ㅠㅠ

빌더는 여기서 다운로드를...ㅠㅠ


 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon 찬욱 2010.04.08 16:47  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.
    요즘 플렉스 공부를 시작했는 데 유용한 글이 많아 많은 도움 받고 있습니다.
    감사합니다. 수고하세요~

    • 머드초보 2010.04.09 10:43  댓글주소  수정/삭제

      앗~ 찬욱님 반갑습니다^^ 잘지내시죠?^^
      저도 플렉스허접이라-_-
      자바랑 참 잘 맞는 프레임워크라 참 좋아요~

 
기존에 Flex3에서 HTTPService는 직접 xml을 확인하여 valueobject를 만들던지 자동으로 파싱해주는 객체를 그냥 쓰던지 그런식으로 했었는데요. Flex4에서는 쉽게 Service객체를 만들어주고, ValueObject를 만들어줍니다.

근데, 그냥 기존방식을 쓰는 게 나을 것 같아요-_- 뭔놈의 클래스를 뭐이리 만들지-_-
그래도 제공하는 기능이니까 한번 사용해서 만들어봅시다-_-

다운로드는 여기서....-_-

1. 프로젝트 생성

New -> Flex Project -> Application type은 Desktop(구글의 httpservice를 가져올 것이어서 데탑으로-_-) -> Finish.

2. 서비스생성
구글에서 날씨를 가져오는 Service를 추가해봅시다.
아래 Data/Services탭에서 Connect to Data/Service..클릭.
HTTP선택 -> Opearation 첫번째 항목에 getWeather로 변경. url은 http://www.google.co.kr/ig/api?weather=seoul 입력.
url만 입력하면 ?붙은 파라메터는 자동으로 Parameters탭에 들어갑니다. weather가 들어가는데, 캐쉬문제 때무넹 캐쉬파라메터도 하나 추가합니다.
Add누르고, cache입력하고, DataType은 Number.
Service name은 WeatherService로 하죠. Finish.

여기서 이름 네이밍룰에 주의해야하는데, Service name이랑 parameter name이랑 같은 게 있으면 안됩니다. 에러나더군요.

3. 서비스에 대한 리턴타입 지정
이제 리턴타입을 지정해줘야하는데요.
그전에 데이터를 잘 가져오는지 테스트를 해볼 수 있습니다.
방금 생성한 WeatherService의 getWeather를 선택하고 오른쪽버튼 누르면 Configure Return Type이 있습니다.
이거 선택하기전에 Test Opearation이라고 해서 테스트를 해볼 수 있는 게 있네요.
테스트를 쉽게 해볼 수 있어서 참 좋네요. Test를 누르면 데이터를 가져와서 Tree View 또는 Raw View로 볼 수 있습니다.
사용자 삽입 이미지
근데, 이상한 점을 발견했는데, 네이버오픈API나 몇몇 XML은 이걸로 하면 잘 안돼요. 왜그런지 모르겠네요. 이런 에러메세지를 뿜습니다.
InvocationTargetException:The response is not a valid XML or a JSON string
이거 왜그런지 아시는 분은 답변좀....굽신굽신....-_-

암튼, 날씨API는 되니까 해봅시다-_-
Configure Return Type선택 후, Auto-detect the return type from sample data선택. 뭐 그냥 object로 받아도 되는데, 필요한 데이터만 가져온다고 하면 쉽게 valueobject를 만들 수 있는 장점이 있어요.
파라메터 방식을 선택하고, seoul을 입력합니다.
그러면 결과값이 나오는데, 여기서 필요한 건 현재 날씨이기 때문에 select root에서 current_conditions를 선택합니다. 그리고, Finish를 누르면 valueObject가 만들어지는데, 한개의 node마다 클래스가 2개씩 생겨요-_-

4. UI생성
UI를 만들어봅시다.
[code]<s:Button id="btnClose" label="X"  width="29" x="67" y="1" click="btnClose_clickHandler(event)"/>
<mx:Image x="6" y="32" width="40" height="40" id="imgIcon"/>
<s:Label x="0" y="80" width="100" textAlign="center"  id="labelCondition" />
<s:Label x="59" y="45" text="30º" fontSize="20" id="labelTemp"/>[/code]
초간단 UI...UI라고 불리지도 못하는 UI.

5. 데이터 바인딩
이제 데이터를 불러와야해요.
Design모드로 가서 아무라벨이나 붙잡고, 오른쪽버튼 눌러서 Bind To Data를 선택. 그리고 방금 만든 서비스를 바인딩해요. 전 labelCondition을 선택했는데, 그러면 WeatherService, getWeather의 condition을 선택합니다.
그러면 service태그도 알아서 들어가고 해당 라벨에는 creationComplete이벤트핸들러가 추가되었네요.
getWeather파라메터는 "seoul"과 cache는 랜덤값을 넣어줍니다.
그리고 실제 태그에는 condition.@data에 값이 있기 때문에, label의 text부분에 getWeatherResult.lastResult.condition.data로 변경해주시면 됩니다.
[code]protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
{
    getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
}[/code]

6. 완성된 코드
그럼 1분마다 데이터를 가져오는 타이머도 만들고 윈도우틀도 없애서 위젯형태로 만들어버리고, 나머지 label에도 값에 맞게 바인딩 시키면 완성이 됩니다.
[code]<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/halo"
                       xmlns:weatherservice="services.weatherservice.*"
                       width="150" height="150"
                       applicationComplete="windowedapplication_applicationCompleteHandler(event)"
                       showStatusBar="false"
                       backgroundColor="#48FF00">
    <fx:Script>
        <![CDATA[
            import flash.events.MouseEvent;
            import flash.events.TimerEvent;
            import flash.utils.Timer;
           
            import mx.controls.Alert;
            import mx.events.FlexEvent;
           
            import spark.components.mediaClasses.VolumeBar;
           
            private var timer:Timer;
           
            protected function windowedapplication_applicationCompleteHandler(event:FlexEvent):void
            {
                timer = new Timer(1000, 0);
                timer.addEventListener(TimerEvent.TIMER, timerHandler);
                timer.start();
            }
           
            protected function btnClose_clickHandler(event:MouseEvent):void
            {
                nativeWindow.close();
            }

            protected function labelCondition_creationCompleteHandler(event:FlexEvent):void
            {
                requestData();
            }

            protected function labelTitle_mouseDownHandler(event:MouseEvent):void
            {
                nativeWindow.startMove();
            }
           
            protected function timerHandler(event:TimerEvent):void
            {
                requestData();               
            }
           
            private function requestData():void
            {
                getWeatherResult.token = weatherService.getWeather("seoul", Math.random());
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:CallResponder id="getWeatherResult"/>
        <weatherservice:WeatherService id="weatherService" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"/>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
    <s:Label id="labelTitle" text="서울날씨"  x="0" y="0" height="22" width="121" verticalAlign="middle" mouseDown="labelTitle_mouseDownHandler(event)"/>
    <s:Button id="btnClose" label="X"  width="30" x="120" y="1" click="btnClose_clickHandler(event)"/>
    <mx:Image x="25" y="47" width="40" height="40" id="imgIcon" source="http://www.google.co.kr{getWeatherResult.lastResult.icon.data}"/>
    <s:Label x="2" y="114" width="148" textAlign="center"  id="labelCondition"  creationComplete="labelCondition_creationCompleteHandler(event)" text="{getWeatherResult.lastResult.condition.data}"/>
    <s:Label x="92" y="58" fontSize="20" id="labelTemp" text="{getWeatherResult.lastResult.temp_c.data}ºC"/>
</s:WindowedApplication>
[/code]

7. Network Monitor로 데이터 확인
HTTPService 요청이 제대로 갔는지 확인할 수 있는 Network Monitor 기능이 추가되었습니다. 기존에는 확인하려면 직접 그냥 노가다로 해보거나 와이어샤크나 firebug 등의 http watcher를 이용해서 했었죠. 근데 이제 Flash Builder에 포함되어져있네요. 그냥 모니터링 하는거라 조금만 만져보면 알 수 있네요. row데이터로 볼 수도 있고, hex데이터로도 볼 수 있고 그러네요. 근데 한글이 깨지네-_-
사용자 삽입 이미지
사용자 삽입 이미지


 
Posted by 머드초보

댓글을 달아 주세요

  1. BlogIcon hansoo 2010.02.09 13:46  댓글주소  수정/삭제  댓글쓰기

    좋은 예제 잘 보고 갑니다.

  2. AZAMARA 2010.05.02 01:25  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 머드초보님 Flex공부하는데 머드초보님 블로그 덕분에 많이 배우네요

    저도 빌드4 XML가져올때 같은 에러때문에 고생중이네요 ㅡㅜ ㅋ 컬렉션형태로오면

    그러네요

    • 머드초보 2010.05.02 10:36  댓글주소  수정/삭제

      음....그렇군요
      컬렉션형태로 가져올 때 그렇게 되는 것이군요.
      방문해주셔서 감사해요~

  3. AZAMARA 2010.05.05 23:41  댓글주소  수정/삭제  댓글쓰기

    확인해보니 jsp에서 xml 반복문 돌릴때만안되네요

    s:iterater c:foreach 스크립트릿 다 써서해봤는데 명령문을 그냥 텍스트로 인식해서 오류라는거같아요

    그냥 적혀있는 xml은 문제되지않네요 ㅇ.ㅇ

    • 머드초보 2010.05.06 00:00  댓글주소  수정/삭제

      음 그렇군요!
      저도 이제 Flash Builder 정식버전을 설치해서!
      삽질을 해봐야겠습니다!
      좋은 정보 감사합니다^^

  4. 쿨해요 2010.07.18 17:08  댓글주소  수정/삭제  댓글쓰기

    오늘 시점의 flash builder 4 trial 버젼에서는 최종 UI소스
    상단을 아래와 같이 고쳐야 에러가 없습니다.

    - 아래-

    <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:weatherservice="services.weatherservice.*"
    width="150" height="150"
    applicationComplete="windowedapplication_applicationCompleteHandler(event)"
    showStatusBar="false"
    backgroundColor="#48FF00">
    <fx:Script>

 
테스트의 중요성은 저번 스프링 강의시간에 충분히 느꼈습니다. 하지만, 역시 귀차니즘 때문에 잘 안만들게 되는 게 테스트인 듯 합니다. 하지만, 한번 만들어놓으면 이래저래 매우 유용한 것이 테스트죠.

다운로드는 여기서....-_-


Java에서는 Junit이라는 것이 있는데, 4버전에서는 Annotation(@)을 이용해서 쉽게 테스트를 만들곤 했는데요. Flex에서도 비슷한 기법을 이용합니다. 메타데이터를 위에다가 붙여서-_- 손쉽게 테스트를 만들 수 있습니다.
[code][Test]
public function testMyTest():void
{
}[/code]
와....Flash Builder를 보면 Java를 많이 따라한 것을 볼 수 있습니다. 예전엔 폴더생성으로만 만들 수 있었던 패키지가 직접적인 패키지 생성메뉴를 만들어서 Package Explorer에서 패키지형태로 볼 수 있습니다.
암튼, 자바와 닮아가는 듯-_-

1. 간단한 프로젝트 생성
Flex Project하나 생성.

2. 서비스 클래스 생성
덧셈 뺄셈 클래스하나 작성
com.mudchobo.test패키지의 Calc클래스 생성
Calc.as
[code]package com.mudchobo.test
{
    public class Calc
    {
        public function Calc()
        {
        }
       
        public function addition(a:Number, b:Number):Number
        {
            return a + b;
        }
       
        public function subtraction(a:Number, b:Number):Number
        {
            return a - b;   
        }
    }
}[/code]

3. 테스트 생성
New -> Test Case Class -> New FlexUnit 4 test선택. Name은 CalcTest로하고 Finish.
CalcTest.as
[code]package flexUnitTests
{
    import com.mudchobo.test.Calc;
   
    import flexunit.framework.Assert;

    public class CalcTest
    {
        private var calc:Calc;
       
        public function CalcTest()
        {
        }
       
        [Before]
        public function before():void
        {
            calc = new Calc();   
        }
       
        [Test]
        public function testAddition():void
        {
            var result:Number = calc.addition(50, 50);
            Assert.assertEquals(result, 100);
        }
       
        [Test]
        public function testSubtraction():void
        {
            var result:Number = calc.subtraction(50, 50);
            Assert.assertEquals(result, 0);
        }
    }
}[/code]
여기까지 작성하면 FlexUnitCompiler.mxml이라는 파일이 자동으로 생겼을겁니다. 이건 건드리지 않습니다. 검색해보니 이전방식으로 core생성해서 UIListener를 넣어서 하려고했는데, 이거 그렇게 하는게 아니더군요-_-
심지어 UIListener클래스는 없습니다-_-

4. 테스트 실행
테스트 실행은 package explorer에서 테스트클래스의 오른쪽 마우스버튼을 누르면 "Execute FlexUnit Tests"라는 메뉴가 있습니다. 그걸 선택하면 테스트가 됩니다. FlexUnitApplication.mxml파일도 같이 생기는군요.
테스트단축키는 Alt + Shift + E, F입니다....-_- 해당 편집파일에서 누르면 됩니다.
사용자 삽입 이미지
테스트 결과는 Junit처럼 이렇게 보여줍니다.
사용자 삽입 이미지
Junit이랑 똑같네.

5. 테스트 Suite 생성
이건 테스트를 다 모아서 실행하는 건데, 통합테스트를 할 때 사용하는 듯-_-
New -> Test Suite Class -> New FlesxxUnit4 test 하면 include할 테스트를 패키지에서 선택할 수 있습니다. 선택 후 Finish.
SuiteTest.as
[code]package flexUnitTests
{
    import flexUnitTests.CalcSecondTest;
    import flexUnitTests.CalcTest;
   
    [Suite]
    [RunWith("org.flexunit.runners.Suite")]
    public class SuiteTest
    {
       
        public var test1:flexUnitTests.CalcSecondTest;
        public var test2:flexUnitTests.CalcTest;
    }
}[/code]
그냥 저렇게 선언해두고, 오른쪽버튼 눌러서 "Execute FlexUnit Tests" or 알트 + 쉬프트 + E, F하면 통합테스트를 합니다.

6. 그외의 기능
저도 더 써봐야 알 것 같은데, Flex는 이벤트기반이다보니 Async와 UI구조가 많은데요. Async구조도 손쉽게 테스트를 할 수 있습니다.
[code][Test(async,ui)][/code]
이런식으로 하면 될 듯 한데, 안해봐서 잘 모르겠네요-_-

참고자료
http://www.insideria.com/2009/05/flashbuilder4-will-support-fle.html
http://balajisridhar.wordpress.com/2009/10/05/flexunit-integration-in-flash-builders-new-awatar-in-beta2/
 
Posted by 머드초보

댓글을 달아 주세요