2010/04/26

MXML as DSL

更新を止める前から、これだけは書いておこうと思っていたのだけれど、ずっと書き忘れてたこと。
MXMLのDSLとしてのプラットフォームの可能性。
ググっても今のところ"MXML as DSL"というそのものずばりなネタを書いている人は居ないので、先を越される前に書いておこうと思う。

まず結論から。

「MXMLはDSLであり、DSLを開発するプラットフォームとして有効」

従って、ここから言えることは「Flexアプリケーション開発はMXMLによるDSLを軸として開発するべき」 ということだ。
# 実は三段論法ちっくになっていて、間に「DSLによるアプリケーション開発は有効」という命題がある 。
# ここは敢えて無批判に認めるものとする。

まず、MXMLはDSLであるということ。
「そりゃそうでしょ、少なくともFlexの画面を作る(特定ドメイン)ための言語(本来はActionScriptによる構築をするところを、画面構造に沿ったXMLという形にした)なんだし」というのは、まず紛れも無いところ。
でも、実際にはValidatorやら画面表示要素でないものの定義も出来るわけで、必ずしも画面定義のためだけのものではない、ということが言える。

というわけで、翻ってMXMLをDSLとして捉えたときに、MXMLとはどういうものかを考えてみたい。

1.画面定義言語


これは元々のMXMLの目的とするところであるのは間違いない。

ここはおさらいと言うか、いうまでも無いところだけども、MXMLというのは画面の構成をXMLのタグ構造として表現しており、

  • XMLの1つのタグが1つの画面コンポーネントを表現

  • XML属性が画面コンポーネントのプロパティ設定を表現

  • XMLの親子関係がコンポーネントの親子関係を表現

  • モジュールはXMLの名前空間として扱う

  • むろん、良く使われるUI部品は提供済み

  • さらに、ActionScriptでいくらでもコンポーネントを作成可能


というあたりの言語の特徴があり、その上で、

  • FlexBuilderなどでのデザインのサポート

  • デザインしなくてもXMLのコードの補完がかなり効く (個人的には、慣れてしまえばデザインモードよりも生産性高いと思う)



という具合。

2. "オブジェクト構造"定義言語


"オブジェクト構造"というはほとんど造語だけど、一言で捉えたかったので、敢えて言ってみた。

まず、「"オブジェクト構造"定義言語」という語の意味全体としては、「オブジェクト構造」なるもの(なんとなくかっこ悪い言い方だけど、今はこういう言い方をしておこう。今回の文脈における定義は後述)があり、MXMLとはそれを定義するための言語、という意味合い。

「オブジェクト構造」の意味するところは何かというと、オブジェクト群(むろん、適切なプロパティが設定されている)とそれらオブジェクト間の関連(参照)、というもの。
ガベージコレクション技術の文脈で言われる「オブジェクトグラフ」と同じような意味合いだけど、さらにオブジェクトの持つプロパティも含まれている。

実際にオブジェクト指向でプログラミングをするときには、オブジェクトの作成があり、必要なプロパティを設定し、オブジェクト同士の関連を設定して利用する(関連の設定と利用は前後する場合がある)けど、この利用に際して出来上がっているオブジェクト間の関連、というあたりの意味合い。

もっと言えば、SpringとかSeasar2とかが持っているXMLと同じことが出来ると言いたいわけ。
# ただし、dicon(Seasar2のオブジェクト定義用XML)などではオブジェクトのライフサイクルもオブジェクトごとに設定できるけれども、MXMLではオブジェクトのライフサイクルは親となるMXMLに対応するオブジェクトと一緒になる、という違いはある。

これでやっていることは、

  1. オブジェクトのインスタンス化

  2. オブジェクトのプロパティの設定

  3. オブジェクト間の関連づけ(正確には、プロパティとしてオブジェクトの参照を設定することになるので、1と2の帰結としてこれが出来る)



の3つ。

特に非ビジュアルなコンポーネントを考えてみれば、そういう理解は間違っていないところかと。

ついでに言えば、ビジュアルコンポーネントの場合には、MXMLCがActionScriptを生成するときに、子タグのコンポーネントをaddChild()している、ということだけで、
ActionScriptとしてのFlashの構造を何らいじることなく、XMLで画面構造を作り上げることになっている。

(2.5 Dependency Injection)

「MXMLはオブジェクト構造を定義できる」ということが分かったけれども、これはMXML側から見た視点。
その裏側として、逆にMXMLに定義されるオブジェクトのほうから見てみると、これはDIをしてもらってることになると思う。

