OpenJFX時代のJavaFXの始め方 - NetBeans + Maven編

今回から主要なIDEJavaFXの環境を整えて、サンプルを作るというところまでを解説していきます。主要なIDEというのは以下の3種類です。

はじめがNetBeansなのは、単に私が使い慣れているからです。

どれもJDKIDEがインストールされているという前提で説明します。Liberica JDKやZuleFXなどのJavaFXを含んだJDKを使う場合でも、JDKに含まれているJavaFXは使わずに一般のライブラリとして話を進めます。

なお、OpenJFX SDKは必要ありません!

MavenやGradleを使えば、Mavenのセントラルレポジトリから自動的にJavaFXをダウンロードするので、OpenJFX SDKをインストールする必要はないのです。

JavaFXのアプリケーションを作るには、それが一番手っ取り早いはずです。

NetBeans + Maven

NetBeansはもともとAntを使ってビルドや実行を行っていましたが、NetBeans 10.0からはAntよりもMavenやGradleの方に重きがかかっているようです。というのも、新規プロジェクトを作る場合、Mavenプロジェクトが先頭、Gradleが次、3番目になってやっとAntが登場しているからです。

今回使用したのはOracle JDK 12.0.2と、NetBeans 11.0です。JDKOracle OpenJDK以外のものでもかまいませんし、Java 12でなくてもJava 11でもOKです。

では、まずMavenJavaFXを始めてみましょう。

プロジェクト作成

はじめに行うのがプロジェクトの作成です。メニューバーの[File]-[New Project...]でプロジェクト作成のダイアログが表示されます。

f:id:skrb:20190713092525p:plain

もしくは左側ペインの[Projects]タブで右クリックして、ポップアップの[New Project...]でもOKです。

すると、プロジェクト作成が表示されます。

f:id:skrb:20190713093007j:plain

ここでは、Mavenを使うので、左側の[Categories]の[Java with Maven]を選択します。すると、右側にはプロジェクトの種類が表示されるので、この中から[Project from Archetype]を選びます。

注意
ここで[JavaFX Application]を選んではダメです!!

この[JavaFX Application]で作成するプロジェクトはJavaFX 8用なので、Java 11以降では使えません!!


MavenArchetypeは、プロジェクトのひな型のようなものです。MavenのCentral RepositryにはいろいろなArchetypeが登録されていますが、JavaFXのArachetypeもあるのです。

[Project from Archetype]を選択したら、[Next]で次に進みましょう。

次画面では、Archetypeを選択します。とはいうものの、Archetypeはいっぱいあるので、上部にある[Search]フィールドでArchetypeのフィルタリングをします。

JavaFXArchetypeはGroup IDがorg.openjfxなので、[Search]フィールドに"org.openjfx"、もしくは"openjfx"などを入力してみます。"javafx"だとJavaFX 8以前のArchetypeも表示されてしまうので、"openjfx"の方がよいです。

下の図では、"openjfx"でフィルタリングしたところです。

f:id:skrb:20190713094827p:plain

すると、[Known Archetypes]に、以下の2つのArchetypeが表示されます。

1つ目のjavafx-archetype-fxmlはFXMLを使用したJavaFXアプリケーション、2つ目のjavafx-archetype-simpleはFXMLを使用しないJavaFXアプリケーションになります。

まずは、FXMLを使用しないjavafx-archetype-simpleでやってみましょう。javafx-archetype-simpleを選択して、[Next]で先に進みます。

すると、プロジェクト名などを入力する画面に遷移します。

f:id:skrb:20190713095751p:plain

プロジェクト名やプロジェクトの場所などは、適当につけておいてください。ここではsimplefxdemoというプロジェクトにしました。

ダイアログの下の方の[Additional Creation Properties]でライブラリとプラグインのバージョンを設定することができます。2019年7月段階でのデフォルトではJavaFXが11.0.2、JavaFX Maven Pluginが0.0.1になっています。これらのバージョンは、プロジェクト作成してからでも変更できるので、今はこのままにしておきましょう。

設定ができたら、[Finish]でプロジェクトが作成されます。

プロジェクトの構成は以下のようになりました。

f:id:skrb:20190713100851p:plain

module-info.javaが存在していることから分かるように、このアプリケーションはモジュールになっています。でも、モジュールにしたくないのであれば、module-info.javaを削除すればOKです。

そして、Mavenのビルドファイルであるpom.xmlは次のようになっています。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.javainthebox</groupId>
    <artifactId>simplefxdemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11.0.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.1</version>
                <configuration>
                    <mainClass>net.javainthebox.simplefxdemo.App</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

使用するライブラリ(依存性のあるライブラリ)はに記載されています。上のpom.xmlにはjavafx-controlsしか記述していませんが、プロジェクトのDependenciesにはjavafx-baseなども含まれています。
これは、javafx-controlsが依存しているライブラリも、プロジェクトのDependenciesに表示されるからです。

Mavenプラグインに列挙されます。ここでは、maven-compiler-pluginとjavafx-maven-pluginが使用されています。maven-compiler-pluginはコンパイルのために使用し、javafx-maven-pluginはJavaFXアプリケーションのコンパイル、実行、パッケージングなどを行います。

なお、2019年7月での最新は、JavaFXが12.0.1、JavaFX Maven Pluginが0.0.2です。必要に応じて、pom.xmlのバージョン記述をあげておいた方がいいと思います。

プロジェクトの実行

このプロジェクトはそのままでも実行できるので、実行してみましょう。

とはいうものの、NetBeansのメニューバーの[Run]-[Run Project]では、すぐには実行できません。単に[Run Project]ではJavaFXのお約束のモジュール指定などを行わないからです。

それらを行うためにJavaFX Maven Pluginがあるのです。JavaFX Maven Pluginでアプリケーションを実行するには、Mavenのゴールとしてjavafx:runを使用します。

NetBeansMavenのゴールを直接指定するには、プロジェクトを右クリックして表示されたポップアップメニューから、[Run Maven]-[Goals...]を選択します。

f:id:skrb:20190713102806p:plain

すると、ゴールを指定するダイアログが表示されるので、[Goals]の欄に"javafx:run”を入力します。

f:id:skrb:20190713102959p:plain

[OK]でゴールを実行します。すると、下の図のようなウィンドウが表示されるはずです。

f:id:skrb:20190713103156p:plain

もう、JavaFXのアプリケーションが実行できてしまいました。とはいうものの、毎回ゴールを指定するのもめんどうです。

そこで、NetBeansですぐに実行できるように設定してみましょう。

MavenのゴールとNetBeansのアクションを結びつけるという設定を行うのですが、それにはプロジェクトのプロパティから行います。

プロジェクトを右クリックして表示されたポップアップメニューの一番下に[Properties]があるので、それを選択します。

f:id:skrb:20190713202114p:plain

すると、設定用のダイアログが表示されます。

ダイアログの左側の[Categories]から[Actions]を選択すると、右側にアクションの一覧が表示されます。この中で、[Run project]を選択します。

f:id:skrb:20190713202402p:plain

[Execute Goals]に表記されている内容が、[Run project]に割り当てられているゴールです。

