2018年 12月

JavaFXのPrintAPIを使ってはがきの印刷をしてみた。(文面)

JavaFX

このエントリーは じゃばえふえっくす Advent Calendar 2018 の12月22日分です。

JavaFX 8でPrintAPIが追加されて随分経ちます。

ネット上にもいろんな情報が共有されるようになりました。

そこではがき印刷をJavaFXのPrintAPIを使ってやってみようと思い立ちました。

宛名のほうは位置決めとか縦書きとかめんどくさいのでパスとします。(^_^;)

あっ・・・ めんどくさいじゃなくて解らないって言った方が近いもしれません。

縦書きなんかは一文字ずつ分解してFontMetricsを取得して構築するのかな?

さて、とりあえずはがき印刷のゴールは文面サイドだけにします。

シンプルにイメージファイルと賀詞を入れるだけのものとします。

まずJavaFXのPrintAPIについてざっくりと調べてみることにします。

 

javafx.graphicsモジュールにあるjavafx.printパッケージに九つのクラスと七つの列挙型があります。

クラス
JobSettings
PageLayout
PageRange
Paper
PaperSource
Printer
PrinterAttributes
PrinterJob
PrintResolution

列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides

ぱっと見、馴染みやすそうな構成になっています。

まずPrinterクラスがどんなものなのか見てみます。

Printerクラス

このクラスにはデフォルトプリンター、全てのプリンター、プリンター名を取得するメソッドなどが用意されています。

それとプリンターのデフォルトのページ・レイアウトを取得するメソッドや指定されたパラメータを使って新しいPageLayoutインスタンスを取得するものがあります。

あとPrinterAttributesクラスを返すメソッドが一つあります。

public PrinterAttributes getPrinterAttributes() 

プリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。

このメソッドを利用してプリンターの各種属性値を取得可能となります。

それでは次に上記のメソッドで返されるプリンターの属性値をカプセルかするPrinterAttributesクラスを見てみます。

PrinterAttributesクラス

このクラスは、ジョブ印刷機能に関連するプリンタ属性とその他の属性をカプセル化します。

デフォルトプリンターのデフォルトの属性値の取得、サポートされている属性値を取得することができます。

丁合い設定、印刷部数、用紙の向き、用紙サイズ、給紙方法、色設定、品質設定、印刷解像度、両面印刷設定、最大部数、ページ範囲など。

属性値にはあらかじめ列挙型が次の7種類用意されています。

列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides

それでは次に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の列挙型定数はありがたいです。

CANCELED     ジョブはアプリケーションによって取り消されました。
DONE   ジョブが印刷を開始し、その後endJob()を呼び出したところ、成功とレポートされました。
ERROR    ジョブの実行中にエラーが発生しました。
NOT_STARTED  新しいジョブのステータス。
PRINTING   ジョブは1ページ以上の印刷をリクエストし、まだ印刷を終了していません。

ジョブの一般的なライフサイクルは次のとおりです:

  • ジョブは、ステータスNOT_STARTEDで作成され、ダイアログなどでの構成中、このステータスにとどまります。
  • ジョブは、最初のページが印刷されると、PRINTING状態になります。
  • ジョブは、取り消されたり、エラーが発生したりすることなく正常に完了すると、DONE状態になります。 これでジョブが完了しました。
  • エラーが発生したERRORのジョブやCANCELEDのジョブも完了したと見なされます。

ジョブがライフサイクル中に前のステータスに戻ることはできず、現在のジョブ状態は実行可能な操作に影響を及ぼします。

たとえば、すでに印刷状態を過ぎて終了状態のいずれかになったジョブが再度印刷を開始することはできません。

PrinterJobクラス14個のメソッドはジョブの作成、取り消しステータス取得などのメソッドがあります。

あとジョブの構成オプションを取得するものや、ジョブに使われるプリンターを取得するもの、ジョブに使うプリンターの変更やネイティブな印刷ダイアログ、ページ設定ダイアログを表示するものなどがあります。

それではJobSettingsクラスを見ていきましょう。

JobSettingsクラス

JobSettingsクラスは、印刷ジョブの構成のほとんどをカプセル化します。

アプリケーションでJobSettingsインスタンスのセットを直接作成したり設定したりすることはありません(できません)。

