2010年6月7日月曜日

Utils.as

package {
    import flash.filesystem.File;
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    
    /**
     * とりあえず、静的な便利メソッドを入れ込んでおくクラス。
     * 
     * TODO: Air 以外の開発をする際に、Air 専用とそれ以外を分離しましょう。
     * 
     * 方針
     *         ・ActionScriptはオーバーロードができないので、メソッド名に引数情報もいれる。
     *         ・見かけの簡潔さを求めず、分かりやすさを求める。初めて見る人でもシグニチャだけで機能を確実に理解できることを目指す。
     *         ・管理しきれなくなって『具体的に困る』ことになったら別ライブラリへの分割を考える。
     */
    public class Utils {
        
        /**
         * 指定された XML ファイルを読み込み XML オブジェクトを返します。
         */
        public static function readXmlFromFile(xmlFile:File):XML {
            return new XML(readUTFBytesFromFile(xmlFile));
        }
        
        /**
         * 指定されたファイルを読み込み UTFBytes を返します。
         */
        public static function readUTFBytesFromFile(utfFile:File):String {
            var fileStream:FileStream = new FileStream();
            fileStream.open(utfFile, FileMode.READ);
            var utfBytes:String = fileStream.readUTFBytes(utfFile.size);
            fileStream.close();
            return utfBytes;
        }
        
        /**
         * 指定されたファイルに対して指定された XML を書き込みます。
         */
        public static function writeXmlToFile(xml:XML, file:File):void {
            writeUTFBytesToFile(xml.toXMLString(), file);
        }
        
        /**
         * 指定されたファイルに対して UTFBytes を書き込みます。
         */
        public static function writeUTFBytesToFile(utfBytes:String, file:File):void {
            var fileStream:FileStream = new FileStream();
            fileStream.open(file, FileMode.WRITE);
            fileStream.writeUTFBytes(utfBytes);
            fileStream.close();
        }
        
        /**
         * 指定されたファイルをファイルシステム中に作成します。既に指定されたファイルが存在する場合には何もしません。
         */
        public static function createFile(file:File):void {
            if (!file.exists) {
                var fileStream:FileStream = new FileStream();
                fileStream.open(file, FileMode.WRITE);
                fileStream.close();
            }
        }
    }
}

2010年6月2日水曜日

FlashのUI系コンポーネント調査

FlashのUI系のコンポーネントについて全く知識がないので、少し調査してみました。

クラス階層

Object
・言うまでもなく、あらゆるクラスのベースとなるクラス。特にUIであるかどうかとは無関係。
・パッケージはトップレベル。

flash.events.EventDispatcher
flash.events.IEventDispatcher の実装クラス。
・イベントを扱うための汎用的な機能を持つクラス。リスナーの追加削除とかイベントの送信とか。
・特にUIであるかどうかとは無関係。

flash.display.DisplayObject
・display list に配置されるすべてのオブジェクトのベースクラス。
・x,y,zとかalphaとかwidthとかvisibleとかマウス位置とか、表示されるものとしての基本機能を持っている。

flash.display.InteractiveObject
・表示されるオブジェクトでマウスやキーボードやその他の入力デバイスを扱うものすべての抽象クラス。
・このクラスはnewできないし、上記までのクラスもUIを作成するうえでプログラマが明示的に new することはなさそうです。

flash.display.DisplayObjectContainer
・display object の入れ物クラス。
・z 軸上に複数の display object を child として配置できる。
・このサブクラスには Loader, Sprite, Stage, TextLine があります。

flash.display.Loader
・java の ClassLoader のようなものを想像していると DisplayObjectContainer のサブクラスということに違和感を感じるが、実際は swf ファイルおよび画像ファイルをロードして表示するものなので、表示オブジェクトのローダーと言える。
・flex 用のサブクラスとして mx.core.FlexLoader があり、toString() メソッドが DisplayObjects の階層を表すようになっている。

flash.text.engine.TextLine
・display text 上に文字列を表示するためのオブジェクト。
・final クラスなので継承できないし、ユーザが直接 new することもできない。
・flash.text.engine 系のパッケージと関わらない限りは必要なさそう。

flash.display.Stage
・ブラウザ内なら flash の表示領域が Stage で、Airなら各 NativeWindow が Stage を持つ。

flash.display.Sprite
・UI 系の一般的なベースクラスで、MovieClip からtimelineを抜いたようなもの。

2010年5月23日日曜日

開発環境メモ

WindowsXP環境開発環境メモ