デフォルトではExec Maven Pluginのexecが使われていることが分かります。この部分をJavaFX Maven Pluginのjavafx:runに変更します。また、[Set Properties]に記載されているプロパティは必要ないので、削除します。

f:id:skrb:20190713202920p:plain

[OK]で設定完了です。

では、プロジェクトの右クリックで、ポップアップメニューの[Run]を実行してみましょう。もしくは、メニューバーの[Run] - [Run Project]、あるいはファンクションキーのF6でも実行できます。

正しく設定ができていれば、上述したウィンドウが表示されるはずです。

この設定を行うと、プロジェクトのpom.xmlと同じ場所にnbactions.xmlというファイルが作成されます。このファイルに、ここで行った設定が記述されます。

<?xml version="1.0" encoding="UTF-8"?>
<actions>
        <action>
            <actionName>run</actionName>
            <packagings>
                <packaging>jar</packaging>
            </packagings>
            <goals>
                <goal>javafx:run</goal>
            </goals>
        </action>
    </actions>

なお、javafx:runだけではビルドが必須ではないので、毎回ビルドをし直すのであればclean javafx:runのように記述してビルドを強制させるようにします。

後は、このプログラムをベースに拡張を行っていけばOKです。ただ、GUI部品が増えてくるとコードがとても見にくくなってきます。そこで、使うのが次節で扱うFXMLです。

FXMLプロジェクト作成

FXMLはXMLGUIの画面構成を記述するファイルです。FXMLはペアとなるJavaファイルが存在し、通常コントローラークラスと呼ばれます。コントローラークラスにはGUI部品で発生したイベントの処理を記述します。

FXMLには画面構成を記述しますが、それと一緒にGUI部品をコントローラクラスで扱うためのIDや、イベント発生時にコールするコントローラクラスのメソッドも記述します。

では、FXMLを使用したプロジェクトを作成していきましょう。

FXMLを使ったプロジェクトも、Mavenarchetypeで作っていきます。

プロジェクト作成の手順はarchetypeを選択するまでは、先ほどと同じです。先ほどはarchetypejavafx-archetype-simpleを選択しましたが、FXMLを使う場合はjavafx-archetype-fxmlを選択します。

f:id:skrb:20190718152457p:plain

プロジェクト名の入力画面は先ほどの例と同じですね。ここでは、simplefxmldemoというプロジェクト名にしました。

プロジェクトの構成は以下のようになっています。

f:id:skrb:20190718155532p:plain

先ほどのサンプルと同じようにAppクラスがメインのクラスです。その他に2つのクラスが存在していますが、クラス名にControllerが含まれることから分かるように、コントローラークラスになります。

FXMLはリソースの方に分類されています。ここでは2つのFXMLファイルが存在しています。

primary.fxmlファイルとPrimaryControllerクラス、secondary.fxmlファイルとSecondaryControllerクラスがペアになっています。

このサンプルもそのままで実行できるので、実行してみましょう。実行のための設定は、先ほどのサンプルと同じです。

f:id:skrb:20190719143422p:plain

実行するとボタンが1つのステージが表示されます。このボタンをクリックすると、2番目の画面に切り替わります。2番目の画面のボタンをクリックすると、再び1番目の画面に遷移します。

Scene Builder

このアプリケーションを改造していく、もしくは新たにFXMLを作成するような場合、FXMLをグラフィカルに編集できるツールがほしいところです。この用途に使えるツールとしてScene Builderがあります。

Scene Builderはオープンソースで開発されていますが、Gluonがバイナリを配布しているので、それを使うのが手っ取り早いです。なお、Oracleで配布しているScene Builderは古いバージョンで、JavaFX 2の頃のものです。JavaFX 8を使う場合でも、Gluonからダウンロードできるバイナリを使用してください。

gluonhq.com

Scene BuilderはインストーラもしくはRPMなどのパッケージが配布されているので、それにしたがってインストールしてみてください。

ただ、Windows版は日本語環境だと文字化けを起こすので、今のところ英語で使うしかないです。

Scene Builderをインストールしたディレクトリの直下にあるappディレクトリにSceneBuilder.cfgというファイルがあります。このファイルを開くと[JVMOptions]という項目があるはずです。

この項目には--add-opensが記載されていますが、これに加えて-Duser.language=enを記述します。

[JVMOptions]
-Duser.language=en
--add-opens
javafx.fxml/javafx.fxml=ALL-UNNAMED

これで、Scene Builderは英語表記になります。

このバグ、日本語のプロパティファイルが壊れているせいで、エラーの報告もされているんですけど、直らないんですよね...

次に、NetBeansからScene Builderを呼び出せるように設定します。

まず、メニューバーの[Tools]-[Options]を選択します。

f:id:skrb:20190721212125p:plain

すると、オプション設定のダイアログが開きます。

f:id:skrb:20190721212350p:plain

上段のカテゴリーの中から[Java]を選択し、タブの中から[JavaFX]を選択します。はじめて[JavaFX]を選択すると、JavaFXをアクティベートしてから上記のように表示されます。デフォルトでは[Scene Builder Home]の欄は空白になっているので、SceneBuilderをインストールしたディレクトリを指定します。

その下の[Save All Modified Files Before Running Scene Builder]はチェックしてもしなくてもOKです。チェックしておいた方が間違いは少ないとは思います。

これで、NetBeansでFXMLをダブルクリック、もしくは右クリックしてポップアップメニューの[Open]を選択すると、Scene Builderが起動し、指定したFXMLファイルをオープンします。

たとえば、primary.fxmlをダブルクリックすると、以下のような画面になります。もし、Scene Builderが起動しない場合は、NetBeansを再起動してみてください。

f:id:skrb:20190722152833p:plain

ちなみに、FXMLをNetBeansで開きたいときは、FXMLを右クリックして、ポップアップメニューの[Edit]を選択します。

これで、グラフィカルにFXMLを変数する用意ができました。あとは、このサンプルをベースに改良するか、新たに作成していくかして、アプリケーションを作っていけるはずです。

まとめ

NetBeansMavenを使ったJavaFXの開発環境を整える方法を紹介しました。簡単にまとめると

  • JavaFXArchetypeを使えば、JavaFXアプリケーションのひな型は簡単に作成できる
  • コンパイル、実行などはNetBeansから行うが、多少の設定は必要
  • FXMLの編集にはScene Builderを使用し、NetBeansから使えるようにしておくと便利

今回は、環境構築とアプリケーションのひな型を作るだけでした。次回は、今回の続きで、実際にひな型を拡張してアプリケーションを作る説明をしていきます。

追記

このエントリーを公開した前日にNetBeans 11.1がリリースされました!

JavaFXに関しては大きな変更はないのですが、サンプルの中にOpenJFXが追加されました。このサンプルを使用すれば、上記のMavenのプロジェクトをarchetypeを使わずに作成することができます。

また、nbactions.xmlも含まれているので、javafx:runをゴールに指定する必要もありません。

OpenJFXのプロジェクトを作成するには、[New Project...]で表示されるダイアログの中から、カテゴリーを[Samples]-[OpenJFX]にします。