印刷ジョブが作成された時点で、すでにそのジョブに1つインストールされています。

このクラスは印刷ジョブを構成するためのメソッドが30個あります。

ジョブの名前の設定、取得ページレイアウト、印刷品質、部数などだいたい必要なものがそろっています。

このJobSettingsクラスでジョブ構成を設定してもネイティブな印刷ダイアログを使用すれば設定を変更できてしまうようです。

次はPageLayoutクラスを見てみます。

PageLayoutクラス

PageLayoutは、コンテンツのレイアウトに必要な情報をカプセル化します。

8個のメソッドが用意されています。

それらを使ってページ・レイアウトのマージン(ポイント単位)、用紙、ページの印刷可能領域のデータ、向きを取得できます。

最後にPageRangeクラスを見ます。

PageRangeクラス

PageRangeは、印刷するジョブ印刷ストリーム・ページを選択または制限するために使用されます。

ページ番号は1から始まり、ユーザーの指定内容に対応します。

開始ページはゼロより大きく、終了ページ以下である必要があります。

開始と終了が同一の場合、範囲は単一ページを表します。

ジョブのページ数を超える値は、印刷時に単に無視されます。

各クラスの関連は下図のような感じですね。

b1

 

APIドキュメントを読んでいるだけでは面白くないのでとりあえず超シンプルなものを組んで印刷してみよう。

テキストエリアにデフォルトプリンターの名前、プリンター一覧の表示、そして印刷ジョブに使用するプリンターの設定、プリンターの属性の一部を表示させます。

UIはこれから作成予定のはがき印刷のものを流用しているのでちょっと変なことなってますがそれはきにしないでくださいませ。

これらの処理をトグルボタンが押されたら開始するように次のようにプログラムを組んでみました。

 

プログラムの実行結果は次のようになります。

1

まず、デフォルトプリンターを取得するためにPrinterクラスのstaticメソッドgetDefaultPrinter()を使います。

そしてデフォルトプリンターの名前を取得するためにPrinterクラスのString getName()メソッドを使いました。

次にプリンター一覧を表示させるために全てのプリンターを取得します。

PrinterクラスのstaticメソッドObservableSet<Printer> getAllPrinters()を使います。

プリンターの名前を取得するために先ほどと同様にPrinterクラスのString getName()メソッドを使います。

そしてプリンターの選択をしてみます。

現在のシステムに組み込まれているプリンターは下図のようになっています。

printer

デフォルトプリンターは”EPSON EP-806A Series”で間違いないのでEP-808A Series(ネットワーク)を指定してみました。

ところが予期せぬ結果となりました。

なんと、プログラムのプリンター一覧の表示結果では確かにあるのに指定できません。

T orElseGet(Supplier<? extends T> supplier)でデフォルトプリンターが返されます。

これの原因は調べてないので解りませんが別のPCでも同じでしたのでJavaFX側の問題かもしれません。

この解決策はOS側のプリンター設定で”EP-808A”のようにリネームすれば指定できました。

次にデフォルトプリンターの属性を取得しています。

var attributes = defaultPrinter.getPrinterAttributes();

PrinterクラスのPrinterAttributes getPrinterAttributes()メソッドでプリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。

そしてそれを使い各種属性値を取得表示させています。

PrinterAttributesクラスのSet<PageOrientation> getSupportedPageOrientations()でサポートされる向きを取得、

Set<PaperSource> getSupportedPaperSources()でサポートされる給紙方法(給紙ビンや給紙トレイ)を取得しています。

解りやすくフレンドリーなAPIがそろってます。

それではこれを印刷するコードを次のように追加しました。

注意: ステータスを確認するのにスレッドを少しスリープさせています。

 

 

PrinterJobクラスのstatic final PrinterJob createPrinterJob(Printer printer)メソッドでデフォルトプリンターを引数にとり新しいPrinterJobを作成します。

印刷に必要ではないけどJobStatus確認用にリスナーつけたり、ラベル、標準出力への表示などをしています。

PrinterJobクラスのsynchronized JobSettings getJobSettings()メソッドでジョブ構成オプション(部数、丁合いオプション、両面オプションなど)をカプセル化します。

初期値はプリンターの現在の設定に基づいています。

次にJobSettingsクラスのvoid setJobName(String name)メソッドでジョブの名前を設定します。

