ホーム >> 左脳Script >> Adobe AIR >> Flex >> flex3 における Tree での dataProvider の扱い方

flex3 における Tree での dataProvider の扱い方


tree.PNG Flex のコントロールに、mx.controls.Tree というものがあります。

判りやすく言うと、explorer のアノ画面(←)です。
これを使ってアプリケーションを作れば、いかにもソレっぽく見えてカッコイイ訳ですが・・・

製作者側としては、データの構築方法&管理方法に注意が必要となります。


基本的記述


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
        [Bindable] private var  treeXml:XML=<root label="root">
            <dir label="dir0"></dir>
            <dir label="dir1">
                <dir label="child0"></dir>
                <dir label="child1">
                    <dir label="data0"></dir>
                    <dir label="data1"></dir>
                    <data/>
                </dir>
            </dir>
        </root>;
        ]]>
    </mx:Script>
    <mx:Tree width="100%" height="100%"
    labelField="@label"
    dataProvider="{treeXml}"></mx:Tree>
</mx:Application>
labelField で、ノード中の属性をテキストラベルとして使用する指定をします。
データとして使う XML は [Bindable] が無いと、<mx:Tree> の dataProvider に設定出来ません。コンパイルで警告が出てしまいます。

tree0.PNG 起動して全てのタグを開いた画面。

tree1.PNG この時、うっかり<root>のlabel属性を忘れると・・・


<root>以下のXMLの記述が何故かラベルテキストに。


ルートが一つ

しかし、このままではルートに一つしかアイテムを設定する事が出来ません。

tree2.PNG そこで、<mx:Tree> に「showRoot="false"」を追加。

副作用として、うっかり <root> の label 属性を忘れても大丈夫になりました。
そもそも XML の仕様として、ルートは一つと決まっています。
発想の転換?と言うほどではありませんが、管理するデータを XML ではなく XMLList にできないでしょうか?

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
        [Bindable] private var  treeXml:XMLList = new XMLList(
            '<dir label="dir0"></dir>'
    +       '<dir label="dir1">'
    +           '<dir label="child0"></dir>'
    +           '<dir label="child1">'
    +               '<dir label="data0"></dir>'
    +               '<dir label="data1"></dir>'
    +               '<data/>'
    +           '</dir>'
    +       '</dir>');
        ]]>
    </mx:Script>
    <mx:Tree width="100%" height="100%"
    labelField="@label"
    dataProvider="{treeXml}"></mx:Tree>
</mx:Application>
ちょっとデータの指定方法が無理やり( Action Script では XMLList データは直接書けない為)ですが、XMLList を使えば「showRoot="false"」が無くとも、ルートに複数のデータを設定できるようになります。


mxml ならば、Action Script のコードとしてではなく、直接 mxml でデータを書くことが出来ます。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:XMLList id="treeXml">
        <dir label="dir0"></dir>
        <dir label="dir1">
            <dir label="child0"></dir>
            <dir label="child1">
                <dir label="data0"></dir>
                <dir label="data1"></dir>
                <data/>
            </dir>
        </dir>
    </mx:XMLList>
    <mx:Tree width="100%" height="100%"
    labelField="@label"
    dataProvider="{treeXml}"></mx:Tree>
</mx:Application>
mxml でコードを書く場合は、こちらの方が自然なのでしょうか?

<mx:XMLList> を更に <mx:XMLListCollection> で括って、id属性の指定を<mx:XMLListCollection> に移し、データを <mx:Tree> の中に置けば・・・

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Tree width="100%" height="100%"
    labelField="@label">
        <mx:XMLListCollection id="treeXml">
            <mx:XMLList>
                <dir label="dir0"></dir>
                <dir label="dir1">
                    <dir label="child0"></dir>
                    <dir label="child1">
                        <dir label="data0"></dir>
                        <dir label="data1"></dir>
                        <data/>
                    </dir>
                </dir>
            </mx:XMLList>
        </mx:XMLListCollection>
    </mx:Tree>
</mx:Application>
dataProvider の記述を取り払う事が出来ます。<mx:XMLLis> の中に在る<mx:XMLListCollection> は、自動的に dataProvider の扱いを受けます。

実は、XML、XMLList は dataProvider に設定される際に、自動的に XMLListCollection にラップされるようです。


ノードデータの変更

操作によって、ツリービューのノードデータを動的に操作したい場合はどうしたら良いでしょうか?


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
        private function nodeClick():void
        {
            var xml:XML = treeView.selectedItem as XML;
            if (    xml )   xml.appendChild(<text label="added"/>);
        }
        ]]>
    </mx:Script>
    <mx:Tree id="treeView" width="100%" height="100%"
    itemClick="nodeClick();"
    labelField="@label">
        <mx:XMLListCollection id="treeXml">
            <mx:XMLList>
                <dir label="dir0"></dir>
                <dir label="dir1">
                    <dir label="child0"></dir>
                    <dir label="child1">
                        <dir label="data0"></dir>
                        <dir label="data1"></dir>
                        <data/>
                    </dir>
                </dir>
            </mx:XMLList>
        </mx:XMLListCollection>
    </mx:Tree>
</mx:Application>
ノードをクリックすると、そのノードに子ノードが追加されます。
label 属性のない <data/> をクリックすると・・・冒頭で述べた「うっかり label 指定をわすれた<root>」と同じ現象になり、XML 記述が表示されてしまいます。

<mx:Tree> の itemClick はノードがクリックされた時のアクションを指定できます。
ノードがクリックされるという事は、ノードの選択作業になるので、呼ばれた関数 nodeClick() 内では、選択されているノードを treeView.selectedItem で取得しています。有効な XML ならば、そこに appendChild するというコードです。