f:id:skrb:20190729063434p:plain

[Samples]の中には[JavaFX]もありますが、こちらはJavaFX 8とJava SE 8の組み合わせなので、Java 11以降では使用できません。

[HelloFXWithMaven]か[HelloFXMLWithMaven]を選択して、[Next]で、プロジェクト名などの入力画面に切り替わります。デフォルトだとプロジェクト名がHelloFXWithMavenもしくはHelloFXMLWithMavenになっているので、必要におうじて変更してください。

後は、Mavenarchetypeを使って作成したプロジェクトと同じです。前述したようにnbactions.xmlはすでに設定してあるので、実行もすぐにできるはずです!

JavaFX 8からJavaFX 11以降へのマイグレーション

前回のエントリーでJavaFX 8はやめようという話をしました。

skrb.hatenablog.com

JavaFX 8からJavaFX 11以降のバージョンへの移行は、ほとんどの場合、ビルド/実行方法を変えるだけです。今回はサンプルを使って、それを説明していきます。

今回使用するサンプルは、散布図と一次回帰を表示するというアプリケーションです。表示するデータはCSVで、その読み込みにはOpenCSVを使用しています。また、一次回帰の計算にはApache Commons Mathを使用しました。

f:id:skrb:20190630212447p:plain

ソースはこちら。

github.com

JavaFX 8で作成したJARをJavaFX 12で動かす

GitHubのscatterplotterのブランチjavafx8でJavaFX 8用のソースになります。

今回はこれをLiberica JDK 8u212を使ってビルドしました。ビルドにはMavenを使用し、前述したようにOpenCSVとApache Commons Mathを使っているので、依存性に記述しています。

では実行してみましょう。ここでは、Windowsで実行しています。

もちろん、Mavenを使っても実行できるのですが、ここではJava 8とJava 11の違いが分かるように、コマンドラインで実行してみました。

Mavenなので、targetディレクトリにJARファイルが作られます。また、クラスパスで指定できるように、依存ライブラリをtarget/dependencyにコピーしておきます。

C:\scatterplotter> mvn dependency:copy-dependencies

実行は次のように行います。

C:\scatterplotter>java -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

2カラムのCSVで、先頭行が軸の名前になっているファイルを読み込むと上のような散布図と一次回帰線が表示されるはずです。

では、これをJavaFX 12.0.1とJava 11.0.3 (ここでは、AdoptOpenJDKのLTSを使用しました)を使って実行してみます。

JavaFX 12.0.1を使うには、以下のURLの"Latest Release"からJavaFX SDKをダウンロードして、適当な場所に展開します。ここではC:\Program Files\Javaに展開しました。

gluonhq.com

では、実行してみます。

C:\scatterplotter>"C:\Program Files\AdoptOpenJDK\jdk-11.0.3.7-hotspot\bin\java" --module-path "C:\Program Files\Java\javafx-sdk-12.0.1\lib" --add-modules javafx.fxml,javafx.controls -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

Java 8の場合と異なるのは、--module-pathオプションと--add-modulesオプションです。--module-pathオプションには先ほど展開したJavaFX SDKのlibディレクトリを指定します。--add-modulesオプションにはJavaFXで使用するモジュールを追加します。

ここでは、javafx.controlsとjavafx.fxmlを指定しています。JavaFXのモジュールとしては、javafx.baseとjavafx.graphicsを使用していますが、javafx.controlsもこの2つのモジュールに依存しているので、--add-modulesオプションに指定する必要はありません。

逆にいうと、このオプションだけを指定すれば、ソースを変更することなくJavaFX 12でも実行することができます。

でも、JavaFX SDKを展開しておかなくてはいけないなど、事前の準備が必要です。そこで、JavaFX 11以降のバージョン用にビルド・実行環境を作成します。

JavaFX 12でのビルド・実行環境を作る

といっても、Mavenのpom.xmlを変更するだけです。

まず、プロジェクトの依存性にJavaFXを加えます。OpenCSVとCommons Mathだけだったところに、javafx-fxmlとjavafx-controlsを加えます。

    <dependencies>
        <dependency>
            <groupId>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>4.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
            <version>3.6.1</version>
        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-fxml</artifactId>
+            <version>12.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>12.0.1</version>
+        </dependency>
    </dependencies>

行頭に+がついている部分が追加した部部分です。

ここでは、javafx-fxmlとjavafx-controlsだけですが、必要に応じて他のモジュールを加えます。たとえば、ムービーやサウンドを使う場合はjavafx-media、WebViewを使いたいのであればjavafx-webを加えます。

前述したように、javafx-controlsはjavafx-graphicsとjavafx-baseに依存しているので、自動的に取り込まれます。

これだけでもいいのですが、MavenJavaFX Pluginがあるので、これをExec Maven Pluginの代わりに使ってみましょう。

        <plugins>
            <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>exec-maven-plugin</artifactId>
-                <version>1.6.0</version>
-                <executions>
-                    <execution>
-                        <goals>
-                            <goal>java</goal>
-                        </goals>
-                    </execution>
-                </executions>
+                <groupId>org.openjfx</groupId>
+                <artifactId>javafx-maven-plugin</artifactId>
+                <version>0.0.2</version>
                <configuration>
                    <mainClass>net.javainthebox.scatterplotter.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>

行頭の-が削除した行で、+が追加した行です。

JavaFX Pluginは以下のURLに情報があります。

github.com

これで、JavaFX SDKをダウンロードして、展開する必要がなくなります。

Exec Maven Pluginではないので、実行にはjavafx:runを使います。

C:\scatterplotter>mvn clean javafx:run

簡単ですね!

もちろん、コマンドラインからでも実行できます。Java 8で実行するときに説明したように依存するライブラリをコピーしてから実行するようにします。

C:\scatterplotter>mvn dependency:copy-dependencies
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< net.javainthebox:scatterplotter >-------------------
[INFO] Building scatterplotter 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:copy-dependencies (default-cli) @ scatterplotter ---
          <<省略>>
C:\scatterplotter>java --module-path target\dependency --add-modules javafx.controls,javafx.fxml -cp target\scatterplotter-1.0-SNAPSHOT.jar;target\dependency\* net.javainthebox.scatterplotter.Main

Java 8の時とは異なり、dependencyディレクトリにJavaFXのJARファイルもコピーされます。このため、モジュールパスとクラスパスで同じディレクトリを指定しているのですが、これで問題なく動きます。

JREをバンドルしたパッケージを作成

最後にアプリケーション用にカスタマイズしたJREを作成して、アプリケーションパッケージを作ってみましょう。

先ほどまではJavaFX SDKを使っていたのですが、ここで使用するのはJavaFX jmodsです。jmodsもSDKと同じページからダウンロードできます。

SDKとjmodsの違いは、SDKにはJARファイルが含められていますが、jmodsの方はJMODファイルだということです。

JMODファイルなんて聞いたことがないという人も多いと思いますが、JMODファイルはDLLとかシェアードライブラリなどのネイティブライブラリを一緒にパッケージングできるファイル形式です。たとえば、JDKjava.baseなどのモジュールもJMODファイルで提供されています。JDKディレクトリの下にあるjmodsファイルに配置されています。