このプログラムではOSネイティブの印刷ダイアログを使用しました。

PrinterJobクラスのsynchronized boolean showPrintDialog(Window owner)を使います。

私はJavaFX 8がリリースされた当時このPrintAPIを早く試してみたい(いじってみたい)あまりにとんでもない間違いを犯していました。

当時、私は印刷ダイアログを呼び出して使うのに次のようにコードを書いていました。

job.showPrintDialog(anchorPane.getScene().getWindow());

これでも動くことは動くけどキャンセルしても印刷が実行されてしまいます。

twitterでこのことをつぶやいて何人かの方にリツイートされてしまい悪いことをしていまいました。

よくよく考えたらなんのためにboolean型の戻り値があるのかってことですね。

ネット上では印刷ダイアログを呼び出し使う方法がよくありますがほとんどキャンセル処理をしていません。

よって正しく使うためには印刷ダイアログとキャンセル処理は抱き合わせて使用することをお勧めします。

// 印刷ダイアログ
var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow());
// 印刷ダイアログでキャンセルした場合の処理
if (!nativeJob) {
    System.out.println(“Job has been canceled.”);
    job.cancelJob();
}

 

そして最後に印刷を開始するためにPrinterJobクラスのsynchronized boolean printPage(Node node)メソッドを使います。

引数のNodeが印刷対象となっています。

印刷に成功すればtrueが返されます。

印刷に成功したことを確認したらPrinterJobクラスのsynchronized boolean endJob()メソッドでジョブをプリンタのキューに正常にスプールされたらtrueを返します。

falseを返されたらプリンタージョブが失敗なのでそれなりの対処をします。

 

2

 

 

3

実際に印刷されたものがこのようになります。

a6

簡単ですね!

これで基本的なPrinter APIを扱うことが可能となったのではがき印刷のプログラムを組むことにします。

下図のようなGUIのプログラムを組んでみました。

21

Printボタンをクリックすると印刷のためのワーカースレッドが起動して印刷をはじめます。

いちおうキャンセルボタンもつけました。

Printer Infoトグルボタンはシステムのプリンターの属性を表示させています。

Open Imageボタンは画像を変更するためのファイルチューザーを出します。

賀詞を入れるためのテキストフィールドとジョブステータス表示用のラベルも用意しました。

印刷に関するコードを下記に示します。

 

コードが長くなっていますが基本的なことは先ほどのテキストエリアの文字列を印刷するものと何ら変わりはありません。

ただ、APIドキュメントでお勧めされているように印刷用のスレッドを別に起動させて印刷をしています。

今回のプログラムでは印刷ダイアログは使用せずJavaFXがはじめから用意してあるものを使用してみます。

まず、PrinterJobクラスのsynchronized JobSettings getJobSettings()を使いをカプセル化されたジョブ構成オプションを取得します。

JobSettingsクラスのsetterメソッドを使い設定します。

void setJobName(String string)メソッドで名前をつけます。

void setPaperSource(PaperSource ps)メソッドで給紙方法を設定します。

void setPageLayout(PageLayout pl)メソッドで使用するPageLayoutを設定します。

void setPrintColor(PrintColor pc)メソッドで印刷カラーを設定します。

void setPrintQuality(PrintQuality pq)メソッドで印刷品質を設定します。

final void setCopies(int i)メソッド印刷部数を設定します。

void setCollation(Collation cltn)メソッドで部単位で印刷設定をします。

カプセル化されているのでgetter、setterメソッドでアクセスできるのは楽ですね

ページレイアウトの設定はPrinterクラスのPageLayout createPageLayout​(Paper paper, PageOrientation orient, Printer.MarginType mType)メソッドを使い設定します。

印刷に関するものはこの程度ですね。

ではこのプログラムを実行してみましょう。

プリンターの印刷される音が鳴り止んではがきをとりにいって唖然としました。

こんなふうに印刷されていました。

a1

左上から少しだけ画像が切り取られて印刷されただけです。

これでは印刷はできたけどこれをもらった人はなんだろう?と首をかしげるに違いない!

プログラムを修正することにします。

何故このようになったのか?

はがきの印刷領域より画像が大きすぎたからと思われる。

