2008/12/20

labelFunctionかItemRendererか。それとあとFormatter (6)

さらに前回の続き。

前回までで、「動的に表示単位とかを切り替える」を「DataGrid以外にもListやLabelでも使える」形にしてみた。
ただ、ここまでくると、「表示単位の切り替え」と「DataGrid以外にもListやLabelでも使える」というのが1つに収まってるのがなんとも具合が悪く見えてくる。
「表示単位の切り替え」以外にも要件が出てきたときに、結局同じようなものを作らないといけないというのがまずい。

というところまで考えると、「表示単位の切り替え」はそれこそFormatterにやらせればいいので、要はFormatterをlabelFunction用に合わせてくれるようなオブジェクトができればいいんでしょと。

ということで、上の2つを分離してみることにした。

利用イメージとしては、

<formatters:LabelFunctionAdaptor id="formatter" labelField="{list.labelField}">
<formatters:formatter>
<formatters:VariableCurrencyFormatter unit="{int(rbtnGrp.selectedValue)}" />
</formatters:formatter>
</formatters:LabelFunctionAdaptor>


という感じでformatterを宣言したい。
LabelFunctionAdaptorというのが、DataGridだろうがListだろうがLabelだろうがフォーマットを受け付けてくれるオブジェクトで、しかもフォーマッティングを変えたいときに動的に変える機能つき。
VariableCurrencyFormatterは、今までのとは違って、Flex標準のCurrencyFormatterを、表示単位が変えられうように継承して拡張したもの。
このFormatterを別のものに差し替えてあげれば、いろいろと他のものにも使えるようになるはず、というイメージ。

# 考えてみれば、こういう差し替えってのは、見方によってはまさにDIなんだなぁと思ってみた。
# MXMLってただの画面定義DSLっていうだけでなくて、オブジェクト定義に使うとしてもいいんじゃないかと。
# しかも、FlexBuilderとかを使ってるとMXMLは補完が効きまくるのでかなり生産性が高い。
# そのうえXMLだから、ActionScriptでチマチマ書くよりも少なくとも文法という意味では容易に覚えられるし。
# DSLとしてのMXMLというところを考えてみると、面白いことができるのかもしれない。
# まぁ、今はこれでとどめておいて後で書くことにする。


と、ここまで利用イメージを固めてみたところで、ひとつ困ったことが。
VariableCurrencyFormatterのオブジェクトがlabelFunctionのデータバインディング式に乗ってこないので表示単位の変更が伝わらずに、このままだと「動的に変更する」要件が実現できなくなっちゃうじゃん、と。

う~ん、諦めて継承とかにしたほうがいいんだろうか。MXMLの記述量も増えちゃったし。

まぁ、とりあえず「できるだけFlex標準のFormatterを使う」方法で進めていくとすると、
VariableCurrencyFormatterのプロパティが変わった時に、自力で再度LabelFunctionAdaptorのformat()が呼ばれるようにすれば実現できるということがわかる。

自力で再度LabelFunctionAdaptorのformat()が呼ばれるようにする、というのは、今までのイベント名付きの[Bindable]を使えばOK。
で、問題は、「VariableCurrencyFormatterのプロパティが変わった時に」をどう実現するか。

いままでよく分かってなかったのだけれど、それにはどうやらChangeWatcherを使えばよいらしい。
使い方としては、

ChangeWatcher.watch([監視したいオブジェクト], [監視したいプロパティ], [プロパティの変更時に呼び出されるイベントハンドラ関数]);

で良いとのこと。

ここのプロパティの指定は、実は"hoge.piyo.hogera"みたいに、子へと辿っていけるものらしい。
何となく、データバインディング式で書いてる内容がこれでうまくパースされてやられてる、ということの裏側な気がする・・・。

ということで、

private var _formatter:Formatter;

[Bindable]
public function get formatter():Formatter { return _formatter; }
public function set formatter(value:Formatter):void
{
if (_formatter != value)
{
_formatter = value;
triggerFormatChange();

if (value)
{
formatterChangeWatcher = ChangeWatcher.watch(value, "format", triggerFormatChange);
}
}
}

private function triggerFormatChange(event:Event = null):void
{
this.dispatchEvent(new Event("formatChanged"));
}



とやってみた。

これで、VariableCurrencyFormatterのformatプロパティが変わった時に、LabelFunctionAdaptorが変わったとチェーンされて、最終的にlabelFunctionの再実行が行われるはず。
実際にはVariableCurrencyFormatterのformatはプロパティじゃなくてメソッドなので、例のイベント名付きの[Bindable]を使って、プロパティの変更時にformatが変更されたかのようにイベントを飛ばせばOK。

ただ、う~ん、これだと、実はFlex標準のFormatterってformat関数に[Bindable]が付いていたりしないので、なんだかんだいって結局そのまま使えるわけじゃないって話になるのね。

しかも、ChangeWatcherって、内部で[監視対象オブジェクト] -> ChangeWatcherインスタンス、という参照が生じるらしいので、下手するとメモリリークになるっぽい。
cf.) SDK-14891

対象のChangeWatcherインスタンスのreset()メソッドにnullを引数として与えれば解消できるみたいだけど、でも、必ず先にLabelFunctionAdaptor側が要らなくなるような場合、やっぱりリークしちゃうじゃん。

というわけで、Flex標準のFormatterをうまく組み込んでlabelFunction用に簡単にできるようにする、という目論見はやっぱりやめたほうが無難っぽい。

これに汎用性を持たせたければ、Formatterを呼ぶ最後の部分だけabstractな関数として切り出して、継承によってバリエーションを持たせておく、というのがいいかな。