JavaFX FXML GUIデザイン
JavaFXでは「FXML」というシンプルな言語を使うことで、XMLを利用してGUIを簡単にデザインできる。ここではFXMLの基本的な使い方を説明する。
FXMLとは
JavaFXは、豊かなGUIを持つアプリケーションを素早く開発できることを重視して作られたGUIライブラリである。しかし、ここまで見てきた範囲では「Swingとあまり変わらない」という印象を持ったかもしれない。アクションイベントの設定などは簡単だったが、「この程度ならわざわざSwingから移行する必要はない」と感じた人も多いだろう。
実はJavaFXでは、Javaクラスを作ってJavaソースコードでGUIを構築する方式が中心というわけではない。それ以上に重視されているのが、FXMLを利用したGUIデザインである。
FXMLは、JavaFXに含まれているXMLベースのGUIマークアップ言語である。「言語」といってもXMLベースのマークアップなので、Javaソースコードを書くよりも記述はかなり簡単である。利用するコンテナやコントロールの内容をXMLとして記述するだけでGUIをデザインできる。その後はJava側でこれを読み込んで表示する短いコードを書くだけで、本格的なGUIアプリケーションを作成できる。
JavaFX Scene Builderについて
FXMLがXMLベースで記述できる利点は、単に「書きやすい」ことだけではない。XMLベースであるため解析しやすく、各種ツールによってデザインしやすいという利点もある。実際にJavaの開発元であるOracleは、FXMLに対応したGUIデザインツール「JavaFX Scene Builder」を提供している。
このツールは、マウスで部品をドラッグして配置するだけでFXMLソースコードを作成できるツールである。FXMLを利用するなら、このようなGUIデザインツールの利用は必須といってよい。まずはこうしたツールを使えるようにしておこう。
JavaFX Scene Builderのダウンロード先:
http://www.oracle.com/technetwork/java/javase/downloads/javafxscenebuilder-info-2157684.html
FXMLの基本コード
では、FXMLファイルとはどのような形になっているのだろうか。基本コードをざっと理解しておこう。
FXMLは.fxml拡張子のテキストファイルとして作成される。このソースコードの基本形をまとめると、おおよそ次のようになる。
FXMLの基本形
<?xml version="1.0" encoding="UTF-8"?>
<?import XX ?>
<Paneクラス
xmlns="..."
xmlns:fx="...">
<!-- Paneに含めるコントロール類 -->
</Paneクラス>
<?xml ?>の後に、Javaのimport文に相当する<?import ?>タグを用意する。これにより使用するクラスをimportできる。<?import ?>タグを書かない場合、クラスはすべて完全なパッケージ名で指定しなければならないので注意しよう。
FXMLのルートタグ、つまり最上位タグはPaneクラスのタグで作成する。たとえばBorderPaneなら<BorderPane>タグを書き、その中にコントロールなどのタグを書く。
基本的にFXMLでは、Javaのクラス名をそのままタグ名として書けば多くの場合認識される。Labelなら<Label>という形である。
ルートタグにはxmlns、つまりXML名前空間の属性を記述する。また、FXML自身の名前空間属性としてxmlns:fxも用意する。この2つの属性により、このXMLコードがFXMLコードとして認識される。
実際には、単にJavaコード内でFXMLファイルをロードして使うだけなら、これらの名前空間属性はなくてもよい。書かなくてもJava内でFXMLを正しくロードして利用できる。
これらはGUI作成ツールなどを使うときに役立つが、必ずしもなくてはならないものではない。それでもFXMLの基本として書いておく習慣をつけよう。
次はFXMLの基本コード例である。
<?import XX ?>
<Paneクラス
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<!-- Paneに含めるコントロール類 -->
</Paneクラス>
FXMLを使う
FXMLを使った簡単なサンプルを作ってみよう。次のコードがFXMLの例である。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<BorderPane
xmlns="http://javafx.com/javafx/8"
xmlns:fx="http://javafx.com/fxml/1">
<top>
<Label text="This is FXML!" />
</top>
</BorderPane>
これはLabelを1つBorderPaneに追加しただけの単純な例である。内容を説明すると次のようになる。
<? import?>タグ
最初に複数の<? import?>文が書かれている。実はこれはJavaFX Scene Builderで作成したときに自動で追加されるものである。Paneやコントロール関連のクラスのimport文をまとめて用意している。
<BorderPane>タグ
ここでは<BorderPane>というタグがあり、その中に<Label>タグが書かれている。よく見ると、直接書かれているのではなく、<Top>というタグで囲まれていることがわかる。これはBorderPane特有のもので、結合される位置を表すタグの中にコントロールのタグを書く仕組みである。用意されているのは次の5つである。
<BorderPane>の位置タグ
<Top>, <Bottom>, <Right>, <Left>, <Ceter>
<Label>タグ
<Label>タグでは、text属性に表示するテキストを指定する。コントロールのプロパティは、このように属性としてタグに書くことができる。text="OO"はsetText("OO")のような処理をしていると考えると理解しやすい。
このように、コントロールのプロパティを設定するsetOO(値)の処理は、そのままコントロールタグのOO=値のような形に置き換えられるものが多い。これはLabelだけでなく、コントロール全般に言えることである。
FXMLをロードする
FXMLは当然ながら、書いただけでは動作しない。Javaクラス側でこれをロードしてインスタンス化し、ウィンドウに組み込んで初めて利用できる。これは一般的にApplicationクラスのstartメソッドに処理を書く作業になる。FXML利用の流れを整理しよう。
Paneをロードする
FXMLをロードしてPaneインスタンスを生成する。これはjavafx.fxmlパッケージのFXMLLoaderクラスを使う。この中のloadクラスメソッドを呼び出すことで、FXMLファイルをロードしインスタンスを取得できる。
FXMLLoader.load(URL)
引数にはURLインスタンスを渡す。FXMLファイルがApplicationクラスと同じ場所に保存されている場合、ClassのgetResourceメソッドでロードするのがよい。つまり、次のような形である。
変数 = FXMLLoader.load(getClass().getResource("xxx.fxml"));
このxxx.fxmlにロードするFXMLファイル名を指定すればよい。注意すべき点は取得されるインスタンスである。これはObjectインスタンスとしてキャストされているため、取得後に本来のPaneインスタンスへ変換して利用する。
Sceneに追加する
FXMLからPaneインスタンスを取得したら、あとは簡単である。Sceneインスタンスを生成するときにこのPaneを引数として指定し、Sceneを用意してStageに設定すればよい。
Scene 変数 = new Scene(Paneインスタンス, 幅, 高さ);
Sceneはこのようにインスタンス化できる。FXMLを利用しても、Paneインスタンスさえ得られれば後は変わらない。
次は実際の使用例である。
package com.devkuma.javafx;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class App extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
BorderPane root;
try {
root = (BorderPane)FXMLLoader.load(getClass().getResource("app.fxml"));
Scene scene = new Scene(root,200,100);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ここではcom.devkuma.javafx.Appクラスと同じ場所に、app.fxmlというファイル名でFXMLファイルが配置されているという前提でコードを書いている。実際に前のFXMLファイルを配置して実行してみよう。正しくウィンドウが表示されるはずだ。
スタイルシートを利用する
コントロールの細かな表示設定は、FXMLの属性として書き込むよりも、さらにわかりやすい方法がある。それが「スタイルシート」を使う方法である。JavaFXには、コントロールのプロパティをスタイルシートから読み込んで適用する機能がある。これを利用すれば、コントロールの表示をJavaコードで書かなくても簡単に構成できる。
スタイルシートの使用はとても簡単である。FXMLのPaneタグの中に、次のような形でスタイルシートを書く。
<stylesheets>
<URL value="@スタイルシート"/>
</stylesheets>
<stylesheets>タグの中に、ロードするスタイルシートのURLを用意する。必要な数だけ書ける。<URL>タグには、valueでロードするスタイルシートのパスを指定する。@ファイル名のように書くことで、FXMLファイルと同じ場所にあるスタイルシートファイルを指定できる。
次のソースコードは、前のFXMLにスタイルシートを追加したものである。
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<stylesheets>
<URL value="@app.css" />
</stylesheets>
<top>
<Label fx:id="label1" text="This is FXML!" />
</top>
</BorderPane>
これでFXMLファイルと同じ場所にあるapp.cssをロードする。ここではスタイルシートを適用するため、<Label>タグにIDを指定している。
<Label fx:id="label1"... />
このようにfx:idと書くことで、そのコントロールにIDを指定できる。スタイルシートではこのIDを使って、特定のコントロールにスタイルを適用できる。
スタイルシートを書く
ではスタイルシートを書いてみよう。まずFXMLファイルと同じ場所にapp.cssというファイル名で作成する。
そしてファイル内に、FXMLで書いた<Label fx:id="label1"/>コントロールのスタイルを記述する。次は簡単な例である。
@CHARSET "utf-8";
Label#label1 {
-fx-font-family:Serif;
-fx-font-size:24pt;
}
文字コードの指定
先頭に@CHARSET "utf-8";が書かれている。基本的にスタイルシートはUTF-8でエンコードする。これは必須ではないが、書いておくのが基本だと考えよう。
コントロールのスタイル
ここではlabel1というIDを持つLabelに対してスタイルが書かれている。
Label#label1 {...}
このようにスタイル指定は「クラス名#ID名」のような形式で書く。HTMLのスタイルで「タグ名#ID」と書くのと同じ感覚である。
もちろんクラス名だけを指定すれば、そのクラスのコントロールすべてにスタイルを適用できる。たとえばLabel {...}とすれば、すべてのLabelのスタイルをまとめて設定できる。
フォント属性について
ここでは2つのスタイルが書かれている。-fx-font-familyと-fx-font-sizeである。これらはフォントファミリー名とフォントサイズを指定するものだ。
JavaFXのスタイルは、すべてこのように-fx-OOで始まる名前になっている。多くはHTMLで使われるスタイル名の前に-fx-を付けるだけで利用できるが、必ずしもすべてがそうではない。
たとえば背景色は-fx-background-colorだが、テキスト色は-fx-text-fillになっている。JavaFX独自の値もあるので注意しよう。