左上の隅はJavaの2Dでは原点(0, 0)になるからそこから印刷可能な範囲だけ画像が印刷されたのだろう。

では残りの部分を印刷可能領域分だけ画像を位置変更して必要なだけ印刷すれば画像全体が印刷できるんじゃないか。

では早速コードを修正してみよう。

 

さぁ、これで理論上は問題ないはず!

さっそくプログラムを実行して印刷してみよう。

a2

印刷結果はこうなりました。

無事に印刷できたけど9枚のはがきが消費されました。

これを年賀状でだしたら受け取った人はパズルをすることになります。(^_^;)

わたしはなんと愚かな発想をしてしまったのだろう・・・

印刷可能領域に合わせるように画像をスケーリングしてしまえば良いだけのことじゃないですか!

せっかくだからこの複数枚印刷の機能を残したまま、縮小印刷機能を実装することにします。

使う用紙の印刷可能領域に画像をあわせるだけだから簡単ですね。

 

複数枚印刷のコードに印刷領域にあわせて印刷対象のノードをスケーリングして印刷するだけのコードを使いするだけです。

どちらの印刷方法にするかはチェックボックスを使って分岐させています。

印刷が終わったら元の大きさにもどしています。もちろん成功、失敗、キャンセルでも。

この自動スケーリングで印刷した結果が下の画像になります。

31

これでクリスマスカード、年賀状もシンプルなものならJavaFXで印刷できるようになった。

完全に自己満足の世界にひたっている。

いちおう動いてるのとキャンセルしたときのGifアニメを貼っておきます。

postcarf1

下のGifアニメではJobStatusがNOT_STARTEDのときにキャンセルボタンによるキャンセルをかけています。

あともう一つ、キャンセルボタンではタイミング合わせるのが難しかったのでJobStatusがPRINTINGに変更されたら強制的キャンセルをするようにしてあります。

JobStatusがPRINTINGの時にキャンセルかけるとCANCELED -> ERROR となるんですね。

なんでCANCELEDで終了しないのだろう?これは印刷リクエストがされたあとだからERRORで終了という解釈でいいんだろうか?

postcard2

最後にこのプログラムのコードを載せておきます。

 

 

 

 

 

Hatena タグ:

Jigsaw のお勉強 その4 JavaFX (Spinner がほんの少しだけ良くなった)

Java JavaFX

このエントリーは じゃばえふえっくす Advent Calendar 2018 の初日(12月4日)です。

未だにJigsawのお勉強をしています。(^_^;)

今回はJavaFXを使ってみます。

そんなJavaFXですが正式リリース前からさらにリリースされてからもいろいろありました。

現在はJavaにバンドルされていません。

正確にはいろいろなところからJDKが提供されているのでひょっとしたらJavaFXをバンドルしたJavaがあるのかもしれません。

今回はOpenJDKとOpenJFXを使います。

JavaFX 11でjavafx.scene.control.Spinner<T>クラスにほんの少し優しい変更が追加されたのでそれを試してみました。

矢印ボタンをマウスにて押し続けたときの入力時間がJavaFX 8の750ms固定だったのが自由に変更できるようになりました。

InitialDelayとRepeatDelayが設定可能となります。

デフォルトの設定時間はそれぞれ300ms、60ms となっています。

まず、Spinnerについて少しだけ復習しておきましょう。

SpinnerのAPIドキュメントに下記のように書かれています。

順序付けられたシーケンスからユーザーが数値またはオブジェクト値を選択できるようにする単一行のテキスト・フィールド。

通常、スピナーはシーケンスの要素間を移動するための小さな矢印ボタンのペアを提供します。

キーボードの上/下矢印キーでも要素間を自由に移動できます。

ユーザーがスピナーに直接(有効な)値を入力することもできます。

コンボ・ボックスも同様の機能を提供しますが、スピナーの方が好まれる場合があるのは、重要なデータを不明瞭化する可能性があるドロップ・ダウン・リストが不要であり、また、他の多くのJavaFX UIコントロールのようにObservableListデータ・モデルを使用せずに、wrappingなどの機能や、より単純な’無限’データ・モデルの仕様(SpinnerValueFactory)を使用できるためです。

Spinnerのシーケンス値はSpinnerValueFactoryで定義します。

value factoryはコンストラクタ引数として指定し、value factory propertyを使用して変更できます。