mxml でハードコーディングされた XMLList のデータは、いわゆる「constデータ」ではなく、変更&編集が可能です。


XML以外のデータをdataProviderに指定したい

XML は階層構造を持つデータの管理に対して大変強力です。
しかし、データ数が多くなると、場合にも寄りますが大変重くなります。XML 以外の選択肢は無いのでしょうか。


[Bindable] private var  treeData:Object =
{
    label:"root",
    children:
    [
        {   label:"dir0"    },
        {   label:"dir1"    }
    ]
};

左記のような Action Script を記述し、treeData を dataProvider に指定する事が出来ます。 この場合、<mx:Tree> には labelField 指定は必要ないようです。

ノードとなる Object の lavel プロパティが表示テキストに、children プロパティにある Array が子ノード Object の配列と認識されるようです。


[Bindable] private var  treeData:Array =
[
    {   label:"dir0"    },
    {   label:"dir1",
        children:
        [
            {   label:"child0"  },
            {   label:"child1"  }
        ]
    }
];

また、Object 配列を直接指定することも出来ます。

この際の、Object と Array の扱いは、前述と同じです。

このような記述をすると・・・

[Bindable] private var  treeData:Array =
[
    {   label:"dir0",children:null  },
    {   label:"dir1",children:[]    }
];

tree3.PNG
children プロパティが存在するだけでは親ノードになることは出来ず、逆に長さ 0 であっても Array が指定されていれば、子ノードを持つ親と判定されるようです。


Object&ArrayのdataProviderでの、ノードデータの変更

実行してみると・・・

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
        [Bindable] private var  treeData:Array =
        [
            {   label:"dir0"    },
            {   label:"dir1"    }
        ];
        //
        private function nodeClick():void
        {
            var obj:Object = treeView.selectedItem as Object;
            if (    obj )
            {
                var childs:Array = obj.children as Array;
                if (    !childs )   childs = obj.children = new Array();
                childs.push( { label:"added" } );
            }
        }
        ]]>
    </mx:Script>
    <mx:Tree id="treeView" width="100%" height="100%"
    itemClick="nodeClick();"
    dataProvider="{treeData}">
    </mx:Tree>
</mx:Application>

クリックしても画面が変化しません。ノードを開くなりの操作をすると、やっとノードデータの変更が画面に反映されるようです。
実は XMLListCollection は、ノードの変更を<mx:Tree> に伝える仕組みを持っています。その為、XMLでデータを形成していた時は、変更が即座に画面へ反映されたのです。

Object&Array でデータを作る場合、データ変更を <mx:Tree> ;に明示的に知らせる必要があります。


→参考:http://www.adobe.com/livedocs/flex/3_jp/langref/mx/controls/treeClasses/DefaultDataDescriptor.html

private function nodeClick():void
{
    var obj:Object = treeView.selectedItem as Object;
    if (    obj )
    {
        treeView.dataDescriptor.addChildAt(obj, { label:"added" }, 0x7fffffff);
    }
}
ところが、これは親ノード(子を持っているノード)の変化は正しく反映しますが、子を持っていないノードが子を持った瞬間を反映してくれません。

dataProvider は、ITreeDataDescriptor をインターフェースとして持っているようですが、これの元を辿ると、ICollectionView へ至ります。これは、IEventDispatcher を継承しているようです。

また、XMLListCollection は、ICollectionView を実装しているという関係から、ICollectionView を調べて見ました。
→参照:http://www.adobe.com/livedocs/flex/3_jp/langref/mx/collections/ICollectionView.html

ICollectionView は、データの更新があると mx.events.CollectionEvent.COLLECTION_CHANGE というイベントを発行します。
→参照:http://livedocs.adobe.com/flex/3_jp/langref/mx/events/CollectionEvent.html

かなり無理やりですが、更新イベントを発行するコードを追加してみました。

private function nodeClick():void
{
    var obj:Object = treeView.selectedItem as Object;
    if (    obj )
    {
        treeView.dataDescriptor.addChildAt(obj, { label:"added" }, 0x7fffffff);
        treeView.dataProvider.dispatchEvent(
            new CollectionEvent(
                CollectionEvent.COLLECTION_CHANGE,
                false,
                false,
                CollectionEventKind.ADD,
                -1,
                -1,
                []
            )
        );
    }
}
実行すると、ノードの変化を正しく画面へ反映出来る様になりました。
ただ、CollectionEvent に関しては情報が少なく、ドキュメントを読んでもパラメータの内訳の理解が正直微妙です。
もしかしたら、問題が起きる可能性もあります。


結論

よほどの事がない限り、データは XML で作り XMLListCollection を dataProvider に登録しよう!

dataProvider 関連の記事を検索すると、XML でのデータ構築方法ばかりがヒットします。 実際は、Object&Array でも階層データを構築出来るのに、何故この情報が少ないのか疑問でした。

で、頑張ってみたわけですが、調べて納得(笑)。
一部理解が微妙な部分があるのもありますが(ぉ)、面倒が嫌なら XML でササッとコーディングした方が、手間も少ないようです。




トラックバック(0)

トラックバックURL: http://n-yagi.0r2.net/sanoupulurun/mt-tb.cgi/250

コメントする

ホーム >> 左脳Script >> Adobe AIR >> Flex >> flex3 における Tree での dataProvider の扱い方

アーカイブ

このブログ記事について

このページは、n-yagiが2010年1月15日 19:14に書いたブログ記事です。

ひとつ前のブログ記事は「HTMLタグのレンダリングについての疑問」です。

次のブログ記事は「XML フィルターについて」です。

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

Creative Commons License
このブログはクリエイティブ・コモンズでライセンスされています。