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 タグ: ,