- 2006-02-04 (土)
- ActionScript
柔軟なプリンタスプール操作が可能な PrintJob クラス は、Flash を帳票印刷ツールとして使う為にはベストなソリューション... だと思うのですが、実際に業務ツールを作っていた際に発覚した欠点というか注意点について述べてみます。
※基本的な PrintJob クラスの使い方は以下のようになります:
var pj = new PrintJob(); //インスタンス作成 var arr = new Array( mc1, mc2, mc3 ); if(pj.start()){ //プリントダイアログ表示 for (var i=0; i<arr.length; i++){ pj.addPage(arr[i]); //プリンタスプールに追加 } pj.send(); //スプールをプリンタに送信 delete pj; }
欠点) 一連の処理はすべて同一フレーム (1 frame) 内で行わねばならない
Macromedia Flash TechNote - PrintJob cannot extend over multiple frames (2004/01/14)
FumioNonaka.com - PrintJobが複数のフレームをまたいで処理できない (2005/03/15)
Macromedia Flash Player 7以降でサポートされるPrintJobクラスは、インスタンス化とその使用を同一フレームで行う必要があります。ひとつのフレームで作成された PrintJobクラスのインスタンスは、その後のフレームでメソッドを呼出しても反応しません。
だそうです。この仕様で何が困るかと言うと、
- サーバXMLを動的にロードしつつ
- 各 XML の内容を元に MovieClip をステージ上に動的生成した上で
- その MovieClip をプリンタに印刷する
- リソース節約の為、印刷後は即座に MovieClip を消去する
といった一連の XML to 帳票印刷の処理を
- 印刷ダイアログ 1 回で済ませられなくなってしまう
- 1つのループ処理で回せなくなってしまう
といった点です。なぜなら、サーバXMLのロードを待つ = 複数フレームをまたがる からです。だらだら文章で説明するのも伝わりにくいので、サンプルソースで表してみました:
import it.sephiroth.XMLObject;
var loadCount = 0;
var arr = new Array('1.xml', '2.xml', '3.xml');
var pj:PrintJob = new PrintJob();
if(pj.start()){
for (i = 0; i<arr.length; i++) {
var xml = new XML();
xml.onLoad = function() {
var v = _root.getNextHighestDepth();
var m = _root.attachMovie('box','box', v);
var x = new XMLObject();
var d = x.parseXML(this, false);
m.txt.text = d.genko.name.data;
pj.addPage(_root);
_root.updateLoadCount();
m.removeMovieClip();
};
xml.load(arr[i]);
}
}
function updateLoadCount(){
loadCount++;
if(loadCount == arr.length){
trace('Sending spool to printer.');
pj.send(); delete pj;
}
}
[ サンプルソース ]
このようなアプローチの利点は
- 印刷ダイアログの表示が1回で済む
- 毎回 プリンタスプールに追加した後に MovieClip をメモリ上から消去するので、リソース節約になる為、ループ回数が増えてもパフォーマンスに影響を与えない
等がある為、ユーザビリティおよびスケーラビリティの点で優れているかと思われます。
しかしこのようなコードではプリンタ印刷が失敗してしまいます。ループ中で定義した xml.onLoad イベントハンドラが実際にコールされるタイミングでは、すでに PrintJob インスタンスは消滅してしまっているようです。未だに解決策は見つけられていません... (涙
代替案) すべての MovieClip を一旦ステージ上にロードする
前述アプローチの スケーラビリティ 的メリットは消えてしまいますが、替わりに以下のようなアプローチにする事で、なんとか機能の実現は出来ます:
import it.sephiroth.XMLObject;
var loadedMc = new Array();
var arr = new Array('1.xml', '2.xml', '3.xml');
for (i=0; i<arr.length; i++) {
var xml = new XML();
xml.onLoad = function() {
var v = _root.getNextHighestDepth();
var r = Math.round(Math.random()*100);
var m = _root.attachMovie('box', 'box' + r, v);
var x = new XMLObject();
var d = x.parseXML(this, false);
m.txt.text = d.genko.name.data;
_root.updateLoadCount(m);
};
xml.load(arr[i]);
}
function updateLoadCount(m) {
loadedMc.push(m);
if (loadedMc.length == arr.length) {
var pj = new PrintJob();
if (pj.start()) {
for (var i = 0; i<loadedMc.length; i++) {
loadedMc[i].swapDepths(this.getNextHighestDepth());
pj.addPage(this);
}
}
pj.send();
delete pj;
}
}
[ サンプルソース ]
でもこれだと、例えば対象の XML が1000件あるだとか、作成する MovieClip が重い (凝った作りになっている / リソース負担が高い) 場合などは、対応可能な件数が限定されてしまいます。んーいまいち。
この仕様はヘルプドキュメントに記載されていない
Flash MX 2004 に付属するヘルプには記載がありません。Macromedia の TechNote ページにこっそり公表されたままです (※Flash 8 のヘルプドキュメントは確認していないので判りませんが、少なくとも PrintJob クラスの挙動は変わっていませんでした) 。
Macromedia 製品は使っていて楽しいので、個人的には好意的なスタンスではあるものの、こういった事象に出くわすと、さすがに 「商用製品くささ」 を感じずにはいられません。基本的に、ドキュメントには不利な内容は書かない、みたいな? やっぱり OpenSource コミュニティ系の Technology (てか Perl ^^;) の方が安心して使えるなあ、なんて感じざるを得ない一件でした。