Microsoft Outlook 2007에서 최소화버튼을 누르면, 창이 사라지면서 작업표시줄에서도 같이 사라지고, 트레이아이콘으로만 남게 됩니다.
그것을 AIR에서도 구현할 수 있습니다.

AIR에서는 윈도우의 DisplayState를 캐치할 수 있는데요. 즉, 최소화, 최대화 이런 이벤트가 발생하는 것을 잡을 수 있습니다. 그래서 만약 위와 같은 기능을 구현하고자 한다면 이렇게 하면 됩니다.

최소화이벤트를 잡은 뒤, 창의 visible을 false로 바꾸고, trayicon의 icon이미지를 넣어주면 됩니다.
또, 창을 다시 원래 대로 돌리려면, trayicon에 이벤트를 걸어서 클릭 시, 창의 visible을 true로 바꿔주고, trayicon의 icon이미지를 삭제하면 됩니다.

http://help.adobe.com/en_US/AIR/1.1/devappsflex/WS5b3ccc516d4fbf351e63e3d118666ade46-7dcb.html

음....저는 찾는데 하루를 소비했습니다만-_-;(머리가 딸려서-_-) 찾은 문서는 Adobe AIR에 있는 "Developing Adobe AIR 1.1 Applications with Flex"문서군요 ㅠ

아래는 제가 구현해봤습니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    creationComplete="creationCompleteHandler()">
    <mx:Script>
        <![CDATA[

            private var icons:Loader = new Loader();
                   
            private function creationCompleteHandler():void
            {
                makeTrayIcon();
                icons.contentLoaderInfo.addEventListener(Event.COMPLETE, iconLoadComplete);
               
                nativeWindow.addEventListener(
                        NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, onChange);
            }
           
            private function onChange(event:NativeWindowDisplayStateEvent):void
            {
                if (event.afterDisplayState == NativeWindowDisplayState.MINIMIZED)
                {
                    event.preventDefault();
                    nativeWindow.visible = false;
                   
                    icons.load(new URLRequest("icons/icon_16.png"));
                }
            }
           
            private function makeTrayIcon():void
            {
                var iconMenu:NativeMenu = new NativeMenu();
                var visibleCommand:NativeMenuItem = iconMenu.addItem(new NativeMenuItem("Visible"));
                visibleCommand.addEventListener(Event.SELECT, function(event:Event):void {
                    nativeWindow.visible = true;
                    NativeApplication.nativeApplication.icon.bitmaps = [];
                });
               
                var exitCommand:NativeMenuItem = iconMenu.addItem(new NativeMenuItem("Exit"));
                exitCommand.addEventListener(Event.SELECT, function(event:Event):void {
                    NativeApplication.nativeApplication.icon.bitmaps = [];
                    NativeApplication.nativeApplication.exit();
                });
               
                var systray:SystemTrayIcon =
                NativeApplication.nativeApplication.icon as SystemTrayIcon;
                systray.menu = iconMenu;
            }
        
            private function iconLoadComplete(event:Event):void
            {
                NativeApplication.nativeApplication.icon.bitmaps =
                    [event.target.content.bitmapData];
            }

        ]]>
    </mx:Script>
</mx:WindowedApplication>
[/code]
핵심은
[code]
nativeWindow.addEventListener(
                        NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, onChange);

private function onChange(event:NativeWindowDisplayStateEvent):void{
    if(event.afterDisplayState == NativeWindowDisplayState.MINIMIZED){
        event.preventDefault();
        event.target.visible = false;
    }
}
[/code]
우선 윈도우에 NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING 이벤트를 겁니다. 이것은 윈도우의 상태가 변경되었을 때 발생합니다.
최소화버튼을 누르거나 작업표시줄에서 버튼을 클릭하여 최소화 할 때 이벤트가 발생합니다.
event.preventDefault()를 호출하게 되면 디폴트동작을 취소하게 됩니다. 그리고 창만 숨기는거죠. 창을 숨기면 작업표시줄에도 사라집니다. 그리고, trayicon을 만들면 됩니다.

후.....달이차오르니 가야겠습니다.

 
Posted by 머드초보
,
 
두번째글입니다.
프로그램 설치 및 체험(?)은 아래의 INSTALL NOW을 클릭해주세요 ^^

Alternative content

Get Adobe Flash player