JavaFXはプラットフォームごとにネイティブのライブラリを含むので、JMODファイルの方がやりやすいのです。でも、JavaFXのJMODファイルは通常実行時には使えません。jlinkでカスタムJREを作成するときに使うことができます。

では、jlinkでカスタムJREを作成してみましょう。

ここでは、ダウンロードしたJavaFX jmodsのZIPファイルをC:\scatterplotterディレクトリに展開しました。

jlinkは使用するモジュールだけをJREにしますが、scatterplotterで直接使用しているのはjava.baseモジュール、javafx.controlsモジュール、javafx.fxmlモジュールの3つです。この中で、java.baseモジュールはJavaのアプリケーションであれば必ず使用するので、デフォルトで追加されます。

また、OpenCSVもApache Commons Mathもモジュールではないので、JREには含めません。

では、jlinkでカスタムJREを作成しましょう。

C:\scatterplotter>jlink --module-path javafx-jmods-12.0.1 --add-modules javafx.controls,javafx.fxml --compress=2 --output scatterplotter

モジュールパス(--module-path)には展開したJavaFX jmodsのディレクトリを指定します。--add-modulesは前述したようにjavafx.controlsとjavafx.fxmlをしてします。--compressオプションはJREの圧縮率を示すオプションで0, 1, 2のいずれかを指定します。0は圧縮なし、2が圧縮率が高いことになります。

最後の--outputオプションでJREの出力先ディレクトリを指定します。ここでは、scatterplotterを指定しているので、scatterplotterディレクトリにJREが作成されます。

作成したJREで以下のように実行すると、JREに組み込まれたモジュールが分かります。

C:\scatterplotter>scatterplotter\bin\java --list-modules
java.base@12.0.1
java.datatransfer@12.0.1
java.desktop@12.0.1
java.prefs@12.0.1
java.scripting@12.0.1
java.xml@12.0.1
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
jdk.unsupported@12.0.1

scatterplotterで直接使用するのは前述した3種類のモジュールですが、javafx.controlsとjavafx.fxmlが依存しているモジュールも芋づる式にインクルードされます。そのため、java.desktopモジュールなどがJREに含まれています。

後は、target\dependencyディレクトリにあるJavaFX以外のJARファイル群と、scatterplotterのJARファイルをscatterplotterディレクトリにコピーします。ここではどちらもscatterplotterディレクトリ直下にdependencyディレクトリを作成しして、そこにコピーしました。

最後に起動用のバッチファイルを用意しましょう。ここではscatterplotter.batとしました。内容は以下の通り。

@echo off
bin\java -cp dependency\* net.javainthebox.scatterplotter.Main

ここではWindows版のJREを作ったのでバッチファイルですが、Linuxであればシェルスクリプトファイルを作ります。

作成したjreディレクトリはこんな構成になってます。

C:\scatterplotter\scatterplotter>dir
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 6868-701E です

 C:\scatterplotter\scatterplotter のディレクトリ

2019/07/05  21:37    <DIR>          .
2019/07/05  21:37    <DIR>          ..
2019/07/05  21:17    <DIR>          bin
2019/07/05  21:17    <DIR>          conf
2019/07/05  21:31    <DIR>          dependency
2019/07/05  21:17    <DIR>          include
2019/07/05  21:17    <DIR>          legal
2019/07/05  21:17    <DIR>          lib
2019/07/05  21:17               182 release
2019/07/05  21:33                75 scatterplotter.bat
               2 個のファイル                 257 バイト
               8 個のディレクトリ  868,741,357,568 バイトの空き領域

C:\scatterplotter\scatterplotter>dir dependency
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は 6868-701E です

 C:\scatterplotter\scatterplotter\dependency のディレクトリ

2019/07/05  21:31    <DIR>          .
2019/07/05  21:31    <DIR>          ..
2019/07/04  20:10           246,174 commons-beanutils-1.9.3.jar
2019/07/04  20:10           588,337 commons-collections-3.2.2.jar
2019/07/04  20:10           752,798 commons-collections4-4.2.jar
2019/07/04  20:10           501,879 commons-lang3-3.8.1.jar
2019/07/04  20:10            61,829 commons-logging-1.2.jar
2019/07/04  20:10         2,213,560 commons-math3-3.6.1.jar
2019/07/04  20:10           182,954 commons-text-1.3.jar
2019/07/04  20:10           170,348 opencsv-4.6.jar
2019/07/04  20:18             7,929 scatterplotter-1.0-SNAPSHOT.jar
               9 個のファイル           4,725,808 バイト
               2 個のディレクトリ  868,741,292,032 バイトの空き領域

C:\scatterplotter\scatterplotter>

後は、JREを作成したscatterplotterディレクトリをZIPかなにかでまとめてしまえば、配布用パッケージの完成です。

ちなみに、アプリケーションがモジュールになっていると、JavaFX Maven PluginでJREを作成できるのですが、JavaFXアプリケーションをモジュールにする意味ってほとんどないんですよね。なので、ここでは非モジュールアプリケーションをサンプルにして、jlinkを直接使用する方法を説明しました。

OpenJFX時代のJDK選び - もしくはOpenJFX時代のアプリケーション配布

Java 11からJavaFXOracle JDKに含まれなくなったことはみなさんご存知の通り。もともとOpenJDKのJDKプロジェクトにはJavaFX (OpenJFX)は含まれていなかったので、Oracle OpenJDKなどにもJavaFXは含まれていません。

でも、ディストリビューションによってはJavaFXを含んでいるものがあります。たとえば、Liberica JDKなどがJavaFXを含んでいます。

なので、JDKソムリエの @yamadamn さんはJavaFXを使うんだったらLiberica JDKを推すのですが、それはハッキリいってまちがいです。

その理由は、デスクトップアプリケーション特有の視点、つまり

アプリケーションを配布しなくてはいけない

ということに対しての配慮がないからです。

これはどういうことかというと、アプリケーションのユーザーにアプリケーションの実行環境を用意してもらわなくてはいけないということです。

はっきりいうと、ユーザーにLeberica JDKをインストールさせるのは、つらすぎるのです。というか、アプリケーションを実行させるために、事前にJDKをインストールさせるというのが無理があると思うのです。

もし、ユーザーのPCにすでに他のJDKがインストールされていたら、それとは別のJDKをインストールさせるのでしょうか?

たとえば、仕事で使うためにAdoptOpenJDK 11がインストールされているとしましょう。ここにJavaFXのアプリケーションを実行させるためだけに、Liberica JDKをインストールさせて、JDKを切り替えさせるなんてことさせるのでしょうか?

どう考えても無理ですよね。

ましてや、ほとんどのユーザーはJDKをインストールしていないのです。アプリケーションを実行させるためにだけ、アプリケーションとは別にJDKをインストールさせるということは、ユーザにとってハードルが高いのです。

