Flex 2: 2007年12月アーカイブ

ご存じの方が多いと思いますが、Flash コンテンツ内で使用している trace() の内容は、FlashTracer などのブラウザ拡張機能を使えば、簡単に丸見えになってしまいます。

Flash には、パブリッシュ(ビルド)の設定に 「 Trace アクションを省略」 という設定項目があり、この機能を使うことによって、パブリッシュ時に ActionScript のコード内にある trace() をすべて削除してから swf ファイルを生成することができます。ですが、FlexBuilder には、この機能に該当するオプションがありません。( mxmlc のオプションにもありません)

『 Flex アプリの開発では、trace() を使わない 』 という選択肢がベストなのですが、trace() を使用せざる得ない場合に、少しでもリスクを削減したいという方のために、簡単なクラスを作成しました。

package
{
    /**
     * FlexTrace.as
     * @author taiga
     * カスタム trace() クラス
     */
    public class FlexTrace
    {
        /**
         * @private
         */
        private static var mFlag:Boolean;
        /**
         * enabled
         * 書き込み専用プロパティ trace() の有効(true)/無効(false)
         */
        public static function set enabled(aFlag:Boolean):void
        {
            mFlag = aFlag;
        }
        /**
         * output
         * @param args trace() させる要素
         * enabled=true のとき、trace() を実行
         */
        public static function output(...args):void
        {
            if(!mFlag)
                return;
            var i:uint;
            var s:String = '';
            for(i = 0; i < args.length; i++)
                s += args[i].toString() + ' ';
            trace(s);
        }
    }
}

使い方ですが、適当にインポートしたあと、FlexTrace.output('hoge', 'fuga'); という具合に、trace() と同様に使うだけです。カンマ区切りで好きなだけ引数を追加できます。( ActionScript 3.0 では、... (rest) パラメータを使用することにより、任意の数のカンマ区切りのパラメータを受け入れる配列パラメータを指定できます)

あとは、FlexTrace.enabled の値を true か false に設定するだけで、trace() の使用/不使用を一括制御することができます。「 Trace アクションを省略」 のように完全にコードを削除できるわけではありませんが、緩和処置としては許せる範囲かなと思いますので、使いたい方はご自由にどうぞ。

ちなみに、私は Flex アプリ開発の仕事では、trace() を一切使用していません。

実は、つい最近まで Repeater コンポーネントの存在を知らなかった taiga です。こんにちは。

Repeater コンポーネントの存在を知ってドキュメントを読んだとき、サンプルコードには、<mx:Script> タグを使用していたり、MXML 内で波括弧を使用して AS を埋め込んでいたりと、あまり模範にしたくない断罪サンプルしか掲載されてなかったので、自分好みのサンプルを作成しました。カスタムコンポーネントを Repeater で繰り返し処理させるサンプルです。

RepeaterSample (画面)
http://labs.taiga.jp/flex2/RepeaterSample/

RepeaterSample (ソース)
http://labs.taiga.jp/flex2/RepeaterSample/srcview/

ソースの説明をする前に、Repeater コンポーネントを知らない人向けに簡単に説明しておきます。
Repeater コンポーネントは、実行時に動的 or 静的な配列に基いて UI コンポーネントを繰り返し処理するコンポーネントです。Repeater.dataProvider = hogeArray 的な使い方ができるので、 ComboBox や DataGrid を使ったことのある人であれば、容易に理解できると思います。他にも、for 文で new して addChild() して…といった AS コーディングの手間を省くことができるというメリットもあります。

ソースの説明に話を移しますが、Repeater コンポーネントを持つ親コンポーネント(RepeaterSample.mxml, ApplicationBase.as)では、特別な処理を行っていません。 今回注目したいのは、繰り返し表示されるカスタム子コンポーネントクラス (SubRowBase.as) の中身です。

public function SubRowBase()
{
    addEventListener(FlexEvent.INITIALIZE, initializeHandler, false, 0, true);
}
private function initializeHandler(event:FlexEvent):void
{
    var lColor:uint = (0 == repeaterIndex % 2) ? 0xffffff : 0xeeeeee;
    mComponent = event.currentTarget as SubRow;
    with (mComponent) {
        compCheckBox.addEventListener(Event.CHANGE, changeHandler, false, 0, true);
        compCheckBox.selected = repeater.currentItem.check;
        comp_0_Label.text     = repeater.currentItem.labelFirst;
        comp_1_Label.text     = repeater.currentItem.labelSecond;
        setStyle('backgroundColor', lColor);
        setStyle('backgroundAlpha', 0.5);
    }
}

Repeater コンポーネントにネストされた子コンポーネントは、親コンポーネントから渡された配列の値を、自分自身の repeater.currentItem プロパティを使用して参照することができます。( repeater プロパティは、UIComponent クラスから派生したすべてのコンポーネントに実装されているプロパティです)