이제 air프로젝트를 하나 생성합니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="vertical" width="300" height="300"
    horizontalAlign="center" verticalAlign="middle"
    verticalScrollPolicy="off" horizontalScrollPolicy="off">
   
    <mx:Script>
        <![CDATA[
            import flash.net.navigateToURL;
            import mx.rpc.events.FaultEvent;
            import mx.controls.Alert;
            import mx.rpc.events.ResultEvent;
            import com.mudchobo.MudchoboOAuth;
           
            private var mudchoboOAuth:MudchoboOAuth;
            private var consumerToken:String = "자신의 Consumer Token";
            private var consumerSecret:String = "자신의 Consumer Secret";
            private var requestTokenURL:String = "http://www.storyq.net/oauth/request_token";
            private var accesstokenURL:String = "http://www.storyq.net/oauth/access_token";
            private var authorizeURL:String = "http://www.storyq.net/oauth/authorize";
           
            private function login():void
            {
                // RequestToken 요청
                mudchoboOAuth = new MudchoboOAuth(consumerToken, consumerSecret);
                mudchoboOAuth.requestOAuth(new Object(), requestTokenURL, "POST",
                    requestTokenResultHandler, requestTokenFaultHandler);
                currentState = "stateRequestToken";
            }
           
            private function logout():void
            {
                mudchoboOAuth = null;
                currentState = "";   
            }
           
            private function loginConfirm():void
            {
                // AccessToken요청
                mudchoboOAuth.requestOAuth(new Object(), accesstokenURL, "POST",
                    accessTokenResultHandler, accessTokenFaultHandler);
            }
           
            private function loginCancel():void
            {
                Alert.show("로그인에 실패했습니다.");   
                currentState = "stateStart";
            }
           
            // RequestToken 요청 성공 시
            private function requestTokenResultHandler(event:ResultEvent):void
            {
                currentState = "stateLogin";
                mudchoboOAuth.parseData(event.result.toString());
                authorizeHTML.location = mudchoboOAuth.authorizeSite(authorizeURL);
            }
           
            // RequestToken 요청 실패 시
            private function requestTokenFaultHandler(event:FaultEvent):void
            {
                Alert.show(event.fault.toString() + "요청이 잘못 되었습니다.");
                currentState = "stateStart";
            }
           
            // AccessToken 요청 성공 시
            private function accessTokenResultHandler(event:ResultEvent):void
            {
                currentState = "stateShowMyQ";
                mudchoboOAuth.parseData(event.result.toString());
               
                // 스토리큐에서 자신의 큐리스트를 가져온다.
                mudchoboOAuth.requestOAuth(new Object(), "http://www.storyq.net/boxes/mine.xml", "GET",
                    getQResultHandler, getQFaultHandler);
            }
           
            // AccessToken 요청 실패 시
            private function accessTokenFaultHandler(event:FaultEvent):void
            {
                Alert.show(event.fault.toString() + "요청이 잘못 되었습니다.");
                currentState = "stateStart";
            }
           
            // 자신의 큐리스트 가져오기 요청 성공 시
            private function getQResultHandler(event:ResultEvent):void
            {
                dgQ.dataProvider = event.result.boxes.box;
            }
           
            // 자신의 큐리스트 가져오기 요청 실패 시
            private function getQFaultHandler(event:FaultEvent):void
            {
                Alert.show(event.fault.toString() + "가져오는 도중 실패했습니다.");
            }
           
            // 버튼으로 다시 요청하기
            private function clickReplyHandler():void
            {
                mudchoboOAuth.requestOAuth(new Object(), "http://www.storyq.net/boxes/mine.xml", "GET",
                    getQResultHandler, getQFaultHandler);
            }
           
            // 더블클릭 시 해당 큐로 이동
            private function doubleClickDgQ():void
            {
                if (dgQ.selectedItem.uri != null)
                {
                    navigateToURL(new URLRequest(dgQ.selectedItem.uri), "_blank");
                }   
            }
        ]]>
    </mx:Script>
   
    <mx:states>
        <mx:State name="stateStart">
            <mx:SetProperty name="width" value="300"/>
            <mx:SetProperty name="height" value="300"/>
        </mx:State>
       
        <mx:State name="stateRequestToken">
            <mx:AddChild position="lastChild">
                <mx:Label text="잠시만 기다려주세요..."/>
            </mx:AddChild>
            <mx:SetProperty target="{btnLogin}" name="enabled" value="false"/>
        </mx:State>
       
        <mx:State name="stateLogin">
            <mx:SetProperty name="width" value="1024"/>
            <mx:SetProperty name="height" value="768"/>
            <mx:AddChild position="lastChild">
                <mx:HTML width="1024" height="642" id="authorizeHTML"/>
            </mx:AddChild>
            <mx:RemoveChild target="{btnLogin}"/>
            <mx:AddChild relativeTo="{authorizeHTML}" position="before">
                <mx:HBox id="hbox1">
                    <mx:Button label="확인" click="loginConfirm()"/>
                    <mx:Button label="취소" click="loginCancel()"/>
                </mx:HBox>
            </mx:AddChild>
            <mx:AddChild relativeTo="{hbox1}" position="before">
                <mx:Text text="아래 창에서 StoryQ사이트 로그인페이지로 이동합니다. &#xa;로그인 완료 후, 확인 버튼을 클릭하주세요." height="40"/>
            </mx:AddChild>
        </mx:State>
       
        <mx:State name="stateShowMyQ">
            <mx:RemoveChild target="{btnLogin}"/>
            <mx:AddChild position="lastChild">
                <mx:Button label="로그아웃" id="btnLogout" click="logout()"/>
                <mx:Button label="다시 가져오기" id="btnReply" click="clickReplyHandler()"/>
            </mx:AddChild>
            <mx:SetProperty name="width" value="550"/>
            <mx:SetProperty name="height" value="422"/>
            <mx:AddChild position="lastChild">
                <mx:DataGrid width="522" height="328" id="dgQ"
                    doubleClickEnabled="true" doubleClick="doubleClickDgQ()">
                    <mx:columns>
                        <mx:DataGridColumn headerText="제목" dataField="title"/>
                        <mx:DataGridColumn headerText="주소" dataField="uri"/>
                    </mx:columns>
                </mx:DataGrid>
            </mx:AddChild>
        </mx:State>
       
    </mx:states>
    <mx:Button label="스토리큐에 로그인" id="btnLogin" click="login()"/>
   