Java 8まではJDKといえば、ほぼOracle JDKだけだったので、まだ許されていたかもしれません。しかし、今は複数のOpenJDKディストリビューションが存在しています。GoogleJDKを検索しても、Liberica JDKはかなり下位にならないと登場しません。そんな誰が作ったかもわからないようなもの (もちろん、Javaアプリの開発者である私たちはLiberica JDKが何かは分かっているはずですが) をユーザにインストールさせるのは酷だと思うわけです。

では、どうすればいいか?

配布するアプリケーションにJREをバンドルさせる

ということです。この観点からJDKを選ぶべきなのです。

JREを同胞させる方法はいくつかありますが、最後に紹介します。

Java 8はやめよう

悪いことはいいません、なるべくJava 11以降のバージョンにしましょう。

OpenJFXではJavaFX 8のバイナリを公開していませんし、ソースのメンテナンスもされていません。たとえば、JavaFX 11以降のバージョンでセキュリティの問題が発見されて、フィックスされたとしても、それがバックポートされることはないのです (ただし、有償のOracle JDK 8だけは、JavaFX 8のサポートを行うようです)。

ということなので、メンテナンスされていないJavaFX 8を使い続けるのはやっぱりよくないと思うわけです。

OpenJFXではJavaFX 11をLTSとして公開しているので、JavaFXはこれを使うのがよいと思います。

gluonhq.com

とはいうものの、上のリンクからダウンロードして使うことはまずないはずです。Mavenのセントラルレポジトリのものを使えるからです。


追記 JavaFX 11のLTSはGluonの商用サービスのようでした。@aoetk さん、ご指摘ありがとうございます。

現状、JavaFXはバージョンアップしてもバグフィックスやパフォーマンス向上など、大きな機能変更は行われなくなっています。なので、その時の最新のJavaFX (今だったらJavaFX 12.0.1)を使ってしまうのがいいと思います。JavaFX 12.0.1はJava 12用に思えるかもしれませんが、Java 11でも使用することが可能です。


JavaFX 8からJavaFX 11への変更では、ほとんどコードの変更は必要ないはずです。モジュールに対応させるため、一部のAPIのパッケージが変更されたりしていますが、普通にデスクトップアプリを作るときにはほぼ関係ありません。

異なるのはビルドと実行方法です。

JavaFXのアプリケーションで使用するようなライブラリは、ほとんどのものがJavaFX 11に対応しています。たとえば、GUI部品のライブラリであるControlsFXもJavaFX 11に対応しています。

github.com

問題はJavaの方で、Java 11に対応していないライブラリも多くあります。でも、デスクトップアプリで使うライブラリはたかが知れているのではないでしょうか。デスクトップアプリと通信するサーバー側はいろいろライブラリ使うかもしれませんけど、そちらはJava 11にする必要はないので。

私が関与したJavaFX 11へのマイグレーションはそれほど多いわけではないのですが、JAXBがJava SEに含まれなくなったことだけを考慮すれば、他のライブラリ(たとえば、Apache Commonsのライブラリとか)はJava 11でも問題なく動作しています。

なぜLiberica JDKはやめるべきなのか

Liberica JDKはユーザーにとってハードルが高すぎる話は前述しましたけど、開発する時にもあまりお勧めできません。

というのも、Liberica JDKJavaFXが同胞されているため、他のJDKJavaFXを使った場合とはアプリケーションのビルド/実行方法がまったく違ってしまうからです。

他のJDKを使った場合、JavaFXは単なるライブラリとして扱います。

ほんのちょっとだけ普通のライブラリと違うのが、モジュールアプリケーションではなくてもモジュールの場所を指定するモジュールパス(--module-path)と使用するモジュールの指定(--add-modules)をしなくてはいけないということです。でも、これは普通のライブラリでクラスパスを指定するのと同じようなものです。

しかも、MavenやGradleを使う場合、JavaFXプラグインがあるので、モジュールパスだのなんだのを気にせずに、まったく普通のライブラリと同じように扱うことができます。

ところが、Liberica JDKではモジュールパスもモジュールの指定も必要ありません。いうなれば、Java 8でのJavaFXの扱いと同じなのですが、もう時代は変わっているのです。

もし、作っているアプリケーションが自分だけで使うのであれば、Liberica JDKでもなんら問題ありません。でも、OSSとしてGitHubなどで公開したいのであれば、他のJDKを使っている人たちでもビルド/実行ができるようにしておかなくてはなりません。

そうすると、結局、Liberica JDKに同胞されているJavaFXではなくて、外部の(たとえば、Mavenのセントラルレポジトリにある) JavaFXを使用する設定にならざるをえません。

だとしたら、JavaFXのためにLiberica JDKを使う意義はなくなりますよね。

もし、Liberica JDKを使うのであれば、バンドルされているJavaFXは使わないのがお勧めです。

では、どのJDKを使えばいいのか?

アプリケーションにJREをバンドルするという前提で考えなくてはなりません。

アプリケーションをフリーで配布するのであれば、有償のサポート付きのJDKを使うことはまずないはずですね。となると、無償のJDKを使うことになります。

繰り返しになりますが、JavaFXは単なるライブラリと割り切った方がよいです。そうすれば、どのJDKを使おうがまったく問題ありません。

だいたいのアプリケーションはMavenもしくはGradleを使って開発されていると思いますが、MavenのセントラルレポジトリにあるJavaFXを使うようにすれば、JDKはどれを使っても同じです。

前述したように、Liberica JDKを使う場合も、バンドルされているJavaFXを使わずに、MavenのセントラルレポジトリのJavaFXを使うようにしましょう。

アプリケーションのリリース間隔が半年未満なのであれば、Oracle OpenJDKでも構わないと思います。新しいバージョンをリリースするときには、新しいJREをバンドルするようにしましょう。

できれば、アプリケーションのバージョンアップ時に、旧バージョンを使っているユーザに通知だせるような仕組みにしておくといいですね。

とはいうものの、ほとんどのアプリケーションはLTSを使いたがるでしょうから、無償でLTSを出しているAdoptOpenJDKやLiberica JDKあたりに落ち着くとは思います。

アプリケーションの配布

アプリケーションを配布する時にはJREをバンドルさせるのがお勧めですが、それにはいくつかの方法があります。

  • jlinkを使用してカスタムJREを作成し、そこにアプリケーションのJARやリソース、起動用のバッチ/シェルスクリプトなどを追加する
  • 上のパッケージをjpackageを使ってネイティブアプリケーション化する
  • Gluon Client Pluginを使ってネイティブアプリケーションを作成する

カスタムJREを作成して、アプリケーションのパッケージを作るのが今のところ一番のお勧めです。

jpackageはJava 8まで存在したjavapackager (もしくはjavafxpackager)をリファインしたものです。現状はまだベータ版で、Java 14がリリースのターゲットになっています。

なので、jpackageを使うのは正式リリース後のJava 14以降がよいと思います。

jdk.java.net

最後のGluon Client PluginはGraalVMを使ってネイティブイメージを作成するものです。現状、GraalVMはJava 8ベースなのですが、Gluon Client PluginではJava 11もサポートしています。

docs.gluonhq.com

