独自メタデータタグを作る方法
Firefoxに初めて、並々ならぬ殺意を覚えた。
なに、俺が珍しくブログ書いてる時に何の前触れも無く落ちやがる。
もう、全部消えたやないけ!
泣くぞ?俺は泣くと手が付けられないんだからな!?
(ノ_<、)
恨み言はこれくらいにして、書き直します。
いいんだ、どうせ僕のポストなんて大宇宙の意思に比べたら全然小さいんだ。だから別に消えても世界は同じように回るんだ。
独自メタデータタグを作る方法
前回のポストでメタデータタグがあまりに便利だったので、オレオレメタデータタグが作りたくなった。以上。
投げやり?苦情はFirefoxに言ってくれ。
そんなこんなで、独自のメタデータタグを作る方法を調べてみました。
Annotating ActionScript Classes with Custom Metadata + Simple ORM Framework for AIR
http://coenraets.org/blog/2007/10/annotating-actionscript-classes-with-custom-metadata-simple-orm-framework-for-air/
AS3で独自メタデータの保持
http://www.be-interactive.org/?itemid=202
上のサイトは英語です。でも、独自メタデータをどうやって使うかがサンプルコードを見ると分かります。
describeTypeっていう関数を使って、インスタンスからクラス構造をXMLで取得できるので、そのXMLを解析すればメタデータタグの情報が取得できるってわけです。
下のサイトはBeInteractive!のyossyさんが独自メタデータタグを使ったコードのビルド方法について書かれている記事です。
これを参考にビルドしてみました。
サンプル
Player.as
{
public class Player
{
public function Player()
{
}
private var _test:String;
[DarkOmeme("programmer")]
public function set test(val:String):void
{
_test = val;
}
public function get test():String
{
return _test;
}
}
}
サンプルとしてこんなクラスを作ってみました。クラス名は適当です。本当は理由があったんだけど、迷宮入りです。Firefoxに言ってください。
testってプロパティにDarkOmemeって独自メタデータを付加して、値として"programmer"という文字列を渡しています。
さて、ではこいつのインスタンスを実際にdescribeTypeして見ましょう。
MetadataApp.mxml
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
import flash.utils.describeType;
import me.rkome.da.labs.Player;
private function onCreationComplete():void
{
var player:Player = new Player();
trace(describeType(player));
}
]]>
</mx:Script>
</mx:Application>
こんなアプリケーションを作ってみました。って事でビルドしてみましょう。
ビルドオプションはこんな感じ。
さて、結果はどう表示されるかというと。こんなん出ました。
<extendsClass type="Object"/>
<accessor name="test" access="readwrite" type="String" declaredBy="me.rkome.da.labs::Player">
<metadata name="DarkOmeme">
<arg key="" value="programmer"/>
</metadata>
</accessor>
</type>
オー、チャントDarkOmemeメタデータタグガフカサレテイルノガワカリマスネー。スゲーヤ。知ってるけどな!さっきやったから!
まー、あとはこのXMLを解析して煮るなり焼くなり、しましょうって事ですね。
次に、Flexでは有名なメタデータタグである[Bindable]を付加して同じようにdescribeTypeしてみました。そしたら、少し驚きだったんですよ。さっきね!
<extendsClass type="Object"/>
<implementsInterface type="flash.events::IEventDispatcher"/>
<method name="addEventListener" declaredBy="me.rkome.da.labs::Player" returnType="void">
<parameter index="1" type="String" optional="false"/>
<parameter index="2" type="Function" optional="false"/>
<parameter index="3" type="Boolean" optional="true"/>
<parameter index="4" type="int" optional="true"/>
<parameter index="5" type="Boolean" optional="true"/>
</method>
<method name="dispatchEvent" declaredBy="me.rkome.da.labs::Player" returnType="Boolean">
<parameter index="1" type="flash.events::Event" optional="false"/>
</method>
<method name="hasEventListener" declaredBy="me.rkome.da.labs::Player" returnType="Boolean">
<parameter index="1" type="String" optional="false"/>
</method>
<method name="removeEventListener" declaredBy="me.rkome.da.labs::Player" returnType="void">
<parameter index="1" type="String" optional="false"/>
<parameter index="2" type="Function" optional="false"/>
<parameter index="3" type="Boolean" optional="true"/>
</method>
<method name="willTrigger" declaredBy="me.rkome.da.labs::Player" returnType="Boolean">
<parameter index="1" type="String" optional="false"/>
</method>
<accessor name="test" access="readwrite" type="String" declaredBy="me.rkome.da.labs::Player">
<metadata name="Bindable">
<arg key="event" value="propertyChange"/>
</metadata>
<metadata name="DarkOmeme">
<arg key="" value="programmer"/>
</metadata>
</accessor>
</type>
なんと、IEventDispatcherが自動的に実装されてるじゃあ、ありませんか!自動的に!
どうやら、調べてみるとこれはmxmlc(Flexのコンパイラ)がメタデータタグを見つけてコードを自動生成するらしくて、これみたいなメタデータタグは今回の方法では作れないようです。
まあ、mxmlcはオープンソースなんで、オレオレmxmlcを作りましょうって事ですね。わかります。
今回はそこまで深入りしませんでした。
ただ、一応、mxmlcのソース(Javaで書かれている)を見に行ったんですが、どうやらコードを自動生成する部分はJavaでない、何やら良く分からないスクリプトで書かれていました。
多分、テンプレートエンジンみたいのを備えていて、そいつに通すと自動生成されるんだと思います。
さて、本当はmxmlcのメタデータタグみたいなのが作れるといいなあと思ったんですが、思ったよりヘビーなんでこの方法で何か出来ないか模索してみることにします。
最後に。
Firefoxさんいつもお世話になっています。ありがとう。
試しにMXMLでメニューを作ってみた
前置き
今、AIRでタスクトレイ常駐型のアプリを作ってます。
それでタスクトレイに常駐した際にタスクアイコンをクリックするとメニューが出るようにしたいと思って
処理を書いてたんですが、これが結構面倒くさい。
僕はある程度静的な処理は極力AS3じゃなくてMXMLで書きたいという、無類のMXMLフェチです。
なので、タスクトレイに常駐したアイコンを右クリックした時に出るNativeMenuをいちいちAS3で、addMenuだのaddSubmenuだのするのはあまり好きくないわけです。
そこで、MXMLでNativeMenuが定義できちゃうようなクラスを作っちゃおうと考えて調べてみました。
自分でMXMLタグを定義する方法
まず、カスタムMXMLタグを定義します。
いくつか方法はあります。
- カスタムコンポーネントを作る
- 既存のクラスを継承する
- UIComponentを継承して作る
- IUIComponentインターフェースを実装して作る(神の領域)
- 非ビジュアライズコンポーネントを作る
- 既存のクラスを継承する
- IMXMLObjectインターフェースを実装して作る
今回の場合、カスタムコンポーネントでNativeなメニューが作れないので、IMXMLObjectインターフェースを実装して定義することにしました。
仕様
Nativeなメニューを定義するMXML(イメージ)
<?xml version="1.0" encoding="utf-8"?>
<menu:MenuTree xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:menu="me.rkome.da.mxml.menu.*">
<menu:menuList>
<menu:MenuTreeItem label="表示" select="setVisible(true)" />
<menu:Separator />
<menu:MenuTreeItem label="終了" select="onTerminate()" />
</menu:menuList>
</menu:MenuTree>
こんな感じにNativeなメニューをSystemTrayMenu.mxmlというファイルにして定義できればいいなあと思います。
MenuTreeというのがメニューのグループになっていて、このクラスの子要素としてMenuTreeItemを書くと
メニューのラベルとクリックされた時のイベント内容を定義できるようになっています。
labelプロパティが表示する文字で、selectイベントがメニューがクリックされた時のイベントです。
Separatorはメニューラベル間を区切る為のメニューアイテムです。
こんな感じのMXMLを書いて、MenuTreeのインスタンスを使って、getMenu()とか呼び出すだけで
MXMLのツリー構造の通りのNativeなメニューが作れたら便利じゃーんというのが今回の仕様です。
実際にMXMLタグを作ってみた
さて、ではMXMLタグを定義する方法です。
FlexにはIMXMLObjectっていうinterfaceがあって、これを実装したクラスはコンポーネントじゃなくてもMXMLで記述できるようになるようです。
mx.core.IMXMLObject (Flex 3.2)
http://livedocs.adobe.com/flex/3_jp/langref/mx/core/IMXMLObject.html
IMXMLObjectの実装について
http://livedocs.adobe.com/flex/3_jp/docs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001702.html
まあ、これくらいは無類のMXMLフェチの名乗るくらいなので知っています。確認です。
MXMLでたとえばListタグの子要素として直接dataProviderの情報を書けたりするあれ。普通なら<mx:dataProvider></mx:dataProvider>で囲まないといけないのに、なんでーとか思ってたら、どうやら[DefaultProperty]っていうメタデータタグがあるらしい。
あと、コンポーネントのビヘイビアを記述するとき、Flex Builderで出てくる候補リストにはFadeとかMoveとかエフェクト関連しか出てこないのは、これもどうも[ArrayElementType]っていうメタデータタグのおかげらしい。
メタデータタグか・・。じゅるり。
さて、メタデータタグフェチになる前にとりあえずMXMLでNativeMenuを書けるようにしてみましょう。
MenuTreeもMenuTreeItemもSparatorも同等に扱えるように、メニューになりえるものが持っている共通項、インターフェースを定義してみる。
IMenuTree.as
package me.rkome.da.mxml.menu
{
import flash.display.NativeMenuItem;
import mx.core.IMXMLObject;
public interface IMenuTree extends IMXMLObject
{
function getMenu():NativeMenuItem;
function get label():String;
}
}
labelは分かりますね。メニューに表示する文字列です。
getMenu()関数はNativeMenuItemを返します。もしNativeMenuItemを複数持つような場合はsubmenuプロパティを参照してもらうようにすれば
NativeMenuもNativeMenuItemもカバーできるかなーっていう適当仕様。
まあ、日本語よりもソースコードのが分かりやすいという病気の人の為に(僕の日本語が下手なだけ)どんどんコードを載せます。
MenuTree.as
package me.rkome.da.mxml.menu
{
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import mx.core.IMXMLObject;
[DefaultProperty("menuList")]
/**
* Nativeなメニューを定義する為のMXMLタグ
* このタグをルートにメニューを定義して、getNativeMenu()を呼び出すと
* NativeMenuが生成される
*
* @author DarkOmeme
*
*/
public class MenuTree implements IMenuTree
{
public function MenuTree()
{
}
public function initialized(document:Object, id:String):void
{
}
private var _label:String;
private var _menuList:Array;
[ArrayElementType("me.rkome.da.mxml.menu.IMenuTree")]
/**
* メニューアイテム(子要素)
* デフォルトのプロパティで、IMenuTreeが実装されたクラスの配列
* @return
*
*/
public function get menuList():Array
{
return _menuList;
}
public function set menuList(value:Array):void
{
_menuList = value;
}
/**
* メニューを生成するための関数
* IMenuTree で定義されている
* @return
*
*/
public function getMenu():NativeMenuItem
{
var menu:NativeMenu = new NativeMenu();
for each (var item:IMenuTree in _menuList)
{
var menuItem:NativeMenuItem = item.getMenu();
var subMenu:NativeMenu = menuItem.submenu;
if (item.getMenu().submenu != null )
{
menu.addSubmenu( subMenu, item.label );
}
else
{
menu.addItem( item.getMenu() );
}
}
var root:NativeMenuItem = new NativeMenuItem(_label);
root.submenu = menu;
return root;
}
/**
* 自分が子要素を持たない時に呼ばれるメニュー生成用関数
* @return
*
*/
public function getNativeMenu():NativeMenu
{
var item:NativeMenuItem = getMenu();
return item.submenu;
}
/**
* メニューのラベル
* ルート要素の場合、この値に意味は持たない
* (toolTipに表示されてもいいかもね)
* IMenuTree で定義されている
* @param value
*
*/
public function set label(value:String):void
{
_label = value;
}
public function get label():String
{
return _label;
}
}
}
デフォルトでタグの子要素がmenuListになるようになってます。
但し、mxmlファイルとして別ファイルで定義する場合、DefaultPropertyメタデータタグが利いてくれません。
ってことで、別のMXMLファイルで別途定義する場合はちゃんとmenuListというプロパティを指定してタグを書くようにしましょう。
(さっきのイメージは別のMXMLファイルで定義してある想定のMXMLソースでした)
MenuTreeItem.as
package me.rkome.da.mxml.menu
{
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.events.Event;
import flash.events.EventDispatcher;
import mx.core.IMXMLObject;
[Event(name="select", type="flash.events.Event")]
/**
* NativeMenuItemの役割を果たすクラス
*
* @author DarkOmeme
*
*/
public class MenuTreeItem extends EventDispatcher implements IMenuTree
{
public function MenuTreeItem()
{
}
public function initialized(document:Object, id:String):void
{
}
/**
* submenuのない、NativeMenuItemを返す
* クリックされた時のイベントをdispatchする為に、イベントハンドラを設定してます
* @return
*
*/
public function getMenu():NativeMenuItem
{
var menuItem:NativeMenuItem = new NativeMenuItem(_label);
menuItem.addEventListener(Event.SELECT, onSelect);
return menuItem;
}
private var _label:String;
public function set label(value:String):void
{
_label = value;
}
public function get label():String
{
return _label;
}
/**
* クリックされたら、そのままdispatchEventでイベント通知
* @param evt
*
*/
private function onSelect(evt:Event):void
{
dispatchEvent(evt);
}
}
}
クリックされた事を知る為に、selectイベントを通知するようにしてあります。
ここでもEventメタデータタグが大活躍。書いておくと、MXMLで書くときにFlex Builderで候補が出てくるようになります。
次回は絶対メタデータタグでなんかしたい。
Separator.as
package me.rkome.da.mxml.menu
{
import flash.display.NativeMenuItem;
public class Separator implements IMenuTree
{
public function Separator()
{
}
public function initialized(document:Object, id:String):void
{
}
public function getMenu():NativeMenuItem
{
return new NativeMenuItem("", true);
}
public function get label():String
{
return null;
}
}
}
ほとんど、説明不要ですが。
getMenu()関数ではセパレータを返します。
と言う感じで、これで一通りMXMLタグを作りました。
これでイメージ通り書けるのか?!
その結果は、ソースを落として実際に確認してみてください。
で、このポストの冒頭のスクリーンショットが結果です。
コードも一緒に収めたんで少し画像が大きいです。
ふいー、ようやく、一個記事がかけた。
気付けば9月
しかも、もう既に3日も経ってるっていう。
内心ポルナレフにもなりますよ。
8月こそは変態になろうと思ってたんですけどね…。
で、とりあえず最近、ゲームの仕事やら同人誌制作やらで忙しかったんで、自分が個人的にやるプログラミングが無かったんですが、9月に入って落ち着いたということで。
僕はブログを書くという習慣がどうやら無い。
厳密に言えば、『なんでも』ブログに書く習慣だね。
もっと言えば、僕はネット上で会話するとか、コミュニケーションするとか、知り合いになるとか、そういうやつができない。
まあ、それは中学の時に発症した病気を今も引きずったような事を不特定多数に向けて発信する恐怖からなんですが。
しかし、ほら、プログラマーがこの先生きのこるには、ブログとか書いてさ、自分が作ったすごいものを挨拶程度に公開したりさ、勉強会の一つも主催したりさ、そういうのをやるべきだ、という強迫観念もあるんですよ。
という事で、とりあえず、その第一歩としてなんでもブログを書くことを習慣づけたい。
今日書きたい事は、8月に予定していて出来なかった、変態になる計画かな。
まあ、最近、Flashプログラミング、敢えてFlexプログラミングと言わせて欲しい。
これを軸にして、今更色々やってみたい。
変態1年生ができる事を列挙してみます。
- カスタムスキン
- カスタムコンポーネント
- カスタムメタデータタグ
- 他言語連携
- 組み込み言語とVM on Flash
- モジュール
- Spark Project のライブラリ研究
- オレオレFlashPlayer
こんなところかな。 よーし、今月からパパ頑張っちゃうぞ。