現に、UIComponentというAbstractなオブジェクトを各コンテナコンポーネントは子として持てるようになっているが、具体的にどのコンポーネントが入るかは親自体は知らない。つまり、依存性は無い。
MXMLでオブジェクト構造が定義されるときに初めて子コンポーネントが設定(注入される)される、と捉えられるわけで、構図としてはまさしくDIと言える。

# 個人的に、Prana FrameworkとかでFlexでもDIって言っているけど、個人的にはそんなコンテナは要らないと思う。
# 上にも書いたけど、MXMLではライフサイクルをオブジェクトの単位で変えられないというのはあるけれど、実際に使うかなぁ?というところ。
# DIでは、親よりも短いライフサイクルを持つように子を設定することは無いし、明示的にそういう状況であれば、実はIFactoryを実装したファクトリをMXMLで作って渡してあげれば実現出来る。

3. データバインディング


もう一つ、MXMLは実際にはActionScriptに変換されることで、ActionScriptで出来ることとMXMLとの違いで考えてみる。

それは、他のオブジェクトやオブジェクトのプロパティを含んだ式をデータバインディング式で参照すると、参照先のオブジェクトのプロパティが変化したときに、
自動的に参照元へ反映されるデータバインディングがMXMLで容易に実現できるということ。

しかもメタデータの記述によってデータバインディングが実現できるように構成されているので、Observerパターンのように変にObserveされるオブジェクトが「汚れる」こともない。
# 汚れる: Observerパターンでやりたい要件を実現するために、本来集中したいこと(画面コンポーネントなんだから、UIだよね)とは違う構造を入れなければならない、というのが汚くなる感じ。


まぁ、大体こんなものかね。

ここまでのまとめ


MXMLで出来ることを捉え直し、整理すると、以下のようになる。

MXMLはXMLベースのDSLであり、Flexアプリケーション内の"オブジェクト構造"を定義することができる

  1. オブジェクト定義の結果として画面定義言語の役割も強く持つ(オブジェクト構造の対象が画面コンポーネントである場合にFlashの構造(子のaddChild()のこと)にも設定される)

  2. DSLのボキャブラリであるところのタグは、ActionScriptによって開発することが出来る

  3. オブジェクト構造の構築はDIに他ならない

  4. 構築されたオブジェクト構造は、データバインディングもされる



DSLの記述環境(FlexBuilder)は、開発効率が高まるように構築されている

  1. 画面定義に関してはWYSIWYGなデザインツールでデザイン可能

  2. 非ビジュアルなコンポーネントに関しても、XMLエディタの補完機能がかなり効いている



というわけで、MXMLのDSLとしての可能性はけっこう高そうだ、という話。

DSLの語彙はどうするのか?


実際にMXMLをDSLとして利用していくにはどうするのか、というところが次に問題だが、
端的にはActionScriptでDSLの語彙となるコンポーネントを作っていくことがそれにあたる。
以前の議論で、新しいFormatterを作ってMXML上に定義してみたが、これは大したことはしていない。
MXMLのDSLにおいて、語彙というものは、ActionScriptのスキルだけで作成できるため、さほど敷居も高くないだろう。

DSLの問題点にどう対処するか?


一方で、WikipediaのDSLに関する記事には、
DSLの問題点も以下の通り記述されている。


・DSL自体を設計/実装/保守するコスト
・正しい適用範囲を探すこと
・ドメイン固有な部分と汎用プログラミング言語の構文とのバランス調整の難しさ
・ハードコードされたソフトウェアに比較して性能的に不利な可能性がある


これらについて検証してみる。

1. DSL自体を設計/実装/保守するコスト


言語は、語彙と文法によって構成される。(まぁ、直感的にも間違っていない言明だと思う)
MXMLの文法はXMLであり、DSLを構成するための最大の障壁と思われる文法設計とそのパーサ系の構築という点に関しては、まったく心配が要らない。
MXMLの語彙については、一定のルールに乗っ取って作成されたActionScriptプログラムであれば、それがすぐに語彙になる。
若干、語彙ごとに変わる文法(構文、MXMLで言えば、属性や子タグとして何を取るか、に相当するだろう)は、語彙の作成と共に設計する必要がある。
いずれにしろ、設計/実装/保守については、その基礎が既にあるため、1からDSLを構築するよりは大幅に小さいと思われる。

2. 正しい適用範囲を探すこと