私もGluon Client Pluginはまだ使っていないので、試してみたら別エントリーで紹介したいと思います。


追記 現状、Gluon Client PluginはMac OS Xだけのサポートでした。LinuxWindowsをサポートするようになったら、使ってみようと思います。



まとめ

いろいろと雑多な書き方になってしまったので、最後にまとめておきます。

  • JavaFXはもはや普通のライブラリとして扱うべき
  • JavaFX 8はやめてJavaFX 11へ移行しよう
  • JavaFXアプリケーションの配布する時には、JREをバンドルするようにしよう

じゃあ、実際にJavaFXをアプリケーションを作成して、パッケージングするにはどうすればというのは、別エントリーで!

ループアニメーション

このエントリーはJavaFX Advent Calendarの25日目です。

qiita.com

12月15日に開催されたJJUG CCCでStream APIについて登壇しました。その内容はさておき、そのプレゼン資料で使用したアニメーションについて紹介しておきます。

知っている人には当たり前化もしれないですけど。

このプレゼンではStreamということで、流れるものをモチーフにした絵や写真、音楽などを使用しました。タイトルやセクションのタイトルで使用したアニメーションもそう。ベルトコンベアで文字が流れてくるという感じにしています。

このスクリーンショットは文字が切れてしまっているのではなくて、流れている途中なのです。

このアニメーションなんですが、実をいうととても簡単に作ってます。

原理的には短いアニメーションをループさせているだけ。

まず、簡単なサンプルで示しましょう。

package net.javainthebox.loopanim;

import java.util.stream.IntStream;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class LoopAnimation extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Pane root = new Pane();
        init(root);

        Scene scene = new Scene(root, 400, 100);
        stage.setScene(scene);
        stage.show();
    }

    void init(Pane pane) {
        Rectangle rectangle = new Rectangle(0.0, 50.0, 40.0, 10.0);
        rectangle.setFill(Color.WHITE);
        rectangle.setStroke(Color.BLACK);
        pane.getChildren().add(rectangle);

        startAnimation(rectangle);
    }

    void startAnimation(Node node) {
        TranslateTransition trans = new TranslateTransition(Duration.millis(1_000), node);
        trans.setToX(40.0);
        trans.setInterpolator(Interpolator.LINEAR);
        trans.setCycleCount(Animation.INDEFINITE);
        trans.play();
    }

    public static void main(String... args) {
        launch(args);
    }
}

このサンプルを実行すると、長方形が移動を繰り返すというアニメーションを実行します。

この長方形は40ピクセルだけ横方向に移動しています。肝となるのは長方形の幅も40ピクセルだということ。

長方形を描画しているのが、initメソッドです。長方形を生成している部分を抜き出しました。

        Rectangle rectangle = new Rectangle(0.0, 50.0, 40.0, 10.0);

第1, 2引数が長方形の左上の座標。第3引数が幅で、第4引数が高さです。

これに対し、アニメーションしているのが、startAnimationメソッドです。抜粋したのが、以下。

    void startAnimation(Node node) {
        TranslateTransition trans = new TranslateTransition(Duration.millis(1_000), node);
        trans.setToX(40.0);
        trans.setInterpolator(Interpolator.LINEAR);
        trans.setCycleCount(Animation.INDEFINITE);
        trans.play();
    }

移動のアニメーションを行うのが、TranslateTransitionクラスです。コンストラクタの第1引数がアニメーションの長さ、第2引数がアニメーションを行うターゲットです。

第1引数で1秒を指定しているので、1秒のサイクルで繰り返しを行っています。

そのあとの、setToXメソッドが移動した後のX座標を示しています。0から移動しているので、40ピクセルでちょうど長方形の幅と一緒になっています。

次のsetInterpolatorメソッドははじめゆっくり、中は急いで、最後はまたゆっくりなどのように、アニメーションの動きの見た目のスムーズさなどを指定します。ただ、ここでははじめゆっくりとかやっていると、ぎこちなくなるので、常に同じ速さでアニメーションするLINEARを指定しています。

setCycleCountメソッドはアニメーションの繰り返し回数を指定しますが、ここでは無限ループにしています。

最後にplayメソッドでアニメーションの開始です。

これで、長方形が繰り返し移動するアニメーションができます。

長方形1つだけなら意味ないと思いますよね。そうです、この長方形を並べてあげれば、ベルトコンベアのベルト的なものになるわけです。

移動する時に左側が欠けてしまわないように、画面の外側の部分から用意しておきます。ということで、initメソッドを以下のように変更しました。

    void init(Pane pane) {
        Group group = new Group();
        IntStream.range(0, 11)
                .forEach(i -> {
                    Rectangle rectangle = new Rectangle(40.0*i - 40.0, 50.0, 40.0, 10.0);
                    rectangle.setFill(Color.WHITE);
                    rectangle.setStroke(Color.BLACK);
                    group.getChildren().add(rectangle);
                });
        pane.getChildren().add(group);
        
        startAnimation(group);
    }

複数の長方形をGroupオブジェクトでまとめています。

このGroupオブジェクトをアニメーションすれば、あら不思議、永遠にベルトコンベアが動いているように見えます。

http://f.hatena.ne.jp/skrb/20181225223729

スクリーンショットなので、動きがよく分からないとは思いますが、ぜひサンプルを試してみてください!

同じ原理を使えば、歯車とか、エスカレータなどのアニメーションもできるはず。実際にCCCのプレゼンでは歯車も一緒に回転させています。

ということで、ループアニメーションの紹介でした。

非モジュールJavaFXのアプリをJlinkで配布可能パッケージにする

このエントリーはJavaFX Advent Calendarの24日目です。
qiita.com

Java SE 9からjlinkコマンドが使えるようになり、カスタムJREを作れるようになりました。

ところが、jlinkを使うにはモジュールアプリケーションでないとダメだというような間違った言説が流れているような気がします。

jlinkで作るカスタムJREは非モジュールアプリケーションでも可能なんです!!!!

ということで、JavaFXの非モジュールアプリでカスタムJREを作ってみましょう。

サンプルは超簡単なラベルを表示するだけのものです。

package net.javainthebox.hello;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class Hello extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        var label = new Label("Hello, JavaFX!");
        label.setAlignment(Pos.CENTER);
        var scene = new Scene(label, 200, 50);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String... args) {
        launch(args);
    }

}

これをMavenでビルドするのは、以下のエントリーでも書いた通り(pom.xmlに記述した各種バージョンは新しいものにしてくださいね)。

skrb.hatenablog.com

もちろん、自分でビルドするのでも全然かまいません。

ここでは、C:\hello\srcにソースファイルがあるとします。また、JavaFX SDKは"C:\Program Files\Java\javafx-sdk-11.0.1"に配置してあります。

C:\hello\src>javac --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" --add-modules javafx.controls -d ..\bin net\javainthebox\hello\Hello.java

C:\hello\src>cd ..

C:\hello>jar --create --file hello.jar -C bin .

これで、helloディレクトリ直下にhello.jarができました。試しに実行してみましょう。

