kagamihogeの日記

kagamihogeの日記です。

Flex の Menu コンポーネント

こんな感じのアレ。

mx.controls.Menu - ActionScript 3.0 言語およびコンポーネントリファレンス を見ながら。どうやって使うのか良くわからず意外と苦労した……

まず、データプロバイダに XML を使うやり方。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.controls.Menu;
            [Embed(source='khoge.jpg')]
            public var Image0 : Class;

            private var xmlMenuData:XML = 
                <root>
                    <menuitem label="MenuItem 1" type="check"/>
                    <menuitem label="MenuItem 2" icon="Image0">
                        <menuitem label="MenuItem 4"/>
                        <menuitem label="MenuItem 5"/>
                    </menuitem>
                    <menuitem type="separator"/>
                    <menuitem label="MenuItem 3"/>
                </root>

            private function createAndShow():void {
                var myMenu:Menu;
                myMenu = Menu.createMenu(null, xmlMenuData, false);
                myMenu.labelField="@label";
                myMenu.iconField="@icon";
                //ボタンの下に表示する
                myMenu.show(
                    createAndShowButton.x,
                    createAndShowButton.y + createAndShowButton.height);
            }
        ]]>
    </mx:Script>
    
    <mx:Button id="createAndShowButton" label="▼" click="createAndShow()"/>
</mx:Application>

次に、データプロバイダに ArrayCollection を使うやり方。XML を使うやり方と表示されるものは同じ。

データプロバイダの型は XML なり Object の配列なり ArrayCollection でよくて、属性名が指定可能なものを使っていれば良いようだ。Java 屋さんからすると、こういうダイナミックな部分は中々慣れんくて苦労する……

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import mx.controls.Menu;
            [Embed(source='khoge.jpg')]
            public var Image0 : Class;
        
            private var collectionMenuData:ArrayCollection = new ArrayCollection([
                {label:"MenuItem 1", type:"check"},
                {label:"MenuItem 2", icon:"Image0", children: [
                    {label: "MenuItem 4"},
                    {label: "MenuItem 5"}
                    ]},
                {type:"separator", icon:"Image0"},
                {label:"MenuItem 3"}
                ]);

            private function createAndShow():void {
                var myMenu:Menu;
                myMenu = Menu.createMenu(null, collectionMenuData);
                myMenu.show(
                    createAndShowButton.x,
                    createAndShowButton.y + createAndShowButton.height);
            }
        ]]>
    </mx:Script>
    
    <mx:Button id="createAndShowButton" label="▼" click="createAndShow()"/>
</mx:Application>

最初に迷ったのは ArrayCollection 使う場合にメニューアイテムを入れ子にするやり方。XML みたいに子タグを定義すればいいわけでもないし、どうやったら……と思っていました。

その他のオブジェクト アイテムの配列、またはアイテムの配列を含むオブジェクトで、ノードの子が children という名前のアイテムに含まれているもの。

メニューベースのコントロールの使用 - メニューの構造とデータの定義 - Adobe Flex 3 ヘルプ より抜粋

children という名前のプロパティに入れりゃいいとかわかるかよw こ、これだから動的な言語は…… って Eclipse の補完に慣れ過ぎなだけですねスミマセン。

次。メニューの中身を動的に作りたい場合はデータプロバイダをいじってやれば切り替わってくれる。

コレクション ArrayCollection クラスや XMLListCollection クラスなどの ICollectionView インターフェイスを実装していて、そのデータソースが上記の 2 つの項目のいずれかで指定された構造に適合しているオブジェクト。DefaultDataDescriptor クラスには、コレクションを効率的に処理できるコードが含まれています。メニューのデータが動的に変わる場合は、コレクションをデータプロバイダとして使用してください。それ以外を使用すると、メニューのデータが更新されずに古くなります。

メニューベースのコントロールの使用 - メニューの構造とデータの定義 - Adobe Flex 3 ヘルプ より抜粋

たとえばこんな感じ。どう変化するかはソース通りと言いますか。

private function modifyMenu():void {
    collectionMenuData.addItem({label:"AddMentItem"});
    collectionMenuData[0].type = "radio";
    var menuItem:Object;
    for each(menuItem in collectionMenuData) {
        menuItem.icon = "Image0";
    }
}

メニュー選択したときのイベントハンドラは何時ものように addEventListener で追加。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.events.MenuEvent;
            import mx.collections.ArrayCollection;
            import mx.controls.Menu;
            [Embed(source='khoge.jpg')]
            public var Image0 : Class;
        
            private var collectionMenuData:ArrayCollection = new ArrayCollection([
                {label:"MenuItem 1", menuitemId:"hoge1", type:"check"},
                {label:"MenuItem 2", menuitemId:"hoge2", icon:"Image0", children: [
                    {label: "MenuItem 4", menuitemId:"hoge4"},
                    {label: "MenuItem 5", menuitemId:"hoge5"}
                    ]},
                {label:"無視される", type:"separator", icon:"Image0"},
                {label:"MenuItem 3", menuitemId:"hoge3", menuitemSelectFunction:function():void {trace("menuitemSelectFunction");}}
                ]);
        
        
            private function createAndShow():void {
                var myMenu:Menu;
                myMenu = Menu.createMenu(null, collectionMenuData);
                myMenu.addEventListener(MenuEvent.ITEM_CLICK, menuHandler);
                myMenu.show(
                    createAndShowButton.x,
                    createAndShowButton.y + createAndShowButton.height);
            }
            
            private function menuHandler(event:MenuEvent):void  {
                trace(event.index);
                if (event.item == collectionMenuData[0]) {
                    trace(event.item.label + " selected");
                }
                if (event.item.menuitemId == "hoge3") {
                    trace(event.item.label + " selected");
                }
                if (event.item.menuitemSelectFunction) {
                    event.item.menuitemSelectFunction();
                }
            }
        ]]>
    </mx:Script>
    
    <mx:Button id="createAndShowButton" label="▼" click="createAndShow()"/
</mx:Application>

メニュー選んだあとの分岐処理だけども。event.index は、その階層の位置を示すので分岐処理にはちょっと向いてなさげ。このソース例だと MenuItem 1 は 0、MenuItem 2 は 1、MenuItem 3 は 3、MenuItem 4 は 0、MenuItem 5 は 1 を返す。

なので、データプロバイダに含めるデータにテキトーな属性を入れるやり方がまず思いつく。ここでは menuitemId ってのを追加してる。この属性そのものはメニューの表示には何ら影響を及ぼさない*1。ので、その文字列なり何なりで分岐させる。

が、文字列で判定するってのはイマイチというか……やはりデータプロバイダのデータ構造(というかクラス)をちょっと工夫してやらんと危険ですかね。

あとは属性に関数持っておいてソレをそのまま呼ぶ、とかかなぁ。



というわけで Menu 使う上でのポイントとしては……

  • メニューに渡すデータ形式XML, その他のオブジェクト, コレクション。
  • XML の属性 or オブジェクトのプロパティ名が label とか type とか指定可能な属性になってればメニューは表示される。
  • メニューを動的に変更したい場合はデータプロバイダをいじる。
  • メニュー項目が沢山ある(=イベントハンドラも沢山ある)場合はデータ構造工夫しないと頭こんがらがりそうな予感。

こんなところか。他の細かいとこはドキュメント読みましょうということで。

*1:ただし、デフォルトの状態の場合。よくわからんけど dataDescriptor ってのを拡張すればその限りではないようだ