「正しい」が何を意味するのか良く分からないが、適用範囲という意味であれば、さすがにFlexなので画面に関するものであろう。
得られる警句としては、ActionScriptでの記述との住み分けには若干注意が必要かもしれない、ということか。

3. ドメイン固有な部分と汎用プログラミング言語の構文とのバランス調整の難しさ


構文のうち、ベースとなる文法に関してはXMLであるため、全く問題ない。
語彙ごとに異なる構文、とはつまり、DSLで実現したいことに対応する語彙・構文をどう設計するか、ということだろう。
これもあまり悩まないのではないだろうか。

4. ハードコードされたソフトウェアに比較して性能的に不利な可能性がある


MXMLはコンパイル時にActionScriptが生成されるし、DSLの語彙はActionScriptによって構成し、インタプリタのような動作はしない。
従って、性能面で不利になることはないと思われる。
ただし、Flexは元々からしてデータバインディングを多用する性能劣化する可能性がある(経験則)ため、DSLの導入によってそれらを助長してしまう可能性はある。


結果として、Wikipediaの記事を元にすると、

  • ActionScriptでの記述との住み分けで悩まないようにするべきである

  • ActionScriptで作るコンポーネントが語彙であり、その語彙とそのための構文の設計がポイントである


という2点が、MXMLをDSLとして活用するというアイディアの注意点と言えるだろう。

いずれにせよ、大きな障害とは言えないところだろう。

2010/03/25

アーキテクトの素養としての数理

1年以上間をあけてしまったものの、ぼちぼち復活させていこうかと。
まず1個目のネタとしては、「アーキテクトの素養としての数理」。

とりあえずアーキテクトの素養として、数学、とくに代数学系が分かってたほうがいいって最近とみに思う。
まぁ、アーキテクトと言っても非常にその語の指す範囲も広く、ビジネスアーキテクトとかそういうのも入ってくるわけで、ここではどちらかというとITSSで言うところの「アプリケーション共通基盤」周りをやる人たちが対象かな。

まぁ、「数学がアーキテクトの素養」とか言うと、「んなこたぁない、経験でやってきてるが上手く行っている」って良く言われることも多い。
私としてはそれはそれで正しいと思うし、むしろアーキテクトとしての必須の素養は「揺るぎない自信」が何よりも勝る気もするので、アーキテクトとしてその主張をするのはそれは間違いないと思うところでもあり。

が、工学な大学院出ていてIT業界でないエンジニアを友人として持っている人間から見てみると、IT業界っておかしくて、機械系の勉強していた奴だけがメーカーに入ってエンジニアをやり、建築系の勉強していた奴だけが建設会社に入ってでかい建造物の設計をやる、というのがエンジニア系の業界の普通であって、(あくまで例えば、ですが)「経済の勉強してました、プログラミングなんてしたことありません」ってのが、(専門性を必要とされる)SEやプログラマをやる、なんてのはちょっと不思議に思えるところ。
まぁ、自分がそう思う、というだけであって、そこからIT業界けしからん論を展開するつもりは毛頭ないので、これはこれっきりでおしまい。

話を戻して、どういう背景で「数理はアーキテクトの素養」なんて思うのかといえば、諸々のアルゴリズムへの理解は言わずもがなとして、近頃出てきた様々なシステム構築パラダイム(言語、フレームワーク、設計方法論)を見ていくと、そこら辺の知識に依拠した背景を持つものも少なくないと思うから。

また、フレームワークを作るときにあたっては、その利用の側面を考えるのはもちろんの事、それらの環境についての理解をしておいた上で作ることがやっぱり必要になるわけで。
というか、そこら辺の素養が「どう作るべきか?」の判断の一つの基準になるような気もしていて、だからこそ、抑えられるところは抑えておきたいと思うところ。

とはいえ、そういった学問は広範であること、抽象度が高いために難解であることなどから、パッと応用に繋げられなそうなところが厄介。

でもまぁ、地道に行こう。

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な関数として切り出して、継承によってバリエーションを持たせておく、というのがいいかな。

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

前回の続き。

前回までで「動的に1000単位とかに切り替えられる仕組み」は完結。
ただ、「いちいちそれ用に作ってあげないといけない」という課題があった。
「それ用に」と言っているのは、たとえば同じような話をListコンポーネントのlabelFunctionにやってあげたりとか、あるいはLabelコンポーネントのtextプロパティにこれを使おうとしても、format関数のシグネチャがDataGrid専用になってるので使い回しが利かない、ということ。