</mx:WindowedApplication>
[/code]
대충만들어서 코드가 지저분합니다만-_-;
아마 알아보실 수 있을듯(응?-_-)합니다.

프로그램을 실행하면, 로그인버튼이 있는데, 로그인 버튼을 클릭하면, 위에서 말한 RequestToken절차가 실행됩니다. RequestToken을 얻어오게 되면, AIR프로그램은 자신의 브라우저를 하나 열어서 로그인 RequestToken을 파라미터로 한 Authorize사이트로 이동합니다.
여기서 사용자는 자신의 아이디로 로그인을 합니다. 로그인이 완료되면, 서버쪽에서 AccessToken을 만들어놓습니다.
로그인 후 위에 확인버튼을 클릭하게 되면, 클라이언트에서는 AccessToken절차가 시작됩니다. AccessToken을 가져온 뒤 그 해당 AccessToken을 이용해 보호된 자원에 접근하는 과정입니다.

접근해서 가져온 데이터는 자신의 큐리스트를 보여주는 겁니다.
자신의 큐들을 데이터그리드에 넣었으며, 그 데이터그리드의 해당 칼럼을 더블클릭하게 되면 해당 큐로 이동하게 됩니다. 정말 별거없네-_-;

위에 코드는 위와 같은 절차로 되어있습니다.
혹시...Oauth로 삽질하시는 분은 이 글로 도움이 되었으면 하네요 ^^

이전 글 링크 - http://mudchobo.tomeii.com/tt/323
PS. 이 짓으로 추석을 날렸습니다-_-;
 
Posted by 머드초보
,
 
우선 OAuth인증에 대해서.....
잘 모르겠지만, 오픈되어있는 인증인 듯 합니다-_-;(응?)
어쨌든, 이걸 이용해서 인증을 하고, 인증이 되었으면, 보호된 자원으로부터 접근을 할 수 있습니다.
절차는 이렇습니다.

1. RequestToken얻어오기.
우선 매쉬업애플리케이션을 등록하면, consumer key와 consumer Secret을 받게 됩니다.
이것과 requestToken요청할 수 있는 주소를 알면 RequestToken을 얻을 수 있습니다.

2. Authorize하기.
RequestToken을 가지고, 인증페이지로 가서 로그인을 합니다. 로그인이 완료되면, 서버프로바이더에서는 해당 RequestToken에 대해서 AccessToken을 준비해놨을겁니다.

3. AccessToken얻어오기.
서버프로바이더가 준비한 AccessToken을 요청합니다. 이건 consumer key, consumer secret, requestToken, requestTokenSecret, AccessToken요청할 수 있는 주소를 알면 얻어올 수 있습니다.

