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



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

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

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

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

以下は、アイコンや画像を使用する為の埋め込み用のコードです
Icon.as
// メニューバー用アイコン
[Embed("_icon1.png")]
private static var icon1:Class;
[Embed("_icon2.png")]
private static var icon2:Class;
[Embed("_icon3.png")]
private static var icon3:Class;

// ポップアップメニュー用アイコン
[Embed("_InfoBox.png")]
private static var iconInfoBox:Class;
[Embed("_Favorites.png")]
private static var iconFavorites:Class;
[Embed("_Search.png")]
private static var iconSearch:Class;

// 画面背景用
[Embed("_back.png")]
private static var imgBack:Class;
// メニューバー背景用
[Embed("_back_1.png")]
private static var imgBack_1:Class;
// ポップアップ・メニューバー背景用
[Embed("_menuBackSmile.png")]
private static var imgMenuBackSmile:Class;

// ポップアップメニューの
// 動的設定用の少し大きいアイコン
[Embed("_iconFire.png")]
private static var iconFire:Class;

// 画面上のボタン用
[Bindable]
[Embed("_Button.png")]
private static var iconButton:Class;
各クラスは、以下のようにインスタンス化し、画像のプロパティを取得できます
// 背景画像をインスタンス化して、サイズを取得
var img:Bitmap = new imgBack_1();
e.menu.width = img.width;
e.menu.height = img.height;


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

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


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

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

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

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

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

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

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


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




import mx.controls.*;
import mx.events.*; 
import mx.rpc.events.*;
import mx.formatters.*;
import flash.external.*;
import flash.events.*; 

include "Parts.as"
// 画像の埋め込み
include "Icon.as"

private var myMenuData1:Array;	// 配列仕立て1
private var myMenuData2:Array;	// 配列仕立て2( ディープコピー先 )
private	var popupMenu1:Menu;
private var save:Object;

// このウインドウ
private var me:Application = null;

// *********************************************************
// アプリケーションの初期化
// *********************************************************
public function initData():void {

	firebug("処理開始");

	me = this;

	// *****************************************************
	// 配列仕立てのポップアップメニュー
	// *****************************************************
	myMenuData1 = 
	[
		{label: "ファイル" , icon: icon1, children:[
				{label: "開く",  enabled: false }
				,{label: "保存", userOption: "001" }
				,{type: "separator" }
				,{label: "オプション" , children:[
						{label: "お気に入り", icon: iconFavorites }
						,{label: "情報",  icon: iconInfoBox }
						,{label: "検索", icon: iconSearch }
				]}
		]}
	,
		{label: "背景処理" , icon: icon2, children:[
				{label: "背景変更", type: "check", toggled: true }
				,{type: "separator" }
				,{label: "ここを動的に変更", type: "check", toggled: true }
		]}
	,
		{label: "予備", icon: icon3 }
	];

	popupMenu1 = Menu.createMenu( null, myMenuData1, false);
	// 表示する内容を対応させる
	popupMenu1.labelField = "label";
	popupMenu1.iconField = "icon";
	// イベントを登録
	popupMenu1.addEventListener("itemClick", clickMenuHandler);
	popupMenu1.addEventListener("itemRollOver", menuItemRollOver);
	popupMenu1.addEventListener("menuShow", popupMenuCheck);

	popupMenu1.setStyle( "backgroundColor", "0x000000" );
	popupMenu1.setStyle("backgroundAlpha","0.2");
	popupMenu1.setStyle("backgroundImage", imgMenuBackSmile );
	popupMenu1.setStyle("backgroundSize","100%");
//	popupMenu1.width = 150;

	// *****************************************************
	// menuBar 用に配列をディープコピー
	// 【ActionScript 3.0 のプログラミング】
	// =>[配列の操作]
	// =>[配列のクローンの作成]
	// *****************************************************
	var myBA:ByteArray = new ByteArray();
	myBA.writeObject(myMenuData1);
	myBA.position = 0;
	myMenuData2 = myBA.readObject();
	// 但し、アイコンはコピーされないので自分でコピー
	myMenuData2[0]["icon"] = icon1;
	myMenuData2[0]["children"][3]["children"][0]["icon"] = iconFavorites;
	myMenuData2[0]["children"][3]["children"][1]["icon"] = iconInfoBox;
	myMenuData2[0]["children"][3]["children"][2]["icon"] = iconSearch;
	myMenuData2[1]["icon"] = icon2;
	myMenuData2[2]["icon"] = icon3;

	myMenuBar.dataProvider = myMenuData2;
}

