Flex3のメニュー操作とアイコンの利用



ブラウザでダウンロード
メニューのアイコンについて
2008/11/22 時点の 3.1.0 build 2710 において、XML でメニュー処理すると
アイコンが使用できません。

iconFunction プロパティを使用すると、表示はされますが( 機能は正常 )
アイテム毎のコントロールができません。明らかにバグです。

それでも、アイコンを使用しなければ XML を使用できるので、それはそれで
良いのですが、プロパティの参照に XML 仕様の "@" を使用するので、
配列と共用がコード上できませんので、注意が必要です。

※ ここに紹介するコードは、AIR でも利用可能です

以下は、アイコンや画像を使用する為の埋め込み用のコードです
Icon.as
01.// メニューバー用アイコン
02.[Embed("_icon1.png")]
03.private static var icon1:Class;
04.[Embed("_icon2.png")]
05.private static var icon2:Class;
06.[Embed("_icon3.png")]
07.private static var icon3:Class;
08. 
09.// ポップアップメニュー用アイコン
10.[Embed("_InfoBox.png")]
11.private static var iconInfoBox:Class;
12.[Embed("_Favorites.png")]
13.private static var iconFavorites:Class;
14.[Embed("_Search.png")]
15.private static var iconSearch:Class;
16. 
17.// 画面背景用
18.[Embed("_back.png")]
19.private static var imgBack:Class;
20.// メニューバー背景用
21.[Embed("_back_1.png")]
22.private static var imgBack_1:Class;
23.// ポップアップ・メニューバー背景用
24.[Embed("_menuBackSmile.png")]
25.private static var imgMenuBackSmile:Class;
26. 
27.// ポップアップメニューの
28.// 動的設定用の少し大きいアイコン
29.[Embed("_iconFire.png")]
30.private static var iconFire:Class;
31. 
32.// 画面上のボタン用
33.[Bindable]
34.[Embed("_Button.png")]
35.private static var iconButton:Class;
各クラスは、以下のようにインスタンス化し、画像のプロパティを取得できます
1.// 背景画像をインスタンス化して、サイズを取得
2.var img:Bitmap = new imgBack_1();
3.e.menu.width = img.width;
4.e.menu.height = img.height;


メニュー用配列データ
01.myMenuData1 =
02.[
03.    {label: "ファイル" , icon: icon1, children:[
04.            {label: "開く",  enabled: false }
05.            ,{label: "保存", userOption: "001" }
06.            ,{type: "separator" }
07.            ,{label: "オプション" , children:[
08.                    {label: "お気に入り", icon: iconFavorites }
09.                    ,{label: "情報",  icon: iconInfoBox }
10.                    ,{label: "検索", icon: iconSearch }
11.            ]}
12.    ]}
13.,
14.    {label: "背景処理" , icon: icon2, children:[
15.            {label: "背景変更", type: "check", toggled: true }
16.            ,{type: "separator" }
17.            ,{label: "ここを動的に変更", type: "check", toggled: true }
18.    ]}
19.,
20.    {label: "予備", icon: icon3 }
21.];
最初、一つのデータで menuBar 用と ポップアップメニューで共用していたのですが、
動的変更のコントロールが複雑になるので、配列をコピーして使用しています。

ActionScript3.0 では、コピーは元が参照されるだけなので、
以下のようにして、配列データをコピーしてクローンを作成しています。
但し、icon データまではコピーされないので、後から設定しています
01.var myBA:ByteArray = new ByteArray();
02.myBA.writeObject(myMenuData1);
03.myBA.position = 0;
04.myMenuData2 = myBA.readObject();
05.// 但し、アイコンはコピーされないので自分でコピー
06.myMenuData2[0]["icon"] = icon1;
07.myMenuData2[0]["children"][3]["children"][0]["icon"] = iconFavorites;
08.myMenuData2[0]["children"][3]["children"][1]["icon"] = iconInfoBox;
09.myMenuData2[0]["children"][3]["children"][2]["icon"] = iconSearch;
10.myMenuData2[1]["icon"] = icon2;
11.myMenuData2[2]["icon"] = icon3;


全体の処理コード
メニューそのものの処理は、itemClick イベントで処理しますが、
見栄えの変更はいろいろ考慮する必要があります。

● ポップアップメニュー
作成時に全体が一つのメニューなので
スタイル設定を最初に適用できるのですが、実際はサブメニューも
含めて、表示されるたびにインスタンスが作成されて初期化されます。