JavaFXには、次に示す一般的なタイプのSpinnerValueFactoryクラスが用意されています。

•SpinnerValueFactory.IntegerSpinnerValueFactory

•SpinnerValueFactory.DoubleSpinnerValueFactory

•SpinnerValueFactory.ListSpinnerValueFactory

Spinnerには、Spinnerの現在のvalueの表示および変更を行う、editorと呼ばれるTextField子コンポーネントがあります。

Spinnerはデフォルトで編集不可能ですが、editable propertyをtrueに設定すると、入力を受け入れることができます。

Spinnerエディタは、値ファクトリのvalue propertyに対する変更をリスニングすることにより、値ファクトリとの同期を保ちます。 ユーザーがeditorに表示された値を変更した場合、Spinnerのvalueとeditorの値が異なってしまう可能性があります。

モデルの値をeditorの値と同じにするには、ユーザーが[Enter]キーを使用して編集をコミットする必要があります。

APIドキュメントによると一般的なタイプのSpinnerValueFactoryクラスと使ってSpinnerインスタンスを作成できると書かれていました。

Spinner​(double min, double max, double initialValue) // SpinnerValueFactory.DoubleSpinnerValueFactory

Spinner​(double min, double max, double initialValue, double amountToStepBy) // SpinnerValueFactory.DoubleSpinnerValueFactory

Spinner​(int min, int max, int initialValue) // SpinnerValueFactory.IntegerSpinnerValueFactory

Spinner​(int min, int max, int initialValue, int amountToStepBy) // SpinnerValueFactory.IntegerSpinnerValueFactory

Spinner​(ObservableList<T> items) // SpinnerValueFactory.ListSpinnerValueFactory

Spinner​(SpinnerValueFactory<T> valueFactory) // 指定されたvalue factoryを設定する場合

Spinnerインスタンスを作成するのは容易にできることが確認できたので次はSpinnerの定数フィールド値を使ってスタイルの設定方法を調べてみます。

public static final String STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL

水平方向(左向きと右向き)の矢印がSpinnerの左側に配置されます。

public static final String STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL

垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置されます。

public static final String STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL

水平方向(左向きと右向き)の矢印がSpinnerの右側に配置されます。

public static final String STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL

Spinnerの左側に減分矢印、右側に増分矢印が配置されます。

public static final String STYLE_CLASS_SPLIT_ARROWS_VERTICAL

スピナーの幅全体にわたって上下に伸びた矢印が配置されます。

何も指定をしない場合はデフォルトの垂直方向(上向きと下向き)の矢印がSpinnerの右側に配置されます。

Spinnerインスタンスの作成、スタイルの設定方法が解ったので試してみます。

Spinnerのデザインをデフォルトの垂直方向(上向きと下向き)の矢印がSpinnerの右側に配置

1

Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL);

2

Spinner のデザインを垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL);

3

Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの右側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);

4

Spinnerのデザインを左側に減分矢印、右側に増分矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);

5

Spinnerのデザインをスピナーの幅全体にわたって上下に伸びた矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);

6

// 最小値 0、最大値 100、初期値 50、増分または減分するステップの量のSpinnerインスタンス作成
Spinner<Integer> spinner = new Spinner<>(0, 100, 50, 10);

javafx.​scene.​Nodeパッケージのpublic final ObservableList<String> getStyleClass()でノードを論理的にグループ化するために使用できる文字列識別子のリストを取得しava.​util.​Listパッケージのpublic abstract boolean add(E arg0)メソッドでSpinnerの定数フィールド値を追加して設定します。

Spinnerの増分、減分の矢印ボタンの配置デザインの設定はこれで可能となりました。

次に Spinnerに表示されている値のアライメントとカスタムフォントを設定してみます。

//  カスタムフォント、アライメントを設定
Font f = Font.loadFont(this.getClass().getResourceAsStream(“resources/AKUBIN1.34.ttf”), 28);
spinner.getEditor().setStyle(“-fx-text-fill: black; -fx-font: 18px ‘Akubin’; -fx-alignment: CENTER_RIGHT;”);

SpinnerExample

上の行でカスタムフォントを読み込んでその下の行でSpinnerにそのフォントを設定、テキストのサイズ、文字色、アライメントを設定しています。