C:\hello>java --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" --add-modules javafx.controls -cp hello.jar net.javainthebox.hello.Hello

これでラベルだけのフレームが表示されるはずです。

カスタムJREを作成する

jlinkを使う前にjdepsを使わなくてはいけないような風潮がありますが、使っているモジュール(ここではJavaFXのモジュール)が明らかであれば、jdepsを使う必要はありません。

また、非モジュールアプリケーションの場合、jdepsの--print-module-depsや--list-depsオプションは使用できません(JDKのモジュールしか表示されません)。

もし、jdepsを使うのであれば、オプションなしに使用します(もしくはサマリー表示の-s)。

C:\hello>jdeps --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\lib" -s hello.jar
hello.jar
hello.jar -> java.base
hello.jar -> javafx.controls
hello.jar -> javafx.graphics
javafx.base -> java.base
javafx.base -> java.desktop
javafx.controls -> java.base
javafx.controls -> javafx.base
javafx.controls -> javafx.graphics
javafx.fxml -> java.base
javafx.fxml -> java.scripting
javafx.fxml -> java.xml
javafx.fxml -> javafx.base
javafx.fxml -> javafx.graphics
javafx.graphics -> java.base
javafx.graphics -> java.desktop
javafx.graphics -> java.xml
javafx.graphics -> javafx.base
javafx.graphics -> jdk.unsupported
javafx.media -> JDK removed internal API
javafx.media -> java.base
javafx.media -> javafx.base
javafx.media -> javafx.graphics
javafx.swing -> java.base
javafx.swing -> java.datatransfer
javafx.swing -> java.desktop
javafx.swing -> javafx.base
javafx.swing -> javafx.graphics
javafx.swing -> jdk.unsupported.desktop
javafx.swt -> java.base
javafx.swt -> javafx.base
javafx.swt -> javafx.graphics
javafx.swt -> 見つかりません
javafx.web -> java.base
javafx.web -> java.desktop
javafx.web -> java.xml
javafx.web -> javafx.base
javafx.web -> javafx.controls
javafx.web -> javafx.graphics
javafx.web -> javafx.media
javafx.web -> jdk.jsobject
javafx.web -> jdk.xml.dom

ずらずらと出てきますが、重要なのは上の方の3行。hello.jarが直接使用しているのはjava.base、javafx.controls、javafx.graphicsモジュールの3つだということ。javafx.graphicsモジュールはjavafx.controlsモジュールが依存しているので、実質はjava.baseとjavafx.controlsだけでOKです。

これが分かればjlinkでJREが作れます。というか、javacやjavaで実行するときに--add-modulesでjavafx.controlsを加えているので、jdepsなど使わなくても何を使っているかは自明ですけどね。

なお、jlinkでJREを作る時、JavaFX SDKのJARファイルよりJMODファイルを使う方がおススメです。JARでもいいのですが、JARファイルにはネイティブライブラリ(DLLやSO)が含まれていないので、別途コピーする必要があります。

JMODファイルであればネイティブライブラリも含んでいるので、簡単です。ここではJMODファイルは"C:\Program Files\Java\javafx-sdk-11.0.1\mods"に置いてあるとします。

C:\hello>jlink --module-path "C:\Program Files\Java\javafx-sdk-11.0.1\mods" --add-modules java.base,javafx.controls --compress=2 --output jre

これで、jreディレクトリにJREができました。

お分かりだろうとは思いますが、このJREにはアプリケーションのJARファイルは含まれていません。カスタムJREに含められるのはあくまでもモジュールだけです。この場合は標準のモジュールとJavaFXのモジュールで構成されています。

他にモジュールで提供されているライブラリを使用しているのであれば、それもJREに含めることができます。

逆にいうと、非モジュールのライブラリやアプリケーションJARはJREには含まれていないので、実行にはクラスパスで指定する必要があります。

では、実行してみましょう。ここでは、クラスパスでhello.jarを指定しています。

C:\hello>jre\bin\java -cp hello.jar net.javainthebox.hello.Hello

JREのモジュールにjavafx.controlsなどが含まれているので、--module-pathや--add-modulesなどのオプションは必要ありません。

では、配布可能にするためにjreディレクトリにhello.jarもコピーしておきましょう。

C:\hello>copy hello.jar jre
        1 個のファイルをコピーしました。

後は、実行に便利なようにバッチファイル、もしくはシェルスクリプトを作っておきましょう。たとえば、hello.batを作ったとします。ファイルの内容は以下の1行だけ。

bin\java -cp hello.jar net.javainthebox.hello.Hello

試しに、hello.batで正しく実行できるかどうか試しておいてください。

これで、jreディレクトリをそのまま配布することができます(ZIPファイルにするとか、ディレクトリ名を変えるとかはご自由に)。

OpenJFX11 + OpenJDK11 + Maven で JavaFX を動かす

前回のエントリーはOpenJFXとOpenJDKでJavaFXを動かそうというものでした。

skrb.hatenablog.com

でも、OpenJFX SDKにパスを通さなくてはいけないなど、ちょっとめんどうだったのもたしか。

そんなおり、GluonのJoahn VosさんがOpenJFXをMavenでも使えるようにプルリク出してくれました。

github.com

この結果、Maven CentralにOpenJFXが登録されました!!

さっそく使ってみましょう。

サンプルコードはこちら。

package net.javainthebox.hello;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.stage.Stage;

public class Hello extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        var label = new Label("Hello, JavaFX + Maven!");
        label.setAlignment(Pos.CENTER);
        var scene = new Scene(label, 200, 50);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String... args) {
        launch(args);
    }

}

JavaFXHello Worldです。

さて、これをコンパイル、実行するためのpom.xmlがこちら。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.javainthebox</groupId>
    <artifactId>hello</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>10</maven.compiler.source>
        <maven.compiler.target>10</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>11-ea+19</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>java</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>net.javainthebox.hello.Hello</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

このサンプルではjavafx.controlsモジュールを使用しているので、dependencyにはjavafx.controlsを指定しています。Maven Centralではモジュールのピリオドをハイフンで置き換えた名前になっています。

javafx.controlsはjavafx.graphics、javafx.baseに依存しているので、それらも自動的にロードされます。
これ以外にjavafx.fxml、javafx.media、javafx.swing、javafx.webモジュールもちゃんと登録されています。

では、まずはコンパイルしてみましょう。

C:\hello>mvn clean compile
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< net.javainthebox:hello >-----------------------
[INFO] Building hello 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello ---
[INFO] Deleting C:\Users\yuichi\Desktop\hello\target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\yuichi\Desktop\hello\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ hello ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\Users\yuichi\Desktop\hello\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.103 s
[INFO] Finished at: 2018-07-25T21:44:22+09:00
[INFO] ------------------------------------------------------------------------

C:\hello>

あっさり成功しました。もちろん、初回はモジュールのダウンロードがあるはずです。

では、実行してみましょう。

C:\hello>mvn exec:java
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< net.javainthebox:hello >-----------------------
[INFO] Building hello 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ hello ---

ここまで表示されれば、フレームが表示されているはずです。
OpenJFX SDKを使うよりは、とても簡単にJavaFXのアプリケーションを書けるようになりました。