// *********************************************************
// creationComplete
// *********************************************************
private function initApp1():void {

	// メニューバーのメニューは、initData では参照できないので
	// 見栄えをこちらで設定します
	var mn:Menu;

	// 「ファイル」メニューの設定
	mn = myMenuBar.getMenuAt(0);
	// Alpha に使われる色
	mn.setStyle("backgroundColor", "0x000000" );
	mn.setStyle("backgroundAlpha","0.2");	// ぼやけて見える
	mn.setStyle("backgroundImage", imgMenuBackSmile );	// アイコンの背景
	mn.setStyle("backgroundSize","100%");	// エリア一杯に拡大
	mn.setStyle("color",0xffffff);

	// 「背景処理」メニューの設定
	mn = myMenuBar.getMenuAt(1);
	// Alpha に使われる色
	mn.setStyle("backgroundColor", "0x000000" );
	mn.setStyle("backgroundAlpha","0.9");	// はっきり見える
	mn.setStyle("backgroundImage", imgBack_1 );	// 砂地の背景
	mn.setStyle("backgroundSize","auto");	// そのままの大きさ

}

// *********************************************************
// itemClick
// *********************************************************
private function clickMenuHandler(e:MenuEvent):void  {

	// ユーザ固有のオプションのチェック
	if ( e.item.hasOwnProperty("userOption") ) {
		Alert.show(e.item.hasOwnProperty("userOption").toString());
	}

	// menuBar の真中のメニューの背景を動的に変更する処理
	// 但し、ポップアップメニューでは、
	// 毎回メニューが動的に作成されているので、
	// 変更しても意味が無いので処理していません
	if ( e.item.label == "この背景を\n変更します" ) {
		if ( !e.item.toggled ) {
			e.menu.setStyle( "backgroundColor", "0x000000" );
			e.menu.setStyle("backgroundAlpha","0.2");
			e.menu.setStyle("backgroundImage", imgMenuBackSmile );
			e.menu.setStyle("backgroundSize","100%");
		}
		else {
			e.menu.setStyle("backgroundColor", "0x000000" );
			e.menu.setStyle("backgroundAlpha","0.9");
			e.menu.setStyle("backgroundImage", imgBack_1 );
			e.menu.setStyle("backgroundSize","auto");
		}
	}

	if ( e.label == "背景変更" ) {
		if ( !e.item.toggled ) {
			save = me.getStyle("backgroundImage");
			me.setStyle("backgroundImage",imgBack);
			// どちらか片方の状態より処理しているので、
			// 両方の元データを同期させる
			myMenuData1[1]["children"][0]["toggled"] = false;
			myMenuData2[1]["children"][0]["toggled"] = false;
		}
		else {
			me.setStyle("backgroundImage",save);
			// どちらか片方の状態より処理しているので、
			// 両方の元データを同期させる
			myMenuData1[1]["children"][0]["toggled"] = true;
			myMenuData2[1]["children"][0]["toggled"] = true;
		}
	}

}