というわけで、これを解決すべく、前回までのVariableCurrencyFormatterのformat関数のシグネチャを変えて対応できるようにしてみようかと。

このVariableCurrencyFormatterの使われそうなところを考えてみると、上記のとおりDataGridとListとLabelがあるので、それぞれどういう感じか考えてみる。

まず、DataGridは今までのとおり、

public function format(item:Object, column:DataGridColumn):String

のシグネチャでOK。

Listは、

public function format(item:Object):String

なんだけど、item はその行が表示対象とするオブジェト、まぁ大抵はいわゆるDTOってやつが来るんだけど、どのプロパティを表示するかを渡してあげないとまずい。
ListコンポーネントにはlabelFieldっていうのがあって、Listの場合はそれで指定することになるので、同じように外部から与えられるようにすればいいでしょう。

ていうか、listっていうListコンポーネントがあったとしたら、

<formatters:VariableCurrencyFormatter labelField="{list.labelField}" />

ってなっているのがわかりやすい。

Labelの場合、使い方としては、

<mx:Label text="{formatter.format(hoge)}" />

という感じだろうから、引数として1個のObjectを取ってStringを返すので十分。


シグネチャが違うのだけれど、ActionScriptだと引数にデフォルト値をしておけば関数のオーバーロードみたいなのができるので、これを使えばできそう。

というわけで、

[Bindable(event="formatChanged")]
public function format(item:Object, column:DataGridColumn = null):String
{
var dat:Object = item;

if (column != null && item.hasOwnProperty(column.dataField))
{
dat = item[column.dataField];
}
else if (labelField != null && item.hasOwnProperty(labelField))
{
dat = item[labelField];
}

var curFormatter:CurrencyFormatter = new CurrencyFormatter();
return curFormatter.format(Number(dat) / unit);
}

private var _labelField:String;

[Bindable]
public function get labelField():String { return _labelField; }
public function set labelField(value:String):void
{
if (_labelField != value)
{
_labelField = value;
dispatchEvent(new Event("formatChanged"));
}
}


と書いてみた。

これでDataGrid、List(ついでにlabelFieldが動的に変わっても対応可能)、Labelのそれぞれで同じ感じに使えるようにできた。

2008/12/19

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

前回の続き。


[Bindable]
public var format:Function;

とやった上で、format プロパティをセットし直すことで再フォーマットさせる、という風にして解決を図ったのだけれと、このformatプロパティをセットし直さないといけない、というのと外から変えられてしまうのがどうもなぁ、というところ。

とりあえず外から変えられないようにgetterを作ってみたけれど、コンパイラに怒られるはバインディングされないわでダメだった。


で、これを解決すべくググってみたりとかして調べてみた結果、実は読み取り専用プロパティでもデータバインディング元にできるということが判明。
ていうか、探せばFlex SDKのなかにたくさん溢れているようで、たとえばRadioButtonGroupのnumRadioButtonsプロパティとか。

ポイントとしては、


  1. getterにイベント名付きの[Bindable]タグを付けて宣言

    e.g.)

    [Bindable(event="hogeChange")]
    public function get hoge() { return _hoge; }


  2. バインドした先にプロパティの変更を通知したい場合、[Bindable]タグでつけたイベントをディスパッチする

    e.g.)

    private function setHoge(value:String)
    {
    _hoge = value;
    dispatchEvent(new Event("hogeChange"));
    }




で良いらしい。

しかも、もうちょっといろいろと試してみると、別にgetterでなくとも、関数に直接[Bindable(event="hogeChange")]を書いてもいいらしいということがわかった。
ちょっとわけがわからなくなりそうだけど、要は、関数に[Bindable]を付けておけば、データバインディングでその関数を使っているところに対して、好きなタイミングでその関数をコールさせることができるということか。
あるいは、好きなタイミングでデータバインディング式を評価させることが可能か。
う~ん、今回の用途にぴったり。

上記を踏まえて、以下のようにして作ってみた。


[Bindable]
public function get unit():int { return _unit; }
public function set unit(value:int):void
{
_unit = value;
dispatchEvent(new Event("formatChange"));
}

[Bindable(event="formatChange")]
public function format(item:Object, column:DataGridColumn):String
{
(略)
}


なんだ、逆にだいぶ簡単になっちゃったじゃん。

あれ、でもこのオブジェクト、別にEventDispatcherを継承してるわけじゃないんだけど。。。
Mix-Inってやつか?まぁ、あんまり気にしないでみよう・・・。

