FlexのバインドとSkinで起きた予想外のこと
先日、お仕事でカスタムコンポーネントとカスタムスキンを作ってた時のお話。
いつものように、SkinnableComponentを継承してスキン適用可能なコンポーネントを作ってたのですが
渡されるデータによって、スキンを変えたいなーと思い立って、こんな感じのことをしてみました。
<s:SkinnableComponent xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" skinClass="skins.State1Skin" skinClass.state1="skins.State1Skin" skinClass.state2="skins.State2Skin" skinClass.state3="skins.State3Skin"> <fx:Declarations> <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 --> </fx:Declarations> <fx:Script> <![CDATA[ import models.HogeData; import models.HogeDataState1; import models.HogeDataState2; import models.HogeDataState3; private var _hogeData:HogeData; [Bindable] public function get hogeData():HogeData { return _hogeData; } public function set hogeData(value:HogeData):void { if (_hogeData == value) return; _hogeData = value; if (value is HogeDataState1) { super.currentState = "state1"; } else if (value is HogeDataState2) { super.currentState = "state2"; } else if (value is HogeDataState3) { super.currentState = "state3"; } } ]]> </fx:Script> <s:states> <s:State name="state1" /> <s:State name="state2" /> <s:State name="state3" /> </s:states> </s:SkinnableComponent>
こんな感じに、HogeDataクラスを継承したHogeDataState1とHogeDataState2とHogeDataState3というモデルクラスを作って
その型を判定して、適用するスキンを変えてみよう、という試みです。
いやあ、共通する部分がありながらも、それぞれ独自に違うデータを持っているモデルなので
そのために3つのコンポーネントとスキンを作るのって何だかかったるいじゃないですか。
なので、実際、こういう事をやってもいいかどうかはともかくとして、こんな感じの実装をしてみたわけです。
スキン側からは、当然、[Bindable]のhogeDataというプロパティを表示しようとするので、向こう側でのバインド式はこんな感じになります。
<s:Label text="{HogeDataState1(hostComponent.hogeData).hoge1}" />こんな感じね。強制型変換を使ってバインドしていたんですよ。
僕の思考としては、スキン側からしたら、あるステートでしかこのスキンが適用されないし
その時のhogeDataの型はhogeDataState1なわけだから、この書式でいいだろう。と。
で、実際に、カスタムコンポーネント1つと3つのスキンを作って、実際に動かしてみたわけです。
HogeDataState1のインスタンスをhogeDataプロパティに代入したら
「強制型変換に失敗しました」というエラーが出ました。
むむ?と思って、StackTraceのソースファイルと行番号を見ると、何故かこれがState2Skin内で出ているんですよね。
これが、どうもですね、スキン適用されてなくても、hostComponent.hogeDataが変更されたら、バインド式が実行されちゃうみたいなんです。
なるほどなー。確かに考えてみれば、BindingUtilsを使ったりして、バインドしてみれば納得の行く話だったりしますが、MXMLでバインド使っているとどうもそういうのが見えなくなるもんですね。
なんで、こんな感じの記述に変えて、一旦は凌ぐことにしました。
<s:Label text="{(hogeComponent.hogeData as HogeDataState1).hoge1}" />ちなみに、こうやって書いておくと、warningがいっぱいtraceされちゃいますけど、まあ、うん、一旦ね。一旦。
MXMLフェチの僕がカスタムMXMLタグを作った1(NativeMenu編)
はるか昔、こんな記事を書いたのを、久々に見返して、Flex4用に作りなおすお話し。
しかし、この記事。2009年なのか。
随分、遠くまで来たものだな。
まず、リファクタリング
キャッシュするように変更しました
昔の僕はおっちょこちょいだなあ。
毎回、オブジェクト生成するなんて、明らかに手抜きじゃないか。手抜きだよな?そうだよな?(不安)
MenuTree.as(一部抜粋)
/** * メニューを生成するための関数 * IMenuTree で定義されている * @return * */ private var _nativeMenuCache:NativeMenuItem; public function getMenu():NativeMenuItem { // キャッシュがある場合は、そのまま返す if (_nativeMenuCache) return _nativeMenuCache; // 新たにNativeMenuを生成 var menu:NativeMenu = new NativeMenu(); for each (var item:IMenuTree in _menuList) { var menuItem:NativeMenuItem = item.getMenu(); var subMenu:NativeMenu = menuItem.submenu; if (subMenu != null ) { menu.addSubmenu( subMenu, item.label ); } else { menu.addItem( menuItem ); } } _nativeMenuCache = new NativeMenuItem(_label); _nativeMenuCache.submenu = menu; return _nativeMenuCache; }
ちゃんと、毎回生成しないように変更。
好みが変わりました
さて、あと気になるのが、このコード。
(旧)NativeMenu.as(一部抜粋)
/** * 自分が子要素を持たない時に呼ばれるメニュー生成用関数 * @return * */ public function getNativeMenu():NativeMenu { var item:NativeMenuItem = getMenu(); return item.submenu; }
interfaceにも定義されてないし、あまり個人的には好きくない。
当時の僕はどうだったか知らないけど。
つまりは、SystemIconTrayにNativeMenuを設定する際に呼びたい関数って事なのですな。
こんな感じで。
呼び出しイメージ
// ここでmxmlMenuはMXMLに書かれたMenuTree var systemTray:SystemTrayIcon = this.nativeApplication.icon as SystemTrayIcon; systemTray.menu = mxmlMenu.getNativeMenu();
あ、Macの事を考えればもう少し違う書き方になるな。
まあ、いいや。一旦、Windows想定ということで。
なんにせよ、このコードが書きたいために、getNativeMenuという関数を書いちゃったのは、ちょっと好みに反する。
ということで、こんなコードに書き換えました。
(新)MenuTree.as(一部抜粋)
/** * メニュー生成用関数 * @return * */ public static function createNativeMenu(tree:MenuTree):NativeMenu { var item:NativeMenuItem = tree.getMenu(); return item.submenu; }
staticな関数にしました。
まあ、好みの問題です。
Flex4で記述してみる
こんな感じにリファクタリングが終わったので、実際にFlex4でMXMLを記述してみました。
AirAppTest.mxml
<?xml version="1.0" encoding="utf-8"?><s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"xmlns:s="library://ns.adobe.com/flex/spark"xmlns:mx="library://ns.adobe.com/flex/mx"xmlns:menu="me.rkome.da.mxml.menu.*"creationComplete="onCreate()"><fx:Declarations><!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 --><menu:MenuTree id="mxmlMenu"><menu:MenuTree label="設定変更"><menu:MenuTreeItem label="萌え萌えモード" select="Alert.show('萌え萌え~☆キュンっ♪')" /><menu:MenuTreeItem label="ざわざわモード" select="Alert.show('圧倒的・・・見落としっ・・!')" /></menu:MenuTree><menu:Separator /><menu:MenuTreeItem label="終了" select="this.nativeApplication.exit()" /></menu:MenuTree></fx:Declarations><fx:Script><![CDATA[import mx.controls.Alert;[Embed(source="../assets/icon_016.png")] private static const TrayIconClass:Class;private function onCreate():void{if (NativeApplication.supportsSystemTrayIcon){var systemIconTray:SystemTrayIcon = this.nativeApplication.icon as SystemTrayIcon;systemIconTray.menu = MenuTree.createNativeMenu(mxmlMenu);systemIconTray.bitmaps = [new TrayIconClass()];}elseif (NativeApplication.supportsDockIcon){var dockIcon:DockIcon = this.nativeApplication.icon as DockIcon;dockIcon.menu = MenuTree.createNativeMenu(mxmlMenu);dockIcon.bitmaps = [new TrayIconClass()];}}]]></fx:Script></s:WindowedApplication>
うん。問題なさそう。やはりstaticにしたのが個人的にはいいね。
実行したら、ちゃんと、こんな感じにできました。
おお ゆうしゃよ 更新が止まってしまうとはなさけない
「生きてます」
意味:ブログの更新が止まってた人が久々に更新した時に唱える復活の呪文。
ということで、日々の業務をブログに残す事で、なんか、こう、仕事をやった気分になって行こうという試みは僕のスタンド能力「3 days bishop」が発動して、志半ばにして潰えたわけです。
考えても見れば、僕は三日坊主どころか三日も続かない性格なのでした。
なので、今後は、もう、習慣化みたいなのを掲げるのはやめて気軽に更新してもしなくてもいい、みたいなスタンスで行った方が実は毎日続く的な事を期待しようと思います。
誰に向けた、何の所信表明なんだって感じだけど。