4. 해당 AccessToken을 이용해서 자원에 접근하기.
이 AccessToken이 있으면 해당 자원에 접근할 수 있습니다. 이 토큰으로 보호된 자원에 접근할 수 있는 것이죠.

이런 절차의 인증방식을 사용합니다. 너무 간소화했지만, 여기에 가면 자세히 설명이 있습니다.
http://aproxacs.springnote.com/pages/1279246 (aproxacs님 감사해요~)

AIR에서도 OAuth인증을 사용하는 곳에 인증을 할 수 있습니다.
사실 오픈마루가 제공하는 귓속말을 하려고 했는데, 이건 잘 안되더라구요 ㅠ 운영자님께 문의해놨습니다 ㅠ

스토리큐로 해봅시다 ^^ 이 서비스 정말 획기적인데요? OAuth때문에 처음알았습니다 ^^

여기서 사용한 라이브러리는 as3용 oauth라이브러리 http://code.google.com/p/oauth-as3/
암호화 라이브러리 http://code.google.com/p/as3crypto/ 를 사용했습니다.

아...소스코드가 너무 기네-_-;
우선 제가 만든 OAuth요청 클래스입니다.
[code]
package com.mudchobo
{
    import mx.rpc.events.FaultEvent;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.http.HTTPService;
   
    import org.iotashan.oauth.OAuthConsumer;
    import org.iotashan.oauth.OAuthRequest;
    import org.iotashan.oauth.OAuthSignatureMethod_HMAC_SHA1;
    import org.iotashan.oauth.OAuthToken;
   
    public class MudchoboOAuth
    {
        private var consumer_key:String;
        private var consumer_secret:String;
        private var oauth_token:String = "";
        private var oauth_token_secret:String = "";
       
        public function MudchoboOAuth(consumer_key:String, consumer_secret:String)
        {
            this.consumer_key = consumer_key;
            this.consumer_secret = consumer_secret;
        }
       
        // OAuth요청
        public function requestOAuth(params:Object, url:String, method:String,
            resultHandler:Function, faultHandler:Function):void
        {
            var consumerToken:OAuthConsumer = new OAuthConsumer(consumer_key, consumer_secret);
            var oauthToken:OAuthToken = null;
            if (oauth_token != "")
            {
                oauthToken = new OAuthToken(oauth_token, oauth_token_secret);
            }
            var request:OAuthRequest = new OAuthRequest(
                method, url, params, consumerToken, oauthToken);
            var builtUrl:String = request.buildRequest(new OAuthSignatureMethod_HMAC_SHA1());
            trace(builtUrl);
           
            var requestHTTPService:HTTPService = new HTTPService();
            requestHTTPService.url = url;
            requestHTTPService.method = method;
            requestHTTPService.addEventListener(ResultEvent.RESULT, resultHandler);
            requestHTTPService.addEventListener(FaultEvent.FAULT, faultHandler);
            requestHTTPService.send(params);
        }
       
        // Authorize를 위한 로그인 사이트  주소 리턴.
        public function authorizeSite(authrizeURL:String):String
        {
            return authrizeURL + "?oauth_token=" + oauth_token;
        }
       
        // Data파싱 후 oauthToken과 oauthTokenSecret추출
        public function parseData(data:String):void
        {
            var tokenStart:Number = data.indexOf("oauth_token");
            var tokenEnd:Number = data.indexOf("&oauth_token_secret");
            this.oauth_token = data.substring(tokenStart + 12, tokenEnd);
            this.oauth_token_secret = data.substring(tokenEnd + 20);
        }
    }
}
[/code]
메소드중에 requestOAuth메소드가 있는데, 이것은 parameter와 url, method, resultHandler, faultHandler만 지정해주면 자동으로 OAuth에 필요한 파라메터를 만들어서 요청해주는 메소드입니다.
이걸로 RequestToken, AccessToken, 그 외 보호된 자원에 접근하기 위한 요청으로 쓸 수 있습니다.

authorizeSite메소드는 로그인 해주는 사이트로 이동할 때, requestToken을 파라메터로 붙여주는 주소를 리턴해줍니다.

