›JavaFXJavaFXJavaFXのPrintAPIを使ってはがきの印刷をしてみた。8でPrintAPIが追加されて随分経ちます。ネット上にもいろんな情報が共有されるようになりました。そこではがき印刷をJavaFXのPrintAPIを使ってやってみようと思い立ちました。宛名のほうは位置決めとか縦書きとかめんどくさいのでパスとします。めんどくさいじゃなくて解らないって言った方が近いもしれません。縦書きなんかは一文字ずつ分解してFontMetricsを取得して構築するのかな?さて、とりあえずはがき印刷のゴールは文面サイドだけにします。シンプルにイメージファイルと賀詞を入れるだけのものとします。JavaFXのPrintAPIについてざっくりと調べてみることにします。javafx.graphicsモジュールにあるjavafx.printパッケージに九つのクラスと七つの列挙型があります。クラスJobSettingsPageLayoutPageRangePaperPaperSourcePrinterPrinterAttributesPrinterJobPrintResolution列挙型CollationPageOrientationPrintColorPrinter.MarginTypePrinterJob.JobStatusPrintQualityPrintSidesぱっと見、馴染みやすそうな構成になっています。Printerクラスがどんなものなのか見てみます。Printerクラスこのクラスにはデフォルトプリンター、全てのプリンター、プリンター名を取得するメソッドなどが用意されています。それとプリンターのデフォルトのページ・レイアウトを取得するメソッドや指定されたパラメータを使って新しいPageLayoutインスタンスを取得するものがあります。あとPrinterAttributesクラスを返すメソッドが一つあります。プリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。このメソッドを利用してプリンターの各種属性値を取得可能となります。それでは次に上記のメソッドで返されるプリンターの属性値をカプセルかするPrinterAttributesクラスを見てみます。PrinterAttributesクラスこのクラスは、ジョブ印刷機能に関連するプリンタ属性とその他の属性をカプセル化します。デフォルトプリンターのデフォルトの属性値の取得、サポートされている属性値を取得することができます。属性値にはあらかじめ列挙型が次の7種類用意されています。列挙型CollationPageOrientationPrintColorPrinter.MarginTypePrinterJob.JobStatusPrintQualityPrintSidesそれでは次にPaperクラスを見てみます。Paperクラスこのクラスはプリンター用紙のサイズをカプセル化するクラスになります。用紙のサイズを指定するのに20個のstaticフィールド値があらかじめ用意されています。使用するプリンターでそれがサポートされているかの確認は必要でしょう。あと、プリンター用紙のサイズ取得のメソッドと用紙の名前を取得するメソッドが用意されています。サイズはポイント(1/72インチ)単位となっています。DTPやってる人はご存じかもしれませんが一般人にはあまり馴染みのない単位です。次はPaperSourceクラスを見ていきましょう。PaperSourceクラスプリンター用紙に使用される給紙トレイ、給紙方法を扱うクラスになります。9個のstaticフィールド値があらかじめ用意されています。あと給紙方法の名前を返すメソッドが一つ用意されています。次はPrintResolutionクラスを見ます。PrintResolutionクラスプリンターのサポートされている解像度(送り方向と前後送り方向)を1インチ当たりのドット数(DPI)で表すクラスです。用紙の前後送り方向の解像度(dpi単位)、用紙の送り方向の解像度(dpi単位)を返すメソッドが二つ用意されています。次はPrinterJobクラスです。PrinterJobクラスPrinterJobは、JavaFXシーングラフ印刷のルートとなります。これには、次のものが含まれます。プリンタの検出ジョブの作成サポートされるプリンタの機能に基づいたジョブの構成ページの設定ノード階層のページへのレンダリング。APIドキュメントには非常にシンプルに印刷がされるように記述されています。注意事項としては印刷と並行してノードを更新するのは避けてください。これはそうだろうなと誰もが思うだろうけど、ちょっと解りづらいというか・・・結局どうなの?って記述が下記のようにされてます。FXアプリケーション・スレッドで印刷を実行する際の必要条件はありません。ノードの印刷準備やジョブの呼出しはどのスレッドでも行うことができます。アプリケーションUIの応答性に影響が及ばないように、FXアプリケーション・スレッドで実行される処理の量を最小限に抑えるのが一般的に望ましいと言えます。したがって、印刷は新しいスレッドで実行し、実装内部のスケジューリングによって、FXスレッドで実行する必要のあるすべてのタスクがそのスレッドで実行されるようにすることをお薦めします。FXアプリケーション・スレッドで印刷をできなくもないけど印刷用の新しいスレッドを用意して実行してくれってことでいいのかな?今回のはがき印刷のプログラムではお勧めの印刷用の新しいスレッドを用意させていただくとしよう。このPrinterJobクラスには14個のメソッドと印刷ジョブのステータスをレポートする際に使われるネストされたstaticクラス列挙型PrinterJob.JobStatusがあります。印刷ジョブのステータスを確認するにあたり、この列挙型PrinterJob.JobStatusの列挙型定数はありがたいです。ジョブはアプリケーションによって取り消されました。ジョブが印刷を開始し、その後endJob()を呼び出したところ、成功とレポートされました。ジョブの実行中にエラーが発生しました。新しいジョブのステータス。ジョブは1ページ以上の印刷をリクエストし、まだ印刷を終了していません。ジョブの一般的なライフサイクルは次のとおりです:ジョブは、ステータスNOT_STARTEDで作成され、ダイアログなどでの構成中、このステータスにとどまります。ジョブは、最初のページが印刷されると、PRINTING状態になります。ジョブは、取り消されたり、エラーが発生したりすることなく正常に完了すると、DONE状態になります。これでジョブが完了しました。エラーが発生したERRORのジョブやCANCELEDのジョブも完了したと見なされます。ジョブがライフサイクル中に前のステータスに戻ることはできず、現在のジョブ状態は実行可能な操作に影響を及ぼします。印刷状態を過ぎて終了状態のいずれかになったジョブが再度印刷を開始することはできません。PrinterJobクラス14個のメソッドはジョブの作成、取り消しステータス取得などのメソッドがあります。あとジョブの構成オプションを取得するものや、ジョブに使われるプリンターを取得するもの、ジョブに使うプリンターの変更やネイティブな印刷ダイアログ、ページ設定ダイアログを表示するものなどがあります。それではJobSettingsクラスを見ていきましょう。JobSettingsクラスJobSettingsクラスは、印刷ジョブの構成のほとんどをカプセル化します。アプリケーションでJobSettingsインスタンスのセットを直接作成したり設定したりすることはありません(できません)。印刷ジョブが作成された時点で、すでにそのジョブに1つインストールされています。このクラスは印刷ジョブを構成するためのメソッドが30個あります。ジョブの名前の設定、取得ページレイアウト、印刷品質、部数などだいたい必要なものがそろっています。このJobSettingsクラスでジョブ構成を設定してもネイティブな印刷ダイアログを使用すれば設定を変更できてしまうようです。次はPageLayoutクラスを見てみます。PageLayoutクラスPageLayoutは、コンテンツのレイアウトに必要な情報をカプセル化します。8個のメソッドが用意されています。それらを使ってページ・レイアウトのマージン(ポイント単位)、用紙、ページの印刷可能領域のデータ、向きを取得できます。最後にPageRangeクラスを見ます。PageRangeクラスPageRangeは、印刷するジョブ印刷ストリーム・ページを選択または制限するために使用されます。ページ番号は1から始まり、ユーザーの指定内容に対応します。開始ページはゼロより大きく、終了ページ以下である必要があります。開始と終了が同一の場合、範囲は単一ページを表します。ジョブのページ数を超える値は、印刷時に単に無視されます。各クラスの関連は下図のような感じですね。APIドキュメントを読んでいるだけでは面白くないのでとりあえず超シンプルなものを組んで印刷してみよう。テキストエリアにデフォルトプリンターの名前、プリンター一覧の表示、そして印刷ジョブに使用するプリンターの設定、プリンターの属性の一部を表示させます。UIはこれから作成予定のはがき印刷のものを流用しているのでちょっと変なことなってますがそれはきにしないでくださいませ。これらの処理をトグルボタンが押されたら開始するように次のようにプログラムを組んでみました。プリンター一覧に存在するが指定できないのでデフォルトプリンターが返される。OSのプリンターの設定で"EP-808A"のようにリネームすれば指定できた。"指定されたプリンターが見つからなかったのでデフォルトプリンターを使います。プリンター一覧に存在するが指定できないのでデフォルトプリンターが返される。OSのプリンターの設定で"EP-808A"のようにリネームすれば指定できた。"指定されたプリンターが見つからなかったのでデフォルトプリンターを使います。Info");}}プログラムの実行結果は次のようになります。デフォルトプリンターを取得するためにPrinterクラスのstaticメソッドgetDefaultPrinter()を使います。getName()メソッドを使いました。次にプリンター一覧を表示させるために全てのプリンターを取得します。getAllPrinters()を使います。getName()メソッドを使います。プリンターの選択をしてみます。現在のシステムに組み込まれているプリンターは下図のようになっています。Series(ネットワーク)を指定してみました。ところが予期せぬ結果となりました。なんと、プログラムのプリンター一覧の表示結果では確かにあるのに指定できません。supplier)でデフォルトプリンターが返されます。これの原因は調べてないので解りませんが別のPCでも同じでしたのでJavaFX側の問題かもしれません。この解決策はOS側のプリンター設定で”EP-808A”のようにリネームすれば指定できました。次にデフォルトプリンターの属性を取得しています。getPrinterAttributes()メソッドでプリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。それを使い各種属性値を取得表示させています。getSupportedPaperSources()でサポートされる給紙方法(給紙ビンや給紙トレイ)を取得しています。解りやすくフレンドリーなAPIがそろってます。それではこれを印刷するコードを次のように追加しました。ステータスを確認するのにスレッドを少しスリープさせています。これだとキャンセルしても印刷開始してしまう。これだとキャンセルしても印刷開始してしまう。printer)メソッドでデフォルトプリンターを引数にとり新しいPrinterJobを作成します。印刷に必要ではないけどJobStatus確認用にリスナーつけたり、ラベル、標準出力への表示などをしています。getJobSettings()メソッドでジョブ構成オプション(部数、丁合いオプション、両面オプションなど)をカプセル化します。初期値はプリンターの現在の設定に基づいています。name)メソッドでジョブの名前を設定します。このプログラムではOSネイティブの印刷ダイアログを使用しました。owner)を使います。8がリリースされた当時このPrintAPIを早く試してみたい(いじってみたい)あまりにとんでもない間違いを犯していました。当時、私は印刷ダイアログを呼び出して使うのに次のようにコードを書いていました。job.showPrintDialog(anchorPane.getScene().getWindow());これでも動くことは動くけどキャンセルしても印刷が実行されてしまいます。twitterでこのことをつぶやいて何人かの方にリツイートされてしまい悪いことをしていまいました。考えたらなんのためにboolean型の戻り値があるのかってことですね。ネット上では印刷ダイアログを呼び出し使う方法がよくありますがほとんどキャンセル処理をしていません。よって正しく使うためには印刷ダイアログとキャンセル処理は抱き合わせて使用することをお勧めします。node)メソッドを使います。引数のNodeが印刷対象となっています。印刷に成功すればtrueが返されます。endJob()メソッドでジョブをプリンタのキューに正常にスプールされたらtrueを返します。falseを返されたらプリンタージョブが失敗なのでそれなりの対処をします。実際に印刷されたものがこのようになります。APIを扱うことが可能となったのではがき印刷のプログラムを組むことにします。下図のようなGUIのプログラムを組んでみました。Printボタンをクリックすると印刷のためのワーカースレッドが起動して印刷をはじめます。いちおうキャンセルボタンもつけました。Infoトグルボタンはシステムのプリンターの属性を表示させています。Imageボタンは画像を変更するためのファイルチューザーを出します。賀詞を入れるためのテキストフィールドとジョブステータス表示用のラベルも用意しました。印刷に関するコードを下記に示します。