Jigsaw のお勉強 その4 JavaFX (Spinner がほんの少しだけ良くなった)
このエントリーは じゃばえふえっくす 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の右側に配置
Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL);
Spinner のデザインを垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL);
Spinnerのデザインを水平方向(左向きと右向き)の矢印がSpinnerの右側に配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL);
Spinnerのデザインを左側に減分矢印、右側に増分矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
Spinnerのデザインをスピナーの幅全体にわたって上下に伸びた矢印が配置
spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL);
// 最小値 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;”);
上の行でカスタムフォントを読み込んでその下の行でSpinnerにそのフォントを設定、テキストのサイズ、文字色、アライメントを設定しています。
そしてついでに
// Spinneのエディタをユーザー入力可能とする
spinner.setEditable(true);
としてユーザー入力可能としてます。
増分、減分のステップ量は変わらずユーザーによって入力された値からの増分値、減分値となります。
また、最大値、最小値を超えることはありません。
ここまでのプログラムのソースコードは次のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
package jp.yucchi.spinnerexample; import javafx.application.Application; import static javafx.application.Application.launch; import javafx.scene.Scene; import javafx.scene.control.Spinner; import javafx.scene.layout.StackPane; import javafx.scene.text.Font; import javafx.stage.Stage; /** * * @author yucchi */ public class SpinnerExample extends Application { @Override public void start(Stage primaryStage) { // 最小値 0、最大値 100、初期値 50、増分または減分するステップの量 Spinner<Integer> spinner = new Spinner<>(0, 100, 50, 10); // // Spinner のデザインを水平方向(左向きと右向き)の矢印がSpinnerの左側に配置 // spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL); // // Spinner のデザインを垂直方向(上向きと下向き)の矢印がSpinnerの左側に配置 // spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL); // // Spinner のデザインを水平方向(左向きと右向き)の矢印がSpinnerの右側に配置 // spinner.getStyleClass().add(Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL); // // Spinner のデザインを左側に減分矢印、右側に増分矢印が配置 // spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL); // // Spinner のデザインをスピナーの幅全体にわたって上下に伸びた矢印が配置 // spinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL); // Spinneのエディタをユーザー入力可能とする spinner.setEditable(true); // カスタムフォント、アライメントを設定 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;"); // チェンジリスナーをつけて値を確認する spinner.valueProperty().addListener((ov, oldValue, newValue) -> { System.out.println("oldValue: " + oldValue); System.out.println("newValue: " + newValue); System.out.println(""); }); StackPane root = new StackPane(); root.getChildren().add(spinner); Scene scene = new Scene(root, 250, 100); primaryStage.setTitle("SpinnerExample"); primaryStage.setScene(scene); primaryStage.show(); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } } |
それでは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を使って下記ファイルを作成します。
1 2 3 4 5 6 7 8 9 |
module IncludedJavaFX { requires javafx.controls; requires java.desktop; requires javafx.fxml; exports jp.yucchi.includedjavafx; opens jp.yucchi.includedjavafx to javafx.fxml; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Spinner?> <?import javafx.scene.layout.AnchorPane?> <AnchorPane id="AnchorPane" fx:id="anchorPane" prefHeight="70.0" prefWidth="700.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.yucchi.includedjavafx.FXMLDocumentController"> <children> <Spinner id="VersionSpinner" fx:id="versionSpinner" layoutX="97.0" layoutY="22.0" prefHeight="50.0" prefWidth="230.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" /> <Label id="ResultLabel" fx:id="resultLabel" alignment="CENTER_RIGHT" layoutX="185.0" layoutY="27.0" prefHeight="50.0" prefWidth="383.0" text="" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="240.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" /> </children> </AnchorPane> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package jp.yucchi.includedjavafx; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; /** * * @author yucchi */ public class IncludedJavaFX extends Application { @Override public void start(Stage stage) throws Exception { Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml")); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("JavaFX is included?"); stage.show(); } public static void main(String[] args) { launch(args); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
package jp.yucchi.includedjavafx; import java.awt.Toolkit; import java.net.URL; import java.util.ResourceBundle; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; import javafx.scene.layout.AnchorPane; import javafx.scene.text.Font; import javafx.util.Duration; /** * * @author yucchi */ public class FXMLDocumentController implements Initializable { @FXML private ResourceBundle resources; @FXML private URL location; @FXML private AnchorPane anchorPane; @FXML private Spinner<String> versionSpinner; @FXML private Label resultLabel; @FXML void initialize() { assert anchorPane != null : "fx:id=\"anchorPane\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert versionSpinner != null : "fx:id=\"versionSpinner\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert resultLabel != null : "fx:id=\"resultLabel\" was not injected: check your FXML file 'FXMLDocument.fxml'."; } private static final double INTIAL_DELAY_TIME = 1_500; private static final double REPEAT_DELAY_TIME = 500; @Override public void initialize(URL url, ResourceBundle rb) { // システム情報 System.out.println(` /` + System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch") + `\ |` + "JDK " + System.getProperty("java.version") + ` | \` + "JavaFX " + System.getProperty("javafx.version") + ` / ----------------------------- \ \ \ __---__ _- /--______ __--( / \ )XXXXXXXXXXX\v. .-XXX( O O )XXXXXXXXXXXXXXX- /XXX( U ) XXXXXXX\ /XXXXX( )--_ XXXXXXXXXXX\ /XXXXX/ ( O ) XXXXXX \XXXXX\ XXXXX/ / XXXXXX \__ \XXXXX XXXXXX__/ XXXXXX \__----> ---___ XXX__/ XXXXXX \__ / \- --__/ ___/\ XXXXXX / ___--/= \-\ ___/ XXXXXX --- XXXXXX \-\/XXX\ XXXXXX /XXXXX \XXXXXXXXX \ /XXXXX/ \XXXXXX > _/XXXXX/ \XXXXX--__/ __-- XXXX/ -XXXXXXXX--------------- XXXXXX- \XXXXXXXXXXXXXXXXXXXXXXXXXX/ VXXXXXXXXXXXXXXXXXXV` ); // カスタムフォント、アライメントを設定 Font f = Font.loadFont(this.getClass().getResourceAsStream("resources/AKUBIN1.34.ttf"), 28); resultLabel.setStyle("-fx-text-fill: black; -fx-font: 28px 'Akubin'; -fx-alignment: CENTER_RIGHT;"); versionSpinner.getEditor().setStyle("-fx-text-fill: black; -fx-font: 28px 'Akubin'; -fx-alignment: CENTER_RIGHT;"); // Spinner の値リスト var versionNumber = FXCollections .observableArrayList("JDK 1.0", "JDK 1.1", "J2SE 1.2", "J2SE 1.3", "J2SE 1.4", "J2SE 5.0", "Java SE 6", "Java SE 7", "Java SE 8", "Java SE 9", "Java SE 10", "Java SE 11", "Java SE 12", "Java SE 13"); // Spinner の生成 var valueFactory = new SpinnerValueFactory.ListSpinnerValueFactory<String>(versionNumber); versionSpinner.setValueFactory(valueFactory); // タイマーの時間を設定 versionSpinner.setInitialDelay(Duration.millis(INTIAL_DELAY_TIME)); versionSpinner.setRepeatDelay(Duration.millis(REPEAT_DELAY_TIME)); // 値リスト変更に対応するためここで初期値を設定 includeFX(versionSpinner.getValue()); // Spinner にチェンジリスナーを使って値の変更を検出 versionSpinner.valueProperty().addListener((ov, oldValue, newValue) -> { // 解りやすいようにビープ音を鳴らす Toolkit.getDefaultToolkit().beep(); // 更新された値を引数として処理する includeFX(newValue); }); } // Label に結果を表示させる private void includeFX(String versionName) { // // JDK12 JEP 325: Switch Expressions // // (まだ String Switch に未対応 (ノД`)シクシク) <- enum, int なんかは対応していた。 // switch (versionName){ // case "Java SE 7" -> resultLabel.setText("JavaFX is included from Update 2."); // case "Java SE 8", "Java SE 9", "Java SE 10" -> resultLabel.setText("JavaFX is included."); // case "Java SE 13" -> resultLabel.setText("Not yet been released."); // default -> resultLabel.setText("JavaFX is not included."); // } switch (versionName) { case "Java SE 7": resultLabel.setText("JavaFX is included from Update 2."); break; case "Java SE 8": case "Java SE 9": case "Java SE 10": resultLabel.setText("JavaFX is included."); break; case "Java SE 13": resultLabel.setText("Not yet been released."); break; default: resultLabel.setText("JavaFX is not included."); } } } |
プロジェクトの構成は下図のようにします。
カスタムフォントを使うのでお気に入りのフォントをご用意ください。
コンパイル時には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オプションでアプリケーションで必要とされるモジュールを指定します。
FXMLDocument.fxmlはコンパイルを実行しても出力ディレクトリbin/IncludeeJavaFXには移動、コピーされないので存在しません。
FXMLDocument.fxmlをコンパイルして作成されたクラスファイルのある場所にコピーします。
フォントを格納しているresourcesディレクトリもコピーします。
プロジェクトの構成は下図のようになります。
それでは実行してみましょう。
–enable-preview VMオプションを忘れずに。
–module-path、–add-modules、–moduleオプションも必要です。
ちゃんとコンパイルされて実行できました。
と、思いきや
何やら妙な警告がでました。(>_<。)
GTK libraries version 3を使うとだめそうなので-Djdk.gtk.verbose=true -Djdk.gtk.version=2とVM オプションをあたえます。
これってJavaFX 12のリリースまでには修正されるのかな?
それではこのプログラムをモジュラjar化してみます。
modsディレクトリを作成してそこにモジュラjarを格納します。
–module-versionでこのモジュールのバージョンを設定します。
実行時メインクラスの指定をしなくていいように–main-classオプションを使ってメインクラスを指定してアプリケーションエントリポイントを設定します。
モジュラjarがちゃんとできたか確認します。
問題無さそうなのでプログラムを走らせてみます。
–module-pathにはこのアプリケーションを格納しているmodsディレクトリとJavaFX SDKのパスを指定します。
–moduleオプションはメインクラスの指定を省力できるようにしたのでモジュール名だけで実行できます。
モジュラjar作成時にメインクラスの指定をしなかった場合は–module モジュール名/モジュールのメインクラス名とアプリケーションエントリポイント指定しなければいけません。
今回の場合だと、–module IncludedJavaFX/jp.yucchi.includedjavafx.IncludedJavaFXとします。
アプリケーションのモジュール化に成功し、ちゃんと動くことが確認できました。
最後にjlinkで配布用にカスタムJREをバンドルしたアプリケーションを作成します。
includedjavafx-imageディレクトリを作成しそこに成果物を格納します。
–module-pathオプションでJDKとJavaFXのモジュールを指定します。JavaFX Linux SDKのパスじゃなくてJavaFX Linux jmodsのパスを指定することに注意してください。
–add-modulesオプションでこのアプリケーション自体を指定します。
–launcherオプションを次のように設定します。
–launcher <コマンド名>=<モジュール名><メインクラス>
–outputオプションで出力先を設定します。
jlinkによって作成されたランチャーファイルにVMオプションを記述します。
これを忘れると少し焦ります。(^_^;)
動きましたね。
JavaがインストールされてないPCでも動くことを確認しました。(Linux)
ただしWindowsでは動きません。
Windowsで動く配布用アプリはWindowsで作成しなくてはいけないようです。
Write once, run anywhere ってのは遠い過去の夢になってしまったのでしょうか・・・
肝心のことを忘れていました。
上の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のような依存性を解決する強力なツールの必要性が求められます。
そのうち統合開発環境なんかに乗っかってくることを期待します。
Trackback URL