そしてついでに

// Spinneのエディタをユーザー入力可能とする
spinner.setEditable(true);

としてユーザー入力可能としてます。

増分、減分のステップ量は変わらずユーザーによって入力された値からの増分値、減分値となります。

また、最大値、最小値を超えることはありません。

ここまでのプログラムのソースコードは次のようになります。

それではSpinnerにInitialDelayとRepeatDelayを設定するプログラムを組んでみます。

せっかくだからJDK 12 Early-Access Builds (build: 21)とJavaFX Early-Access Builds (openjfx-12-ea+2)を使ってみます。

さらに、モジュラjarを作ってモジュール化します。

そして、jlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成します。

NetBeans IDE 10.0-vc4を使ってお気楽に組もうとしたがこのバージョンには未対応なので古典的なツールを使いました。

ちなみにNetBeans IDE 10.0 Devでプロジェクトをビルドし、jlinkでカスタムJRE組み込みアプリケーションの作成はできました。

ただ、エラーバッジの嵐のエディタは心が痛みます。

それではお気に入りのエディタか心を痛めながらNetBeans IDE 10.0-vc4を使って下記ファイルを作成します。

 

 

 

 

プロジェクトの構成は下図のようにします。

11

カスタムフォントを使うのでお気に入りのフォントをご用意ください。

コンパイル時にはJDK 12の新機能を使えるように–release 12 –enable-preview VMオプションをつけます。

Java SE 11からJavaFXはバンドルされなくなったのでそれらライブラリをダウンロードしてコンパイル、実行時にモジュールパスを指定します。

JavaFXはこちらから入手できます。

https://gluonhq.com/products/javafx/

JavaFX Linux SDKとJavaFX Linux jmodsがあります。

JavaFX Linux SDKだけでいいのですがjlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成する場合はJavaFX Linux jmodsが必要となります。

-d オプションでbin/IncludeeJavaFXディレクトリにコンパイルによって作成されるクラスファイルが格納されるようにしています。

–module-pathオプションでJavaFX SDKのパスを指定します。

–add-modulesオプションでアプリケーションで必要とされるモジュールを指定します。

12

FXMLDocument.fxmlはコンパイルを実行しても出力ディレクトリbin/IncludeeJavaFXには移動、コピーされないので存在しません。

FXMLDocument.fxmlをコンパイルして作成されたクラスファイルのある場所にコピーします。

13

フォントを格納しているresourcesディレクトリもコピーします。

14

プロジェクトの構成は下図のようになります。

15

それでは実行してみましょう。

–enable-preview VMオプションを忘れずに。

–module-path、–add-modules、–moduleオプションも必要です。

16

ちゃんとコンパイルされて実行できました。

17

と、思いきや

18

何やら妙な警告がでました。(>_<。)

GTK libraries version 3を使うとだめそうなので-Djdk.gtk.verbose=true -Djdk.gtk.version=2とVM オプションをあたえます。

19

これってJavaFX 12のリリースまでには修正されるのかな?

20

それではこのプログラムをモジュラjar化してみます。

modsディレクトリを作成してそこにモジュラjarを格納します。

–module-versionでこのモジュールのバージョンを設定します。

実行時メインクラスの指定をしなくていいように–main-classオプションを使ってメインクラスを指定してアプリケーションエントリポイントを設定します。

21

モジュラjarがちゃんとできたか確認します。

22

問題無さそうなのでプログラムを走らせてみます。

–module-pathにはこのアプリケーションを格納しているmodsディレクトリとJavaFX SDKのパスを指定します。

–moduleオプションはメインクラスの指定を省力できるようにしたのでモジュール名だけで実行できます。

モジュラjar作成時にメインクラスの指定をしなかった場合は–module モジュール名/モジュールのメインクラス名とアプリケーションエントリポイント指定しなければいけません。

今回の場合だと、–module IncludedJavaFX/jp.yucchi.includedjavafx.IncludedJavaFXとします。

23

アプリケーションのモジュール化に成功し、ちゃんと動くことが確認できました。

24

最後にjlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成します。

includedjavafx-imageディレクトリを作成しそこに成果物を格納します。

–module-pathオプションでJDKとJavaFXのモジュールを指定します。JavaFX Linux SDKのパスじゃなくてJavaFX Linux jmodsのパスを指定することに注意してください。

