Corredor

ウェブ、プログラミングの勉強メモ。

VSCode + Gradle で構築したプロジェクトで JavaFX を実装する

エディタに VSCode、ビルドツールに Gradle を使用した Java プロジェクトで、JavaFX を使ってみる。

JavaFX とは、AWT や Swing の後発ライブラリで、Java 言語でクロスプラットフォームな GUI アプリを作るためのライブラリだ。

実装するにあたって、いくつか手を入れないとうまく動かなかったので、その点を紹介する。

前提条件

前提条件は以下のとおりとする。

  • JDK は JavaFX が同梱されている JDK8 を使用する
    • 現在の JDK には同梱されていないので、最新版の JDK で構成する場合は、OpenJDK と OpenJFX を入れる必要があるらしい (試していない)
  • $ gradle init --type java-application で雛形を作成しておく
  • VSCode に拡張機能「Java Extension Pack」をインストールしておく

VSCode に拡張機能を入れたあと Gradle プロジェクトを開くと、

  • .project
  • .classpath
  • bin/

などのファイルが自動的に生成される。これらによって VSCode 上の自動補完などが効くようになるようだ。

.classpath を編集して JavaFX を参照できるようにする

現状、VSCode 上でコードを入力しても、JavaFX 関連のクラス名だとうまく自動補完が効かない。そこで、.classpath ファイルを開いて次のように編集すると、自動補完などが効くようになる。

<!-- ↓元はこのような行がある -->
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/" />

<!-- ↓次のように編集する -->
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/">
  <accessrules>
    <accessrule kind="accessible" pattern="javafx/**" />
    <accessrule kind="accessible" pattern="com/sun/javafx/**" />
  </accessrules>
</classpathentry>

このように accessrules 要素でパッケージを参照できるようにしておく。これは外部 JAR ライブラリを導入する場合も必要になることがあるので、覚えておくと良いかと。

<!-- 例として OpenCV の JAR ファイルを読み込む場合 -->
<classpathentry kind="lib" exported="true" path="lib/opencv.jar">
  <accessrules>
    <accessible kind="accessible" pattern="**" />
  </accessrules>
</classpathentry>

コレを自動でなんとかしてくれる方法がないか調べたが、よく分からず。良い管理方法があったら教えてほしい。

build.gradle を編集して JavaFX 込みのビルドができるようにする

.classpath の記述は VSCode 上の自動補完やコンパイルにのみ影響するらしい。gradle build コマンドなどを使う際の依存関係は、build.gradle ファイルを編集しないといけない。

JavaFX を使う上で必要な変更を以下に記す。

plugins {
  // 既存の 'java' と 'application' はそのままに以下を追加
  
  // classpath を出力させて自動補完を効かせる
  id 'eclipse'
}

dependencies {
  // 既存の依存関係はそのままに以下を追加
  
  // JavaFX を追加する
  compile files("${System.properties['java.home']}/lib/ext/jfxrt.jar")
}

// Java ファイルと同じ階層に FXML ファイルを置けるようにする
sourceSets {
  main {
    java {
      srcDir 'src'
    }
    resources {
      srcDir 'src/main/java'
    }
    java.outputDir = file('bin')
    output.resourcesDir = file('bin')
  }
}

ユニットテストが鬱陶しい場合は以下を消してしまえば、./test/ 配下のファイルによる影響をなくせる。

dependencies {
  // Use JUnit test framework
  testImplementation 'junit:junit:4.12'
}

JavaFX を実装してみる

試しに JavaFX を用いたウィンドウを開いてみる。とっても簡素なモノだ。

  • src/main/java/my_app/App.java
    • エントリポイント
package my_app;

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public final class App extends Application {
  public static void main(final String ...args) {
    launch(args);
  }
  
  @Override
  public void start(final Stage primaryStage) throws IOException {
    final FXMLLoader loader = new FXMLLoader(getClass().getResource("./root.fxml"));
    final Pane root = loader.load();
    final Scene scene = new Scene(root, 600, 400);
    primaryStage.setScene(scene);
    primaryStage.show();
  }
}

FXMLLoader を使い、root.fxml というファイルを読み込んでいる。

  • src/main/java/my_app/root.fxml
    • コンポーネントの UI (View) を定義する XML 風のファイル
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>

<HBox xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="my_app.RootController">
  <Label text="TEST" />
</HBox>

先程 build.gradlesourceSets ブロックを記述したので、.java ファイルと同じ階層に .fxml ファイルが置けるようになった。

fx:controller 属性で対応するクラスを指定している。

  • src/main/java/my_app/RootController.java
    • root.fxml に紐付くコントローラクラス
package my_app;

public final class RootController { }

今回は特に処理を入れていないので、コントローラクラスには実装なし。

  • Initializable を実装すれば、FXMLLoaderload() したタイミングで初期処理が実行できる
  • 画面上の要素に fx:id 属性を付与すれば、コントローラ側でその要素を特定して参照できる
    • <Label fx:id="myLabel" />
    • @FXML private Label myLabel; とアノテーション付きでフィールドを宣言する
    • this.myLabel.setText("HOGE FUGA"); というように要素を操作できる
  • onAction 属性を使えば、ボタンからコントローラのメソッドを呼び出したりできる
    • <Button onAction="#onExec" text="実行する" />
    • @FXML public onExec() {} とアノテーション付きでメソッドを宣言する

どんなレイアウトがあって、どんなコントロール部品があるのか、どうやってプロパティを設定したらいいか、とかいうことは、チマチマ検索していくしかない…。「こういうことをやりたいんだけどどうやるんだろう?」という逆引きは StackOverflow が参考になると思う。

アプリを起動してみる

とりあえずココまで実装すると、

$ ./gradlew run

で Java アプリが起動する。実行割合が 75% 程度で止まったまま、GUI ウィンドウが開くことが確認できると思う。ウィンドウを閉じたり、Ctrl + C でターミナルを終了したりすれば良い。

今日はココまで。