ですから、menuShow をうまく使って変更しないと、最初に表示
されるルート部分の状態が引き継がれてしまいます。

● メニューバー
初期状態では、menus プロパティには何も入っていません。
一度表示されて始めて格納されます。

また、initData で dataProviderにデータをセットしていますが、
Flex の特性ですぐ使用できません。
ですから、次に発生するイベントでメニューの外観の設定を行っています、

また、メニューバーでは、一度作成されたインスタンスは
継続して使用されます。ですから、ポップアップ部分の外観の設定を動的に変更可能です。
( 厳密に言うと、menuShow イベントで毎回表示前に変える事はできます )

※ イベントが指すメニュー
itemRollOver は、マウスの下にあるメニューですが、menuShow は、
次に開かれるサブメニューを指します。この場合、サブメニューが無い場合は
なにも起こりませんし、イベントの menuShow の イベントの menu プロパティは常に使用可能です。
( マニュアルの「MenuBar アイテムがイベントを送出している場合は、null」は嘘 )


mwnuShow で、e.menu.dataProvider.getItemAt(0).label を使用しているのは、 次に開かれるサブメニューを判別する為です




001.import mx.controls.*;
002.import mx.events.*;
003.import mx.rpc.events.*;
004.import mx.formatters.*;
005.import flash.external.*;
006.import flash.events.*;
007. 
008.include "Parts.as"
009.// 画像の埋め込み
010.include "Icon.as"
011. 
012.private var myMenuData1:Array;  // 配列仕立て1
013.private var myMenuData2:Array;  // 配列仕立て2( ディープコピー先 )
014.private var popupMenu1:Menu;
015.private var save:Object;
016. 
017.// このウインドウ
018.private var me:Application = null;
019. 
020.// *********************************************************
021.// アプリケーションの初期化
022.// *********************************************************
023.public function initData():void {
024. 
025.    firebug("処理開始");
026. 
027.    me = this;
028. 
029.    // *****************************************************
030.    // 配列仕立てのポップアップメニュー
031.    // *****************************************************
032.    myMenuData1 =
033.    [
034.        {label: "ファイル" , icon: icon1, children:[
035.                {label: "開く",  enabled: false }
036.                ,{label: "保存", userOption: "001" }
037.                ,{type: "separator" }
038.                ,{label: "オプション" , children:[
039.                        {label: "お気に入り", icon: iconFavorites }
040.                        ,{label: "情報",  icon: iconInfoBox }
041.                        ,{label: "検索", icon: iconSearch }
042.                ]}
043.        ]}
044.    ,
045.        {label: "背景処理" , icon: icon2, children:[
046.                {label: "背景変更", type: "check", toggled: true }
047.                ,{type: "separator" }
048.                ,{label: "ここを動的に変更", type: "check", toggled: true }
049.        ]}
050.    ,
051.        {label: "予備", icon: icon3 }
052.    ];
053. 
054.    popupMenu1 = Menu.createMenu( null, myMenuData1, false);
055.    // 表示する内容を対応させる
056.    popupMenu1.labelField = "label";
057.    popupMenu1.iconField = "icon";
058.    // イベントを登録
059.    popupMenu1.addEventListener("itemClick", clickMenuHandler);
060.    popupMenu1.addEventListener("itemRollOver", menuItemRollOver);
061.    popupMenu1.addEventListener("menuShow", popupMenuCheck);
062. 
063.    popupMenu1.setStyle( "backgroundColor", "0x000000" );
064.    popupMenu1.setStyle("backgroundAlpha","0.2");
065.    popupMenu1.setStyle("backgroundImage", imgMenuBackSmile );
066.    popupMenu1.setStyle("backgroundSize","100%");
067.//  popupMenu1.width = 150;
068. 
069.    // *****************************************************
070.    // menuBar 用に配列をディープコピー
071.    // 【ActionScript 3.0 のプログラミング】
072.    // =>[配列の操作]
073.    // =>[配列のクローンの作成]
074.    // *****************************************************
075.    var myBA:ByteArray = new ByteArray();
076.    myBA.writeObject(myMenuData1);
077.    myBA.position = 0;
078.    myMenuData2 = myBA.readObject();
079.    // 但し、アイコンはコピーされないので自分でコピー
080.    myMenuData2[0]["icon"] = icon1;
081.    myMenuData2[0]["children"][3]["children"][0]["icon"] = iconFavorites;
082.    myMenuData2[0]["children"][3]["children"][1]["icon"] = iconInfoBox;
083.    myMenuData2[0]["children"][3]["children"][2]["icon"] = iconSearch;
084.    myMenuData2[1]["icon"] = icon2;
085.    myMenuData2[2]["icon"] = icon3;
086. 
087.    myMenuBar.dataProvider = myMenuData2;
088.}
089. 
090.// *********************************************************
091.// creationComplete
092.// *********************************************************
093.private function initApp1():void {
094. 
095.    // メニューバーのメニューは、initData では参照できないので
096.    // 見栄えをこちらで設定します
097.    var mn:Menu;
098. 
099.    // 「ファイル」メニューの設定
100.    mn = myMenuBar.getMenuAt(0);
101.    // Alpha に使われる色
102.    mn.setStyle("backgroundColor", "0x000000" );
103.    mn.setStyle("backgroundAlpha","0.2");   // ぼやけて見える
104.    mn.setStyle("backgroundImage", imgMenuBackSmile );  // アイコンの背景
105.    mn.setStyle("backgroundSize","100%");   // エリア一杯に拡大
106.    mn.setStyle("color",0xffffff);
107. 
108.    // 「背景処理」メニューの設定
109.    mn = myMenuBar.getMenuAt(1);
110.    // Alpha に使われる色
111.    mn.setStyle("backgroundColor", "0x000000" );
112.    mn.setStyle("backgroundAlpha","0.9");   // はっきり見える
113.    mn.setStyle("backgroundImage", imgBack_1 ); // 砂地の背景
114.    mn.setStyle("backgroundSize","auto");   // そのままの大きさ
115. 
116.}
117. 
118.// *********************************************************
119.// itemClick
120.// *********************************************************
121.private function clickMenuHandler(e:MenuEvent):void  {
122. 
123.    // ユーザ固有のオプションのチェック
124.    if ( e.item.hasOwnProperty("userOption") ) {
125.        Alert.show(e.item.hasOwnProperty("userOption").toString());
126.    }
127. 
128.    // menuBar の真中のメニューの背景を動的に変更する処理
129.    // 但し、ポップアップメニューでは、
130.    // 毎回メニューが動的に作成されているので、
131.    // 変更しても意味が無いので処理していません
132.    if ( e.item.label == "この背景を\n変更します" ) {
133.        if ( !e.item.toggled ) {
134.            e.menu.setStyle( "backgroundColor", "0x000000" );
135.            e.menu.setStyle("backgroundAlpha","0.2");
136.            e.menu.setStyle("backgroundImage", imgMenuBackSmile );
137.            e.menu.setStyle("backgroundSize","100%");
138.        }
139.        else {
140.            e.menu.setStyle("backgroundColor", "0x000000" );
141.            e.menu.setStyle("backgroundAlpha","0.9");
142.            e.menu.setStyle("backgroundImage", imgBack_1 );
143.            e.menu.setStyle("backgroundSize","auto");
144.        }
145.    }
146. 
147.    if ( e.label == "背景変更" ) {
148.        if ( !e.item.toggled ) {
149.            save = me.getStyle("backgroundImage");
150.            me.setStyle("backgroundImage",imgBack);
151.            // どちらか片方の状態より処理しているので、
152.            // 両方の元データを同期させる
153.            myMenuData1[1]["children"][0]["toggled"] = false;
154.            myMenuData2[1]["children"][0]["toggled"] = false;
155.        }
156.        else {
157.            me.setStyle("backgroundImage",save);
158.            // どちらか片方の状態より処理しているので、
159.            // 両方の元データを同期させる
160.            myMenuData1[1]["children"][0]["toggled"] = true;
161.            myMenuData2[1]["children"][0]["toggled"] = true;
162.        }
163.    }
164. 
165.}
166. 
167.// *********************************************************
168.// itemRollOver
169.// e.menu : マウスの下のメニュー
170.// *********************************************************
171.private function menuItemRollOver(e:mx.events.MenuEvent ):void {
172. 
173. 
174.    // ポップアップメニューの場合
175.    if ( e.menu != null ) {
176.        firebug("itemRollOver:menu:"+ e.menu );
177.        if ( e.item.label == "背景処理" ) {
178.        }
179.    }
180.    // menuBar の場合
181.    else {
182.        firebug("itemRollOver:menu:null" );
183.        if ( e.item.label == "背景処理" ) {
184.            myMenuData2[1]["children"][2]["label"] = "この背景を\n変更します";
185.            myMenuData2[1]["children"][2]["icon"] = null;
186.            myMenuData2[1]["children"][2]["enabled"] = true;
187.        }
188.    }
189. 
190. 
191.}
192. 
193.// *********************************************************
194.// menuShow
195.// サブメニューが開かれた
196.// e.menu : 開かれたメニュー
197.// *********************************************************
198.private function popupMenuCheck(e:mx.events.MenuEvent ):void {
199. 
200.    firebug("menuShow:"+ e.menu );
201. 
202.    e.menu.setStyle("leftIconGap",0);
203. 
204.    // ポップアップメニューの場合
205.    if ( e.menuBar == null ) {
206.        if ( e.menu.dataProvider.getItemAt(0).label == "開く" ) {
207.            e.menu.rowHeight = 24;
208.        }
209.        if ( e.menu.dataProvider.getItemAt(0).label == "お気に入り" ) {
210.            e.menu.rowHeight = 20;
211.        }
212.        if ( e.menu.dataProvider.getItemAt(0).label == "背景変更" ) {
213.            myMenuData1[1]["children"][2]["label"] = "この背景は変更不可";
214.            myMenuData1[1]["children"][2]["icon"] = iconFire;
215.            myMenuData1[1]["children"][2]["enabled"] = false;
216.            e.menu.width = 240;
217.        }
218.    }
219.    // menuBar の場合
220.    else {
221.        if ( e.menu.dataProvider.getItemAt(0).label == "背景変更" ) {
222.            myMenuData2[1]["children"][2]["label"] = "この背景を\n変更します";
223.            myMenuData2[1]["children"][2]["icon"] = null;
224.            myMenuData2[1]["children"][2]["enabled"] = true;
225.            // 背景画像をインスタンス化して、サイズを取得
226.            var img:Bitmap = new imgBack_1();
227.            e.menu.width = img.width;
228.            e.menu.height = img.height;
229.        }
230.    }
231.     
232. 
233.}
XML 定義部分はアプリケーションでは使用していません。
アイコンが動作しないのを確認する為に使用しました。
01.<?xml version="1.0" encoding="utf-8"?>
02.<mx:Application
03.    xmlns:mx="http://www.adobe.com/2006/mxml"
04.    initialize="initData();"
05.    creationComplete="initApp1()"
06.    layout="absolute"
07.>
08. 
09.<!-- *************************************************** -->
10.<!-- 外部ソース -->
11.<!-- *************************************************** -->
12.<mx:Style source="extern/Style.css" />
13.<mx:Script source="extern/Script.as" />
14. 
15.<!-- *************************************************** -->
16.<!-- メニューバー -->
17.<!-- *************************************************** -->
18.<mx:MenuBar
19.    x="0" y="0"
20.    id="myMenuBar"
21.    labelField="label"
22.    iconField="icon"
23.    width="100%"
24.    fontSize="12"
25.    showRoot="false"
26.    itemRollOver="menuItemRollOver(event)"
27.    itemClick="clickMenuHandler(event)"
28.    menuShow="popupMenuCheck(event)"
29./>
30. 
31.<!-- *************************************************** -->
32.<!-- ボタン -->
33.<!-- *************************************************** -->
34.<mx:Button
35.    x="20" y="60"
36.    id="btn1"
37.    icon="{iconButton}"
38.    label=""
39.    styleName="orangeButton"
40.    click="popupMenu1.show(btn1.x + btn1.width, btn1.y);"
41./>
42. 
43.<mx:XML format="e4x" id="myMenuDataXML">
44.<root>
45.    <menuitem label="ファイル" icon="icon1">
46.        <menuitem label="開く" enabled="false"/>
47.        <menuitem label="保存" userOption="001" />
48.        <menuitem type="separator"/>
49.        <menuitem label="オプション">
50.            <menuitem label="お気に入り" icon="iconFavorites"/>
51.            <menuitem label="情報" icon="iconInfoBox"/>
52.            <menuitem label="検索" icon="iconSearch"/>
53.        </menuitem>
54.    </menuitem>
55.    <menuitem label="背景処理">
56.        <menuitem label="背景変更" type="check" toggled="true" />
57.        <menuitem type="separator"/>
58.        <menuitem label="ここを動的に変更" type="check" toggled="true" />
59.    </menuitem>
60.    <menuitem label="予備" icon="icon3"/>
61.</root>
62.</mx:XML>
63. 
64.</mx:Application>