// *********************************************************
// itemRollOver
// e.menu : マウスの下のメニュー
// *********************************************************
private function menuItemRollOver(e:mx.events.MenuEvent ):void {


	// ポップアップメニューの場合
	if ( e.menu != null ) {
		firebug("itemRollOver:menu:"+ e.menu );
		if ( e.item.label == "背景処理" ) {
		}
	}
	// menuBar の場合
	else {
		firebug("itemRollOver:menu:null" );
		if ( e.item.label == "背景処理" ) {
			myMenuData2[1]["children"][2]["label"] = "この背景を\n変更します";
			myMenuData2[1]["children"][2]["icon"] = null;
			myMenuData2[1]["children"][2]["enabled"] = true;
		}
	}


}

// *********************************************************
// menuShow
// サブメニューが開かれた
// e.menu : 開かれたメニュー
// *********************************************************
private function popupMenuCheck(e:mx.events.MenuEvent ):void {

	firebug("menuShow:"+ e.menu );

	e.menu.setStyle("leftIconGap",0);

	// ポップアップメニューの場合
	if ( e.menuBar == null ) {
		if ( e.menu.dataProvider.getItemAt(0).label == "開く" ) {
			e.menu.rowHeight = 24;
		}
		if ( e.menu.dataProvider.getItemAt(0).label == "お気に入り" ) {
			e.menu.rowHeight = 20;
		}
		if ( e.menu.dataProvider.getItemAt(0).label == "背景変更" ) {
			myMenuData1[1]["children"][2]["label"] = "この背景は変更不可";
			myMenuData1[1]["children"][2]["icon"] = iconFire;
			myMenuData1[1]["children"][2]["enabled"] = false;
			e.menu.width = 240;
		}
	}
	// menuBar の場合
	else {
		if ( e.menu.dataProvider.getItemAt(0).label == "背景変更" ) {
			myMenuData2[1]["children"][2]["label"] = "この背景を\n変更します";
			myMenuData2[1]["children"][2]["icon"] = null;
			myMenuData2[1]["children"][2]["enabled"] = true;
			// 背景画像をインスタンス化して、サイズを取得
			var img:Bitmap = new imgBack_1();
			e.menu.width = img.width;
			e.menu.height = img.height;
		}
	}
	

}
XML 定義部分はアプリケーションでは使用していません。
アイコンが動作しないのを確認する為に使用しました。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	initialize="initData();"
	creationComplete="initApp1()"
	layout="absolute"
>

<!-- *************************************************** -->
<!-- 外部ソース -->
<!-- *************************************************** -->
<mx:Style source="extern/Style.css" />
<mx:Script source="extern/Script.as" />

<!-- *************************************************** -->
<!-- メニューバー -->
<!-- *************************************************** -->
<mx:MenuBar
	x="0" y="0"
	id="myMenuBar"
	labelField="label"
	iconField="icon"
	width="100%"
	fontSize="12"
	showRoot="false"
	itemRollOver="menuItemRollOver(event)"
	itemClick="clickMenuHandler(event)"
	menuShow="popupMenuCheck(event)"
/>

<!-- *************************************************** -->
<!-- ボタン -->
<!-- *************************************************** -->
<mx:Button
	x="20" y="60"
	id="btn1"
	icon="{iconButton}" 
	label=""
	styleName="orangeButton"
	click="popupMenu1.show(btn1.x + btn1.width, btn1.y);"
/>

<mx:XML format="e4x" id="myMenuDataXML">
<root>
	<menuitem label="ファイル" icon="icon1">
		<menuitem label="開く" enabled="false"/>
		<menuitem label="保存" userOption="001" />
		<menuitem type="separator"/>
		<menuitem label="オプション">
			<menuitem label="お気に入り" icon="iconFavorites"/>
			<menuitem label="情報" icon="iconInfoBox"/>
			<menuitem label="検索" icon="iconSearch"/>
		</menuitem>
	</menuitem>
	<menuitem label="背景処理">
		<menuitem label="背景変更" type="check" toggled="true" />
		<menuitem type="separator"/>
		<menuitem label="ここを動的に変更" type="check" toggled="true" />
	</menuitem>
	<menuitem label="予備" icon="icon3"/>
</root>
</mx:XML>

</mx:Application>