ところで、JavaFXはネイティブライブラリが必要です。Maven Centralを見てみると、やっぱりプラットフォームごとに分けられてました。下のリンクはjavafx.graphicsのものです。

Central Repository: org/openjfx/javafx-graphics/11-ea+19

pom.xmlを見てみると、プラットフォームによって該当するJARファイルも依存があるように書かれてますね。

現状の問題点

Mavenで実行するとき、exec:javaであれば問題ないのですが、exec:execで別プロセスとして実行させると以下のようなメッセージを出して、実行ができません。

C:\Users\yuichi\Desktop\hello>mvn exec:exec
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< net.javainthebox:hello >-----------------------
[INFO] Building hello 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:exec (default-cli) @ hello ---
エラー: JavaFXランタイム・コンポーネントが不足しており、このアプリケーションの実行に必要です
[ERROR] Command execution failed.
org.apache.commons.exec.ExecuteException: Process exited with an error: 1 (Exit value: 1)
    at org.apache.commons.exec.DefaultExecutor.executeInternal (DefaultExecutor.java:404)
    at org.apache.commons.exec.DefaultExecutor.execute (DefaultExecutor.java:166)

ロードされたクラスを調べてみたのですが、Applicationクラスはロードされているものの、その他のJavaFXに関連するクラスがロードされていません。もうちょっと調べてみようと思ってます。

Using JavaFX with OpenJFX + OpenJDK

Hello, JavaFX developers using Oracle JDK!

You may know, we can't get Oracle JDK for free from Java SE 11. Therefore, we have no choice to move to OpenJDK from Oracle JDK.

But, JavaFX is NOT included in OpenJDK!!

You may think "OpenJFX is a project in OpenJDK project, isn't it?" Yes, OpenJFX is a project in OpenJDK project. But, OpenJFX isn't included in JDK project of OpenJDK project.

So, we should use OpenJFX with OpenJDK.

You are worried "I should build OpenJFX?" Don't worry! OpenJFX build can be downloaded in jdk.java.net site now.

But, even if your application is not modular application, you should set module configuration!!

I'll describe as follows. If you'd like to know only setting, you can skip to "JavaFX Configuration for OpenJFX."

Set up OpenJDK and OpenJFX

You can download OpenJDK and OpenJFX belowed URL.

JDK Builds from Oracle

OpenJFX
OpenJFX Early-Access Builds

In this article, I used early access of OpenJDK 11 on Windows.

OpenJDK 11
JDK 11 Early-Access Builds

I used openjfx-11-ea+13 that was released on 9 May and jdk-11-ea+15 that was released on 25 May.

After downloading openjdk-11-ea+15_windows-x64_bin.tar.gz, you should decompress it.

OpenJDK doesn't include installer, so you just add the path to jdk-11\bin directory.

In the same way, decompress openjfx-11-ea+13_windows-x64_bin-sdk.zip after download it.

There are 3 directories in javafx-sdk-11 directory: bin, legal and lib.

There are some native libraries (DLL in Windows) in bin directory, so you also add the path to bin directory.

There are some modular JAR files in lib directory.

Try to execute JavaFX sample application

First of all, let's try to compile and execute a sample application.

Here is the sample application.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class FxSample extends Application {
    public void start(Stage stage) {
        Button button = new Button("OK");
        button.setOnAction(e -> System.out.println("Clicked!"));

        Scene scene = new Scene(button, 300, 200);
        stage.setScene(scene);

        stage.setTitle("FxSample");
        stage.show();
    }

    public static void main(String... args) {
        Application.launch(args);
    }
}

The application indicates "OK" button only, and print out "Cliecked!" when you click the button.

Of course, I don't make module.

I located FxSample.java in javafx-sdk-11 directory.

C:\javafx-sdk-11>java -version
openjdk version "11-ea" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11-ea+15)
OpenJDK 64-Bit Server VM 18.9 (build 11-ea+15, mixed mode)

C:\javafx-sdk-11>javac -cp lib\* FxSample.java

C:\javafx-sdk-11>java -cp lib\*;. FxSample
Error: JavaFX runtime components are missing, and are required to run this application

C:\javafx-sdk-11>

Compile was done, but error occurred in execution!

I don't understand the error message. What is "runtime components"?

At first I thought JVM didn't load DLL, but the problem seems to be not that.

To check what happens, I add -verbose option for execution.

C:\javafx-sdk-11>java -verbose -cp lib\*;. FxSample
[0.008s][info][class,load] opened: C:\jdk-11\lib\modules
[0.018s][info][class,load] java.lang.Object source: jrt:/java.base
[0.019s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.019s][info][class,load] java.lang.Comparable source: jrt:/java.base
[0.023s][info][class,load] java.lang.CharSequence source: jrt:/java.base
[0.024s][info][class,load] java.lang.String source: jrt:/java.base
[0.025s][info][class,load] java.lang.reflect.AnnotatedElement source: jrt:/java.base
[0.025s][info][class,load] java.lang.reflect.GenericDeclaration source: jrt:/java.base
[0.026s][info][class,load] java.lang.reflect.Type source: jrt:/java.base
[0.027s][info][class,load] java.lang.Class source: jrt:/java.base
     <<snip, snip, snip>>

You can see what class was loaded by using -verbose option. For example, java.lang.Object class in java.base module was loaded firstly.

I checked all loaded classes, but JavaFX classes weren't loaded!!

That is why execution was failed.

I wonder why JavaFX classes weren't loadded in spite of setting classpath.

I remembered the case of Java EE modules such as JAXB in Java SE 9 or 10.

These modules are included in Java SE, but are not included in standard modulepath. Therefore, we should add --add-modules options when using these modules even if we execute non-modular application.

JavaFX case may be the same.

If so, we should set modulepath and --add-modules option.

JavaFX Configuration for OpenJFX

We set --module-path option or -p option for modulepath. value of modulepath is not file, but directory that modular jar files are located in.

In this case, I set lib directory for modulepath.

If modular application, we describes modular dependencies in module-info.java file. But, we use --add-modules option when non-modular application.

Which modules the application use? You can check them by jdeps tool.

C:\javafx-sdk-11>jdeps --module-path lib -s FxSample.class
FxSample.class -> java.base
FxSample.class -> javafx.base
FxSample.class -> javafx.controls
FxSample.class -> javafx.graphics
javafx.base -> java.base
javafx.base -> java.desktop
javafx.controls -> java.base
javafx.controls -> javafx.base
javafx.controls -> javafx.graphics
    <<snip, snip, snip>>

We don't use -p option in jdeps tool, so use --module-path. The other option -s is for showing dependency summary only.

In the result, we can know FxSample class uses java.base, javafx.base, javafx.controls and javafx.graphics.

But, we don't need to set java.base module because it is loaded by default.

Moreover, javafx.controls module uses javafx.base and javafx.graphics. So, we should set only javafx.controls for --add-modules.

Then, let's execute!

C:\javafx-sdk-11>java -p lib --add-modules javafx.controls FxSample

If going well, you can see the window below.