このとき注意しなければならないのは、FlexEvent.CREATION_COMPLETE イベント発行のタイミングで repeater プロパティを参照すると、null 参照になり例外エラーが発生するということです。実際に FlexEvent.INITIALIZE イベントを addEventListener している理由は、この例外エラーを回避するためなのです。

次に、子コンポーネント内のデータの変更方法について、CheckBox の Event.CHANGE イベントリスナー関数を例に説明します。

private function changeHandler(event:Event):void
{
    var lRowIndex:int     = repeaterIndex;
    var lSelected:Boolean = event.currentTarget.selected;
    repeater.dataProvider[lRowIndex].check = lSelected;
}

repeaterIndex プロパティは、親コンポーネントの Repeater.dataProvider プロパティ内にある、子コンポーネントのインデックスを取得することができます。( repeaterIndex プロパティも、repeater プロパティと同じく、UIComponent クラスから派生したすべてのコンポーネントに実装されているプロパティです。余談ですが、サンプル画面のストライプの表現でも、repeaterIndex プロパティを使用して、インデックスが偶数か奇数かを判別して背景色を切り替える処理を行っています。)

そして、repeater.dataProvider プロパティを使用することによって、親コンポーネントの Repeater.dataProvider プロパティを参照できるので、子コンポーネントのデータが更新されたときには、repeater.dataProvider[repeaterIndex] に対してデータの変更(上書き)処理を行えばよいわけです。

以上のポイントさえ抑えておけば、Repeater コンポーネントを使用したときでも、MXML コーディングはシンプルかつ最小限に留め、画面のコントロールはすべて AS クラスにまとめる…という、ステキアーキテクチャが割と簡単にできあがるハズです。

時分秒 (hh:mm:ss) の値を含んだ Date オブジェクトを selectedDate プロパティにセットすると、アイコンをクリックしたとき表示されるカレンダーに、セットした日にち (DD) がフォーカスされない…という不具合が発生します。

この現象、Flex 3 では解消されていますが、時間情報を含んだ日付情報を扱うような Flex 2 アプリを作るときには、注意が必要です。(余談ですが、fla ファイル (AS 2.0) で扱える DateField コンポーネントでは、上記現象は発生しません)

以下検証コード

<?xml version="1.0" encoding="utf-8"?>
<!-- DateFieldShotTest -->
<mx:Application
    xmlns:mx         = "http://www.adobe.com/2006/mxml"
    layout           = "vertical"
    creationComplete = "init();">

    <mx:Script>
        <![CDATA[
            private function dateOutput():void
            {
                traceText.text = String(comp_0_DF.selectedDate)+'\n'+
                                 String(comp_1_DF.selectedDate)+'\n'+
                                 String(comp_2_DF.selectedDate)+'\n'+
                                 String(comp_3_DF.selectedDate)+'\n'+
                                 String(comp_4_DF.selectedDate);
            }
            private function init():void
            {
                traceText.text = '';
                comp_0_DF.selectedDate = new Date();
                comp_1_DF.selectedDate = new Date(2007, 0, 23);
                comp_2_DF.selectedDate = new Date(2007, 0, 23, 0, 0, 0);
                comp_3_DF.selectedDate = new Date(2007, 0, 23, null, null, null);
                comp_4_DF.selectedDate = new Date(2007, 0, 23, 12, 34, 56);
            }
        ]]>
    </mx:Script>

    <mx:Style>
        Label
        {
            font-weight: bold;
        }
    </mx:Style>

    <mx:VBox width="400" textAlign="left">

        <mx:HBox>
            <mx:DateField id="comp_0_DF" />
            <mx:Label text="new Date();" />
        </mx:HBox>

        <mx:HBox>
            <mx:DateField id="comp_1_DF" />
            <mx:Label text="new Date(2007, 0, 23);" />
        </mx:HBox>

        <mx:HBox>
            <mx:DateField id="comp_2_DF" />
            <mx:Label text="new Date(2007, 0, 23, 0, 0, 0);" />
        </mx:HBox>

        <mx:HBox>
            <mx:DateField id="comp_3_DF" />
            <mx:Label text="new Date(2007, 0, 23, null, null, null);" />
        </mx:HBox>

        <mx:HBox>
            <mx:DateField id="comp_4_DF" />
            <mx:Label text="new Date(2007, 0, 23, 12, 34, 56);" color="#CC0000" />
        </mx:HBox>

    </mx:VBox>

    <mx:TextArea  id="traceText" width="400" height="100" />

    <mx:HBox>
        <mx:Button label="trace" click="{dateOutput()}" />
        <mx:Button label="reset" click="{init()}" />
    </mx:HBox>

</mx:Application>

前回のエントリーに引き続き、itemRenderer について書きます。

サンプルは以下の通り。

ItemRendererComboBoxSample (画面)
http://labs.taiga.jp/flex2/ItemRendererComboBoxSample/

ItemRendererComboBoxSample (ソース)
http://labs.taiga.jp/flex2/ItemRendererComboBoxSample/srcview/

まず、ItemRendererComboBoxSample.mxml の <mx:DataGridColumn> タグに着目します。