GAE, git, svn, maven を前提とした環境。
今のところ、GAE系やGITの環境の試行錯誤中。

TODO: とりあえずメモなので、あとでちゃんと書きましょう。
  • 7zip
    • http://www.7-zip.org/ からダウンロードしてすべてデフォルトでインストール。
    • 以降、アーカイブの展開は7zipを使います。(Windows付属のはどうもバギーなので)
  • ブラウザのインストール
    各種ブラウザの動作確認を行うため、複数のブラウザをインストールする。
    • Firefoxのインストール
      • http://mozilla.jp/firefox/ から最新版をダウンロードしてすべてデフォルト設定でインストールする。
    • Google Chrome のインストール
  • Java
    • http://java.sun.com/ から Java SE の最新版をダウンロードして、すべてデフォルト設定でインストールする。
    • 環境変数 JAVA_HOME を適切に設定する。(C:\Program Files\Java\jdk1.6.0_20 等)
    • 環境変数 PATH に %JAVA_HOME%\bin を設定する。
  • maven
    • http://maven.apache.org/ から安定版の最新のバイナリをダウンロード。(ex. apache-maven-2.2.1-bin.zip)
    • C:\Program Files 直下にコピー。(ex. C:\Program Files\apache-maven-2.2.1)
    • 環境変数 MAVEN_HOME を設定。
    • 環境変数 Path に %MAVEN_HOME%\bin を追加。
  • Eclipse
    • http://www.eclipse.org/downloads/ から Eclipse IDE for Java EE Developers の galileo の最新版をダウンロード。
    • C:\Program Files\eclipse として展開する。
    • 文字コードの設定
      • Window -> Preferences -> General -> Workspace -> Text file encoding を utf-8 に設定。
    • xmlのwidthを999、インデントをスペース2つに変更する。
    • eclipseにjdkのソースをアタッチ
      • Window -> Preferences... -> Java -> Installed JREs に移動。
      • jre を選択して、Edit... ボタンを押下。
      • C:\tmp\devenv\jdk\jre\lib\rt.jar を選択。
      • Source Attachement... ボタンを押下。
      • C:/Program Files/Java/jdk1.6.0_20/src.zip をアタッチ。
    • eclipseにJavaDocをアタッチ
      • Sunのサイトからドキュメントをダウンロードして、src.zipと同じフォルダに置く。
      • Window -> Preferences... -> Java -> Installed JREs に移動。
      • jre を選択して、Edit... ボタンを押
      • C:\tmp\devenv\eclipse\jre\lib\rt.jar を選択。
      • JavaDoc Location... ボタンを押下。
      • Javadoc in archive と Workspace file を選択。
      • Archive path を設定先ほどダウンロードしたドキュメントのzipに設定。
      • Path within archive を設定。(ex. docs/api)
      • ソース上の対象を選択してShift+F2キーを押せば、システム設定のブラウザ経由でJavaDocが起動します。
    • m2eclipseの設定
      • アップデートサイト http://m2eclipse.sonatype.org/sites/m2e からインストールする。
      • [Window] -> [Preferences...] -> [Maven] にて、以下のオプションをチェック。
        • Download Artifact Sources
        • Download Artifact JavaDoc
      • デフォルトのmavenが3.0系のSNAPSHOT版の場合は、[Window] -> [Preferences...] -> [Maven]  -> [Installations] -> [Add] から Program Files 以下にインストールした maven を指定する。
    • Google 関連プラグインの設定
    • subclipse プラグインの設定
      • アップデートサイト http://subclipse.tigris.org/update_1.6.x からインストールする。その際、オプショナルコンポーネントはすべて無視する。
      • SVN インタフェースを SVNKit (Pure Java) に設定する。
        • [Window] -> [Preferences...] -> [Team] -> [SVN] -> [SVNインタフェース] -> [SVNKit (Pure Java)]
    • EGit プラグインの設定。


    2010年5月11日火曜日

    Flex のコンポーネント遅延ロードとデータバインディングとモデリングに関する考察

    Flexではコンポーネントの遅延ローディングがデフォルトで行われるようになっています。そのため、mxml ファイル内で定義されたコンポーネントに対して同一の mxml ファイル内の ActionScript からアクセスしても、null が返る可能性があります。

    そのような場合、内部コンポーネントを定義するXML要素の属性として、ActionScript 内の変数をバインディングするというワークアラウンドで乗り切ることができます。しかし、データバインディングをする場合、モデルが既にあったとしても、バインディング用の変数をわざわざ用意する必要があります。モデルの中で配列で100要素バインディングするとしたら、100種類の変数を用意する必要があります。

    これも、ArrayCollection 型を使用することで解決できますが、既に Vector でデータを持っているモデルがあるとしたら、毎回移し変えなければなりません。

    このように、ワークアラウンドで乗り切ることもできますが、あまり建設的ではないように思えます。まだまだ落とし穴があるように思えますし、、、。

    ということで、今後の作業効率や成果物の品質にも大きく影響すると考えられるので、この機会に確認しておこうと思います。


    例1:ある state で生成された情報を、これから初めて遷移される state に表示する場合。

    ソース
    思惑
    • State 1 が初期状態。
    • State 1 上の入力フォームから結果表示用ラベルに A と B の値を入力し、add ボタンを押下する。
    • State 2 に遷移し、A + B の結果が表示される。
    結果
    • State 2 で表示される結果表示用ラベルの値が null となり、エラーが発生。
    考察
    • creationPolicy を all にすることにより遅延ロードを抑制できないか?
    • ドキュメントによると、単一ビューコンテナでは、auto でも all でも同じ動作となり、『デフォルトでは、アプリケーションが最初に起動されたときに単一ビューコンテナのすべての子が生成されます。』となるらしい。生成されてるのに null なのはなぜ? (´・ω・`)
    • State が絡む場合はダメらしい。結局、State の遷移を先に行うしかないらしい。
    解決法
    • ある State に含まれるコンポーネントへのアクセスは、その State に遷移してから行う。
    解決後のソース

    例2:複雑なモデル内部のデータを参照する場合。

    バインディング版
    • ソース:Ex2.mxml
    • 特に問題なく動作する。


    例3:配列へバインディングしようとして失敗する版
    • ソース:Ex2_1.mxml
    • 最初の1回目はうまく行くが、2回目以降も最初の結果が出続ける。

    例4:ArrayCollection を用いて対応する版
    • ソース:Ex2_2.mxml
    • 問題なく動作するが、バインディングのために設計を崩すことになった。バインディングするよりも、特定のタイミングでデータをリロードしてくれるような設計の方がありがたいのだが。例えば状態遷移のタイミングとか。
    例5:ボタンクリックのタイミングでデータを格納する版
    • ソース:Ex2_3.mxml
    • サブコンポーネントに対する text の指定を、親コンポーネント側で決定することが自然な設計であれば、無理にバインディングする必要はない。そのため、クリックのハンドラでラベルの表示を行っている。
    例6:Stateに入るタイミングでデータを格納する版
    • ソース:Ex2_4.mxml
    • サブコンポーネントに対する text の指定を、親コンポーネント側で決定することが自然な設計であれば、無理にバインディングする必要はない。状態遷移をする箇所が一箇所であればその場所にラベルにデータを書き込む処理を埋め込めば良いのだが、ソースの様々な箇所から状態遷移が発生する場合には同様の処理を複数回記述することになる。その際、状態遷移をトリガーにした方がシンプルになる場合は、State に入るイベントを用いた方がよい。
    付録:ソース


    Ex1.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        var a:String = aNs.value.toString();
        var b:String = bNs.value.toString();
        var result:String = (aNs.value + bNs.value).toString();
        Alert.show("resultLabel = " + resultLabel); // ここで、resultLabel が null になる。
        resultLabel.text = a + " + " + b + " = " + result;
        currentState = "State2";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="A">
       <!--- A の値入力用 NumericStepper -->
       <s:NumericStepper id="aNs"/>
      </mx:FormItem>
      <mx:FormItem label="B">
       <!--- B の値入力用 NumericStepper -->
       <s:NumericStepper id="bNs"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- A + B = (A+Bの結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     
    </s:Application>
    


    Ex1_1.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        var a:String = aNs.value.toString();
        var b:String = bNs.value.toString();
        var result:String = (aNs.value + bNs.value).toString();
        currentState = "State2";
        Alert.show("resultLabel = " + resultLabel); // ここで、resultLabel が null ではなく、きちんと表示される。
        resultLabel.text = a + " + " + b + " = " + result;
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="A">
       <!--- A の値入力用 NumericStepper -->
       <s:NumericStepper id="aNs"/>
      </mx:FormItem>
      <mx:FormItem label="B">
       <!--- B の値入力用 NumericStepper -->
       <s:NumericStepper id="bNs"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- A + B = (A+Bの結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     
    </s:Application>
    


    Ex2.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{v1Ns.value} + {v2Ns.value} + {v3Ns.value} = {v1Ns.value + v2Ns.value + v3Ns.value}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_1.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        values.push(v1Ns.value);
        values.push(v2Ns.value);
        values.push(v3Ns.value);
        
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{values[0]} + {values[1]} + {values[2]} = {values[0] + values[1] + values[2]}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_2.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.collections.ArrayCollection;
       import mx.controls.Alert;
       
       [Bindable]
       /**
        * 値を保持する ArrayCollection
        */
       private var values:ArrayCollection = new ArrayCollection(new Array(3));
       
       /**
        * A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド
        */
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        values.setItemAt(v1Ns.value, 0);
        values.setItemAt(v2Ns.value, 1);
        values.setItemAt(v3Ns.value, 2);
        
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{values.getItemAt(0)} + {values.getItemAt(1)} + {values.getItemAt(2)} = {values.getItemAt(0) + values.getItemAt(1) + values.getItemAt(2)}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_3.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        if (values.length == 0) {
         values.push(v1Ns.value);
         values.push(v2Ns.value);
         values.push(v3Ns.value);
        } else {
         values[0] = v1Ns.value;
         values[1] = v2Ns.value;
         values[2] = v3Ns.value;
        }
            
        currentState = "State2";
        
        // ラベルの値を直接指定する。
        resultLabel.text = values[0].toString() + ' + ' + values[1].toString() + ' + ' + values[2].toString() + ' = ' + (values[0] + values[1] + values[2]);
    
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_4.mxml
    <?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/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        if (values.length == 0) {
         values.push(v1Ns.value);
         values.push(v2Ns.value);
         values.push(v3Ns.value);
        } else {
         values[0] = v1Ns.value;
         values[1] = v2Ns.value;
         values[2] = v3Ns.value;
        }
            
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2" enterState="resultLabel.text = values[0].toString() + ' + ' + values[1].toString() + ' + ' + values[2].toString() + ' = ' + (values[0] + values[1] + values[2])"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    

    2010年5月6日木曜日

    flex で creationComplete 時に stage == null になる問題。

    ドキュメントによると、少なくとも activate されている間は stage が生きているらしいです。

    そのため、Event.ACTIVATE と Event.RESIZE イベント内なら確実に stage を扱えそうです。

    2010年5月2日日曜日

    GIMPを使い始めてみようと思ったり思わなかったり。

    以下、GIMPを使おうと思い立ってから以降のメモ書き。

    今は何でも屋としてサイト開発をしているので、グラフィック系のチームに画像やレイアウトの注文を任せるようなことができない今日この頃。

    グラフィックもやらにゃいかんだろうと思い、どうしたものかと思案中。

    PhotoShop をいきなり購入しても、3日で飽きたとかしゃれにならんし。

    ということで、GIMPに挑戦してみる。


    公式ページ

    http://www.gimp.org/


    Windows版ダウンロードページ

    http://www.gimp.org/windows/


    ダウンロード&インストール

    アプリとマニュアルの両方をダウンロードして実行。本体は国際化されているので、何語版というのはありません。

    2010年4月22日木曜日

    作業メモ:BlazeDS + Flex 環境の Enum ルール

    今回は、BlazeDS + Flex 環境で Enum をどのように扱うかについて考察してみます。

    基本事項

    基本的な設定やその他基本事項については、以下に準じます。

    ActionScript 側の enum 名格納変数について

    ActionScript 側の enum 名格納変数を name とします。AMFで通信される情報も java の Enum#name() に準じたものにします。そのため、以下のような独自のEnumProxyを定義します。
    package com.objectfanatics.flex.messaging.io;
    
    import java.util.Collections;
    import java.util.List;
    
    import flex.messaging.io.AbstractProxy;
    import flex.messaging.io.BeanProxy;
    
    public class EnumProxy extends BeanProxy {
    
        private static final String ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME = "name";
        private static final List<String> propertyNames = Collections.singletonList(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME);
    
        @SuppressWarnings("unchecked")
        public Object createInstance(String className) {
            return new EnumStub((Class<Enum>) AbstractProxy.getClassFromClassName(className));
        }
    
        public Object getValue(Object instance, String propertyName) {
            if (propertyName.equals(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME)) {
                return Enum.class.cast(instance).name();
            } else {
                throw new IllegalArgumentException("No property named: " + propertyName + " on enum type");
            }
        }
    
        public void setValue(Object instance, String propertyName, Object value) {
            if (propertyName.equals(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME)) {
                EnumStub.class.cast(instance).value = String.class.cast(value);
            } else {
                throw new IllegalArgumentException("no EnumStub property: " + propertyName);
            }
        }
    
        @SuppressWarnings("unchecked")
        public Object instanceComplete(Object instance) {
            EnumStub es = EnumStub.class.cast(instance);
            return Enum.valueOf((Class) es.cl, es.value);
        }
    
        @SuppressWarnings("unchecked")
        public List getPropertyNames(Object instance) {
            if (!(instance instanceof Enum)) {
                throw new IllegalArgumentException("getPropertyNames called with non Enum object");
            } else {
                return propertyNames;
            }
        }
    
        private class EnumStub {
            private Class<?> cl;
            private String value;
            private EnumStub(Class<?> c) {
                cl = c;
            }
        }
    }
    

    同時に HttpFlexSession の static ブロックで以下のように定義し、あらゆる Enum 型に対して EnumProxy が適用されるようにします。
    // patch for enum serialization
      static {
          PropertyProxyRegistry.getRegistry().register(Enum.class, new EnumProxy());
      }
    

    ActionScript 側の enum ルール

    ActionScript 側の enum は、以下のような形式で作成します。
    // Sample enum class.
    package com.objectfanatics.englishfanatics.dataobject {
        import com.objectfanatics.flex.EnumBase;
        
        // fqcn of java counterpart enum class
        [RemoteClass(alias="com.objectfanatics.sample.SampleEnum")]
        
        // class name should be the same as the name of the java counterpart enum class.
        public class SampleEnum extends EnumBase {
            
            // enums
            //  - const name and the value of the first parameter of the constructor should be the same.
            public static const KUMA_SAN:SampleEnum = new SampleEnum("KUMA_SAN", "くまさん");
            public static const TANUKI_SAN:SampleEnum = new SampleEnum("TANUKI_SAN", "たぬきさん");
            
            // constructor
            //  - default parameter values are required for deserialized enums.
            //  - this must be invoked only for enum constant creation in this class and deserialized enum creation.
            public function SampleEnum(name:String = null, string:String = null) {
                super(name, string);
            }
            
            // returns all enum constants in this class.
            //  - this method is workaround for ActionScript3.0's limitation. 
            //    static methods are not inherited and cannot be overridden in ActionScript 3.0.
            public static function values():Array {
                return superValues();
            }
        }
    }
    

    すべての enum は下記のベースクラスを継承します。
    package com.objectfanatics.flex {
        import flash.utils.getDefinitionByName;
        import flash.utils.getQualifiedClassName;
        
        /**
         * Base class of enum object.
         */
        public class EnumBase {
            // key = enum class name, value = enum array.
            private static const valuesMap:Object = new Object();
            
            // enum class name
            private var enumClassName:String;
            
            // enum name
            public var name:String;
            
            // string for presentation
            protected var string:String;
            
            public function EnumBase(name:String = null, string:String = null) {
                this.name = name;
                this.string = string;
                enumClassName = getQualifiedClassName(this);
                if (string != null) {
                    // for normal enum
                    var values:Array = valuesMap[enumClassName];
                    if (values == null) values = new Array();
                    valuesMap[enumClassName] = values;
                    values.push(this);
                } else {
                    // for deserialized enum
                    return;
                }
            }
            
            // This method name is weird because of ActionScript 3.0's limitation.
            //   - Static methods are not inherited and cannot be overridden in ActionScript 3.0.
            public static function superValues():Array {
                // TODO: check.
                var blocks:Array = new Error().getStackTrace().split("at ");
                var block:String = blocks[2];
                var startIndex:int = block.indexOf("at ");// start of first line
                var endIndex:int = block.indexOf("$");    // end of class name
                var enumClassName:String = block.substring(startIndex + 1, endIndex);
                return valuesMap[enumClassName];
            }
            
            public function toString():String {
                if (string == null) {
                    var enumClass:Class = getDefinitionByName(enumClassName) as Class;
                    this.string = enumClass[name].string;
                }
                return string;
            }
            
            //use equality instead of identity.
            public function equals(other:EnumBase):Boolean {
                return this.name == other.name;
            }
        }
    }
    

    java 側の enum ルール

    Java 側の enum は、ActionScript 側の name プロパティと Java 側の Enum#name() の値が対応していれば、どのような形でもかまいません。

    以下例。
    package com.objectfanatics.sample;
    
    public enum SampleEnum {
        KUMA_SAN, TANUKI_SAN;
    }
    

    (´・ω・`) まだまともにに使ってないから、落とし穴がいっぱいありそうな気がする今日この頃。