–add-modulesオプションでこのアプリケーション自体を指定します。

–launcherオプションを次のように設定します。

–launcher <コマンド名>=<モジュール名><メインクラス>

–outputオプションで出力先を設定します。

25

jlinkによって作成されたランチャーファイルにVMオプションを記述します。

26

27

これを忘れると少し焦ります。(^_^;)

28

動きましたね。

JavaがインストールされてないPCでも動くことを確認しました。(Linux)

ただしWindowsでは動きません。

Windowsで動く配布用アプリはWindowsで作成しなくてはいけないようです。

Write once, run anywhere ってのは遠い過去の夢になってしまったのでしょうか・・・

Spinner

肝心のことを忘れていました。

上のGifアニメで増分、減分ボタンを押しっぱなしにしている時のSpinnerの更新時間に注目してください。

// タイマーの時間を設定
versionSpinner.setInitialDelay(Duration.millis(INTIAL_DELAY_TIME));
versionSpinner.setRepeatDelay(Duration.millis(REPEAT_DELAY_TIME));

setInitialDelay​(Duration value) メソッドで押し続けて最初に更新される時間を設定しています。

このプログラムでは解りやすくするために1500ミリ秒に設定しました。

そのまま押し続けた時の更新時間(リピート時間)は

setRepeatDelay​(Duration value) メソッドで 500ミリ秒と設定しました。

Java SE 8 u40 では750ミリ秒に固定されていて変更することができなかったので少しうれしいアップデートです。

Swingも悪くはないけど近い将来にはJavaの標準 GUI はJavaFXになってほしい。

 

Jigsawにもほんの少しだけ慣れてきたけど難しいですね。

モジュールの依存関係を調べるのがお手軽にできるツールがあればいいのだけど・・・

jdepsは万能ではないので落とし穴に落ちます。(>_<。)

今回のプログラムではFXMLを利用しています。

JavaFXでGUIの構築にはとても便利です。

で、それを使うためにmodule-info.javaにrequires javafx.fxml;とrequiresモジュールディレクティブ設定します。

ところがそれだけではjava.lang.reflect.InvocationTargetExceptionが投げられます。

FXMLを利用したJavaFXアプリケーションではjavafx.fxmlモジュール側からこのプログラムのFXMLのコントローラークラスに対してリフレクションによるアクセスがあります。

モジュールのカプセル化はリフレクションにも適用されるからです。

JDK 9より前はリフレクションを使えばパッケージ内の全てのクラスやクラスの全てのメンバーにアクセスできました。

つまりリフレクションは開発者の本意と関係なくカプセル化を破壊することが可能です。

Jigsawでは強力で繊細なカプセル化が提供されています。

デフォルトでは外部モジュールからモジュール内のクラスにアクセスすることはできません。

外部モジュールからアクセスできるのはexportsされたパッケージの公開クラス、公開メンバだけなのです。

非公開なメンバへのアクセスは外部モジュールから禁止されています。

こういった所でもモジュール化のメリットである依存性、公開範囲の制限が絡んでくるんですね。

なのでこのリフレクションによるjavafx.fxmlモジュール側からのアクセスを許可する必要があります。

それにはopens…toモジュール・ディレクティブを設定します。

module-info.javaにopens jp.yucchi.includedjavafx to javafx.fxml;とopensモジュールディレクティブを付け加えるだけです。

module-info.javaにこのモジュールディレクティブを加えることによって実行時にのみ特定のモジュールに対してパッケージへのアクセスを許可することが可能です。

つまり、特定のパッケージ内のpublic型(およびネストされているpublic型とprotected型)に対して実行時にのみアクセスできることを指定可能となります。

指定したパッケージのすべての型(およびその型のすべてのメンバ)にリフレクションを使ってアクセスすることもできます。

Jigsawの強力で繊細なカプセル化はJDK 9より前のものより開発者には喜ばれるはずですね。

そうなるとjdepsのような依存性を解決する強力なツールの必要性が求められます。

そのうち統合開発環境なんかに乗っかってくることを期待します。

 

Jigsaw のお勉強 その1

Jigsaw のお勉強 その2

Jigsaw のお勉強 その3

Hatena タグ: ,