parseData메소드는 요청 후 리턴받은 데이터가 oauth_token=~~~~&oauth_token_secret=~~~ 라고 되어있는 것을 단순히 파싱해서 변수에 저장하는 역할을 합니다.(참고로, 오픈마루의 귓속말서비스 같은 경우 AccessToken을 요청할 때 뒤에 오픈아이디를 같이 붙여줘서 리턴값이 틀립니다. 그걸 참고해서 저걸 사용해야할 듯 싶습니다.)

글이 너무 길어져서 다음글로 패스-_-;
http://mudchobo.tomeii.com/tt/324
 
Posted by 머드초보
,
 
이거 완전 신기하네요-_-;
HTMLLoader라는 클래스가 AIR전용으로 있는데요.
HTML을 읽어와서 보여주는 역할 뿐아니라 JAVASCRIPT함수를 호출할 수 있으며(ActionScript3에서 자바스크립트 문법을 그대로 사용할 수 있는 듯 합니다-_- 아직많이 해보지는 않았지만-_-), 물론 자바스크립트 프레임워크 메소드도 호출이 가능하겠죠.

한번 해보니 잘 되네요-_-;

AIR로 프로젝트를 만들고, src폴더에 prototype-1.6.0.2.js 파일을 넣구요.
prototypetest.html파일을 아래와 같이 작성했습니다.
[code]
<?xml version="1.0" encoding="EUC-KR" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>AIR와 JAVASCRIPT(prototype)연동</title>
<script src="prototype-1.6.0.2.js" type="text/javascript"></script>
</head>
<body>
<div id="myDiv">
안녕하세요!<br />
후....추석날 뭔짓이냐..-_-;
</div>
</body>
</html>
[/code]
저기 DIV내용을 FLEX로 가져오겠습니다.
[code]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            private var _htmlLoader:HTMLLoader;
           
            private function clickHandler():void
            {
                _htmlLoader = new HTMLLoader();
                _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
                _htmlLoader.load(new URLRequest("prototypetest.html"));
            }
           
            private function completeHandler(event:Event):void
            {
                Alert.show(_htmlLoader.window.$("myDiv").innerHTML);
            }
        ]]>
    </mx:Script>
   
    <mx:Button label="DIV내용을 프로토타입함수 사용해서 가져오기" click="clickHandler()"/>
   
</mx:WindowedApplication>
[/code]
간단합니다. HTMLLoader를 이용해서 HTML파일을 읽어와서 다 읽어오게 되면, HTMLLoader객체에 포함되어있는 window라는 property가 있는데 이것을 이용해서 prototype함수를 호출하면 됩니다.
$('') 요게 먹힙니다-_-; 아래는 결과입니다.
사용자 삽입 이미지

우선 이게 되서 제가 기쁜게...Oauth인증이.....AIR에서 안되더라구요-_-;
AIR에서도 되는군요-_-; 다시 시도를...-_-;
근데, 신기하게 JAVASCRIPT에서는 되더라구요. 그래서 왠지 JAVASCRIPT를 이용해서 인증하고 사용하면 될 것 같은 느낌이 들어서 시도해보려고 해요. 망할..Oauth-_-;

 
Posted by 머드초보
,
 
책이 나오기 까지 참 많이 힘들었죠 ^^
(에이레네님 수고하셨어요~ ^^)

이 책은 그전에 AIR가 정식 런칭하지 않은 상태에서 그전에 출간되었습니다.
그 후 AIR가 정식 출간되고 나서, 정식버전에 바뀐 내용이 수정되고, 내용이 추가되면서 책이 다시 바뀌게 되었죠.

어쨌든 이제 나오게 되어서 다행이네요 ^^
AIR에 관한 책은 옥상훈님이 쓰신 "예제로 배우는 Adobe 플렉스(개정판)"과 "Flex/AIR Bible"책이 있는데요. 이 책은 Flex와 같이 나온 책이라 순수 AIR책이라고 하기에는 뭔가 맘에 걸립니다-_-;

하지만, 이번에 나오는 AIR in Action은 순수 AIR관련된 책이므로 AIR에 대해서 완벽하게 공부하실 분은 꼭 보셨으면 합니다. AIR에서 특화된 기능을 예제로 잘 만들어서 활용하고, 설명까지 상세히 나와있어서 AIR를 처음 접하는 개발자분들이나 FLEX만 주로하는 개발자들이 봐도 전혀 손색이 없어 보이네요.

http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=8992939175
알라딘에 판매정보가 떴는데 아직 출간하지 않아서 주문할 수 없네요.

2008년 9월 23일날 출간하려나봅니다 ^^

사용자 삽입 이미지

 
Posted by 머드초보
,