<!-- カスタム ComboBox -->
<mx:DataGridColumn
    headerText       = "colmn01"
    dataField        = "combo"
    itemRenderer     = "lib.SubComboBox"
    rendererIsEditor = "true"
    editorDataField  = "selectedIndex"
    editable         = "true"
/>

itemRenderer プロパティ値に、カスタム ComboBox クラスを定義しています。
rendererIsEditor プロパティと editorDataField プロパティを設定している箇所は、前回のカスタム CheckBox と同じです。
そして今回、editorDataField プロパティには、ComboBox のプロパティ名である slectedIndex を定義しています。

次に、SubComboBox.as に着目します。

package lib
{
    import flash.events.Event;
    import mx.collections.ArrayCollection;
    import mx.controls.ComboBox;
    import mx.controls.dataGridClasses.DataGridListData;
    /**
     * SubComboBox
     * itemRenderer 用 ComboBox サブクラス
     */
    public class SubComboBox extends ComboBox
    {
        /**
         * mValue
         * @private
         * クラス内部で保持する selectedIndex 値
         */
        private var mValue:uint;
        /**
         * SubComboBox
         * コンストラクタ
         */
        public function SubComboBox()
        {
            super();
            width        = 100;
            dataProvider = [ {label:'Label_A', data:10},
                             {label:'Label_B', data:20},
                             {label:'Label_C', data:30} ];
            addEventListener(Event.CHANGE, changeHandler, false, 0, true);
        }
        /**
         * tmpValue
         * @private
         * selectedIndex 値(内部保持用)
         */
        private function get tmpValue():uint
        {
              return mValue;
        }
        /**
         * @private
         */
        private function set tmpValue(aValue:uint):void
        {
            mValue = aValue;
            invalidateProperties();
        }
        /**
         * data
         * @param aValue itemRenderer の値
         * @private
         * プロパティ ComboBox の selectedIndex 値
         */
        override public function set data(aObject:Object):void
        {
            super.data = aObject;
            if (null != dataProvider)
            {
                selectedIndex = getLabelIndex( data[DataGridListData(listData).dataField] );
            }
        }
        /**
         * commitProperties
         * @private
         * Binding しているプロパティの更新
         */
        override protected function commitProperties():void
        {
            this.selectedIndex = getLabelIndex( data[DataGridListData(listData).dataField] );
            super.commitProperties();
        }
        /**
         * getLabelIndex
         * @param aValue dataProvider の値
         * @return selectedIndex 値
         * @private
         * 引数と ComboBox 内のデータを評価して、マッチしたら該当インデックスを返却
         */
        private function getLabelIndex(aValue:int):uint
        {
            var lArrayCollection:ArrayCollection = dataProvider as ArrayCollection;
            var lLength:Number = lArrayCollection.length;
            for (var i:uint = 0; i < lLength; i++)
            {
                if (lArrayCollection.getItemAt(i).data == aValue)
                {
                    return i;
                }
            }
            return 0;
        }
        /**
         * changeHandler
         * @param event Event オブジェクト
         * @private
         * ComboBox の change イベント
         */
        private function changeHandler(event:Event):void
        {
            var lArrayCollection:ArrayCollection = dataProvider as ArrayCollection;
            if (listData)
            {
                tmpValue = lArrayCollection.getItemAt(this.selectedIndex).data;
                data[DataGridListData(listData).dataField] = tmpValue;
            }
        }
    }
}

コンストラクタで ComboBox の label 値と data 値を定義します。

DataGrid をスクロールさせると ComboBox.commitProperties() メソッドが実行されます。
このとき ComboBox の label 値を変更していると初期表示状態に戻ってしまうので、この現象を回避するために ComboBox.commitProperties() メソッドをオーバーライドし、changeHandler() メソッドで保持しておいた data 値をセットさせる処理を追記しています。

DataGrid.dataProvider プロパティにセットする ComboBox の値ですが、ComboBox の data 値のみ使用します。( label は、ただのお飾りということです)
なぜかというと、本来 ComboBox.selectedIndex プロパティには、label 値と data 値を内包したオブジェクト(例: {label:"hoge", data:"fuga"} )を代入しないと正しく動作しないのですが、DataGrid.dataProvider プロパティに例のようなオブジェクトをセットしてしまうと、DataGird のヘッダをクリックしてソート処理を行ったとき、DataGird がソート対象を判別できず、例外エラーが発生してしまうためです。

そして、正しく動作しない selectedIndex プロパティを正しく動作させるために、getLabelIndex() というメソッドを作成し、ComboBox の data 値と DataGrid.dataProvider プロパティにセットされた値を、総当たりでマッチングさせます。(賢い方法ではありませんが)

このアーカイブについて

このページには、2007年12月以降に書かれたブログ記事のうちFlex 2カテゴリに属しているものが含まれています。

前のアーカイブはFlex 2: 2007年11月です。

次のアーカイブはFlex 2: 2008年1月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。