さて、これでだいぶいい感じになってきたけど、どうもしっくりきてないのが、Formatterの存在。
Flex標準で提供されている割には、いまいちlabelFunctionとかと相性が悪いんだよね。
結局、いちいち今まで作ってきたようなオブジェクトを作って、その中でFormatterを作って利用、みたいな流れなので、面倒。
最後はこれを何とかしてみたい。

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

前回の続き。

「動的に1000単位とかに切り替えられる仕組み」に挑戦中なわけですが、ここで「動的に」といっているのは、前回のVariableCurrencyFormatterの、「表示単位」を


<formatters:VariableCurrencyFormatter id="formatter" unit="{int(rbtnGrp.selectedValue)}"/>
<mx:RadioButtonGroup id="rbtnGrp" selectedValue="1000" />
<mx:HBox>
<mx:RadioButton group="{rbtnGrp}" label="(1)" value="1" />
<mx:RadioButton group="{rbtnGrp}" label="(千)" value="1000" selected="true" />
<mx:RadioButton group="{rbtnGrp}" label="(百万)" value="1000000" />
</mx:HBox>


という感じで変えてやりたい、ということです。
上記だと、ラジオボタンを選べばその単位になってほしい、というところ。

ここまでだと、確かに VariableCurrencyFormatter のunitプロパティは、データバインディングがうまくはたらいてラジオボタンの選択した値になってくれます。
これを、


<mx:DataGridColumn dataField="int1" labelFunction="{formatter.format}" textAlign="right"/>


と書いたlabelFunctionまで伝搬させてあげたいわけです。


というところで、関数も第一級のオブジェクトとして、つまりは変数として扱えるActionScriptの性質を利用してやればできるんじゃないかなぁと思ってみた。
変数の値が変わればいいので、強引に関数オブジェクトを入れ替えれてやればいいだろうと。

で、unitが変わったら関数オブジェクトを入れ替えてやるので、


private var _unit:int = UNIT_ONE;

[Bindable]
public function get unit():int { return _unit; }
public function set unit(value:int):void
{
_unit = value;
this.format = null;
this.format = formatFunction;

}

[Bindable]
public var format:Function = formatFunction;

private function formatFunction(item:Object, column:DataGridColumn):String
{
var dat:Object = item;

if (column != null && item.hasOwnProperty(column.dataField))
{
dat = item[column.dataField];
}

var curFormatter:CurrencyFormatter = new CurrencyFormatter();

return curFormatter.format(Number(dat) / unit);
}


とやってみた。。。


まぁ、少々強引だけれども、確かに意図したとおりに動くは動くみたい。
format プロパティに一度nullをセットしているのは、どうもデータバインディングは同じ参照が渡されてもバインディング先に通知されないから。


なんとも美しくないのと、format プロパティがpublicなので外からトンデモナイものをセットされないとも限らない、というのが怖いので、次のステップはこれを何とかしてみたい。

2008/12/17

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

前回の続き。

まずは、「動的に1000単位とかに切り替えられる仕組み」に挑戦。
labelFunctionで試してみる。
とはいえいきなりは難しいので、まずは1000単位にした金額フォーマットするlabelFunctionあたりから作成。

呼び出し側は

<formatters:VariableCurrencyFormatter id="formatter" unit="1000" />
<mx:DataGrid id="dg" dataProvider="{dp}">
<mx:columns>
<mx:DataGridColumn dataField="str1" />
<mx:DataGridColumn dataField="str2" />
<mx:DataGridColumn dataField="str3" />
<mx:DataGridColumn dataField="str4" />
<mx:DataGridColumn dataField="int1" labelFunction="{formatter.format}" textAlign="right"/>
</mx:columns>
</mx:DataGrid>

という感じで。

で、中で使ってるVariableCurrencyFormatterの、format()関数はこんな感じ。

public function format(item:Object, column:DataGridColumn):String
{
var dat:Object = item;

if (column != null && item.hasOwnProperty(column.dataField))
{
dat = item[column.dataField];
}

var curFormatter:CurrencyFormatter = new CurrencyFormatter();

return curFormatter.format(Number(dat) / unit);
}


さすがにこれは問題なく1000単位での表示になる。

問題は、このunitプロパティを変えてあげた時に、動的に表示が変わってほしいっていうこと。
変えるたびにdp.refresh()とかやってあげれば、DataGrid側にPropertyChangeイベントが飛んで実現できそうだけど、さすがにそれはスマートじゃないような。。。
やっぱりせっかく用意されているデータバインディングでうまくやりたいよね。