JavaFX Hands on Lab
JavaFX で Hello, World!
JavaFX ではじめのプログラムは、やはり Hello, World! です。
その前に、まず開発環境を使えるようにしましょう。ここでは、すでにJDK 7、Scene Builder 1.0/1.1がインストールされていることを想定しています。また、IDE として、NetBeans 7.3.1/7.4 もしくは IntelliJ IDEA 12/13 のいずれかがインストールされているとします。
NetBeans も IntelliJ IDEA も、JavaFX を使うための設定は必要ありません。しかし、外部ツールの Scene Builder の設定だけはやっておかなくてはなりません。
なお、ここでは Windows 8 を使用しています。他の OS でも同様に行うことができますが、不明な点などありましたらチューターに質問してください。
完成した Hello, World! のプロジェクトは GitHub からクローンできます。
Hello プロジェクト https://github.com/skrb/Hello
NetBeans の設定
NetBeans では Scene Builder との連携を行うため、Scene Builder の場所を設定します。
- メニューバーの [ツール]-[オプション] を選択。
- 上部に表示されているカテゴリから [Java] を選択。さらにタブの [JavaFX] を選択する。
- [Scene Builderホーム] のドロップダウンリストをクリックすると[参照...]の項目が表示されるので、それを選択。
- ファイルチューザが表示されるので、Scene Builderをインストールしたディレクトリを指定する。すると、ドロップダウンリストに、今指定したディレクトリが表示されるので、右下の [OK] をクリック。
IntelliJ IDEA の設定
IntelliJ IDEA も NetBeans と同様に Scene Builder の場所を設定します。
- Quick Start から [Configure]-[Settings] を選択する。
- IDE Settings の中から [JavaFX] を選択する。
- [Path to SceneBuilder] の右側にある [...] ボタンでファイルチューザを表示させ、Scene Builder の実行形式 (Windows の場合、JavaFX Scene BUilder 1.1.exe ファイル) を選択する。
- 右下の [OK] ボタンをクリックして設定を終了させる
プロジェクトの作成 (NetBeans)
では、はじめての JavaFX プロジェクトを作成しましょう。
- メニューバーの [ファイル] - [新規プロジェクト] を選択します。
- カテゴリの中から [Java] を選択し、プロジェクトから [JavaFX FXML Application] を選択し、[次へ] をクリック。
- プロジェクト名を適当に設定します。ここでは Hello としました。また、プロジェクトの場所も必要に応じて、指定します (デフォルトでもかまいません)。
- FXML 名も同様に設定します。ここでは HelloView としました。最後に [終了] をクリック。
これでプロジェクトが作成できます。プロジェクトには、以下の 3 つのファイルが生成されます。
- Hello.java
- HelloView.fxml
- HelloViewController.java
Hello.java がメインクラス、HelloView.fxml がGUI の構成を定義したファイルになります。最後の HelloViewController.java は FXML と Java を結びつけるためのクラスになります。
NetBeans が作成したこれらのファイルは、アプリケーションとして実行することができます。左側の [プロジェクト] ペインの Hello を右クリックし、ポップアップメニューの [実行] を選択します。
すると、以下のようなウィンドウが表示されます。
[Click Me!] ボタンをクリックすると、ボタンの下に "Hello World!" が表示されます。
この後、これらのファイルを変更していきます。
プロジェクトの作成 (IntelliJ IDEA)
同様に IntelliJ IDEA でも JavaFX のプロジェクトを作成しましょう。
- Quick Start の [Create New Project] を選択します。
- Java カテゴリの []JavaFX Application] を選択します。
- 右サイドの [Project name] は NetBeans の場合と同様 Hello とします。[Project location] は必要に応じて設定します。
- []Project SDK] は1.7 を選択。もし、1.7 がない場合、右側の [New] ボタンをクリックし、ポップアップメニューから [JDK] を選択します。すると、ファイルチューザが表示されるので、JDK をインストールしていある場所を指定します。
- 最後に [Finish] ボタンをクリックします。
これでプロジェクトが作成できます。作成されたプロジェクトには以下の3つのファイルが作成されます。
- Main.java
- sample.fxml
- Controller.java
このプロジェクトもそのまま実行できます。実行には、メニューバーの [Run] - [Run 'Main'] 、もしくはツールバーの緑の矢印でも実行できます。
しかし、ここでは NetBeans のプロジェクトとファイルを合わせたいため、Main.java などのファイルを削除し、NetBeans で作成したファイルと同じファイルを作成します。
sampleパッケージを右クリックして、ポップアップメニューから [Delete...] を選択します。sampleパッケージにはファイルがあるため、確認のダイアログが表示されますが、ダイアログの [Delete] ボタンで削除します。
次に src を右クリックし、ポップアップメニューから [New] - [Package] を選択し、hello パッケージを作成します。次に、今作成したhelloパッケージを右クリックし、ポップアップメニューの [New] - [Java Class] を選択して、Hello クラスを作成します。この時、下図にあるように [Kind] を JavaFXApplication にします。
同様に HelloViewController クラスも作成します。この時は [Kind] は Class にします。
最後に、HelloView.fxml を作成します。[New] - [File] で HelloView.fxml を指定します。
ここまでで、Project ペインは下図のようになっているはずです。
ファイルの内容に関しては、次章で説明します。
Hello, World!
では、Hello, World! を作成していきましょう。
JavaFX のアプリケーションは、メインクラス、FXML、コントローラクラス、そしてモデルから構成されます。
メインクラスは、メインとなる FXML のロードとメインウィンドウの表示を行うだけです。このため、ほとんどのアプリケーションでメインクラスの内容は同じになります。
GUI の構成を記述したのが FXML です。FXML は単独で使用することもありますし、複数の FXML で GUI を記述することもあります。
そして、FXML に対応するのがコントロールクラスです。FXML とコントローラクラスは 1 対 1 に対応させる必要があります。これは複数の FXML を使用した場合でも同じです。
最後にモデルとなるクラスですが、こちらに関しては JavaFX では規定はありません。ただし、JavaFX で扱いやすいように、JavaFX で拡張した Java Beans を使用することが多いです。
Hello, World! 程度のアプリケーションであれば、モデルに相当するクラスは必要ありません。
では次節から、メインクラス、FXML、コントローラクラスの順に作成していきましょう。
メインクラス
Hello プロジェクトでは、メインクラスはHelloクラスになります。
NetBeansでは自動的に作成されたHelloクラスは次のようになっています。
package hello; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class Hello extends Application { @Override public void start(Stage stage) throws Exception { // FXMLのロード Parent root = FXMLLoader.load(getClass().getResource("HelloView.fxml")); // シーンの生成 Scene scene = new Scene(root); // ステージにシーンをセット stage.setScene(scene); // ステージを表示 stage.show(); } public static void main(String[] args) { // JavaFX のスレッドの起動 launch(args); } }
JavaFX のアプリケーションクラスは Application クラスのサブクラスにする必要があります。そして、メインクラスのコンストラクタではなく、start メソッドに GUI に関する記述を書いていきます。
ちょうどアプレットが Applet クラスのサブクラスとして定義し、start メソッドに GUI を記述するのと同じような形式になります。
JavaFX は他の GUI ライブラリと同様にシングルスレッドで動作するのですが、start メソッドはその JavaFX のスレッドからコールされます。
FXMLLoader クラスの load メソッドが FXML のロードを行う部分です。load メソッドの戻り値は FXML で記述されたルートのコンテナクラスになります。
ここでは、すべてのコンテナクラスのスーパークラスである Parent クラスになっています。
ルートコンテナを貼るのが Scene クラスです。JavaFX では GUI 構造をシーングラフと呼びますが、そのルート要素になるため Scene クラスと呼ばれます。
Swing では JRootPane クラスに相当します。
そして、Stage クラスがウィンドウに相当します。Stage オブジェクトに Scene オブジェクトをセットして、show メソッドで表示を行います。
最後に main メソッドです。main メソッドでは JavaFX のスレッドを起動するために Application クラスの launch メソッドをコールします。
前述したように、JavaFX アプリケーションのメインクラスは Hello クラスとほぼ同じです。ここでも、NetBeans が生成した Hello クラスを変更せずに使用していきます。
IntelliJ IDEA を使用している場合はこのコードをコピペしてください。
FXML
FXML では XML で GUI の構造を記述していきます。
FXML は Java との連携を行う要素などを除いて、スキーマレスな XML になっています。逆にいうと、スキーマレスであるため、自作した GUI 部品なども FXML で扱うことが可能です。
NetBeans が生成した HelloView.fxml を以下に示します。
<?xml version="1.0" encoding="UTF-8"?> <?import java.lang.*?> <?import java.util.*?> <?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="hello.HelloViewController"> <children> <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" /> <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" /> </children> </AnchorPane>
FXML ではクラス名が XML の要素名になります。ただし、クラスには名前空間があるので、Java のコードと同様に import をする必要があります。
FXML のルート要素では XML の名前空間として fx を接頭辞とするスキーマを指定します。また、コントローラクラスもルート要素の属性として指定します。コントローラを指定するには fx:controller 属性を使用します。
JavaFX のクラス名が FXML の要素名になりますが、クラスのプロパティは属性もしくは子要素として表します。たとえば、ルート要素の AnchorPane クラスでは、サイズを指定する prefHeight プロパティや prefWidth プロパティは属性として記述してあります。
逆に AnchorPane クラスに貼る子ノードを表す children プロパティは子要素として表しています。これは children の値に、他のクラス、つまり XML の要素が含まれるためです。
ここでは AnchorPane クラスに、ボタンを表す Button クラスと、ラベルを表す Label クラスを貼っています。
では、この FXML を編集していきましょう。
で、Scene Builder を起動..... とは行きません。というのも、Scene Builder ではルート要素のコンテナの編集だけはできないからです。
ここで使用しているルートコンテナの AnchorPane は便利なコンテナなのですが、はじめにしてはちょっと難しいので、もっと簡単に使用できるコンテナクラスの VBox クラスを使用していきます。
VBox クラスは垂直方向に GUI 部品 (JavaFX ではコントロールと呼ぶため、これ以降はコントロールと記述します) を並べていくコンテナクラスです。
では、HelloView.fxml を IDE で次のように変更してみましょう。なお、煩雑になるため、これ以降は import 要素は省略します。
<VBox prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="hello.HelloViewController"> </VBox>
ルート要素の <VBox> だけに指定してしまいました。
ここで、やっと Scene Builder の起動です。
NetBean では、左側のプロジェクトペインで FXML をダブルクリックすれば、Scene Builder が起動します。IntelliJ IDEA の場合、FXML をダブルクリックするだけではエディタペインに FXML が表示されるだけです。Scene Builder を起動するには FXML を右クリックし、ポップアップメニューの [Opne In Scene Builder] を選択します。
Scene Builder は左上のペインがコンテナやコントロールなどのライブラリの一覧があり、左下のペインに現在の GUI の階層が表示されます。ここではルート要素の VBox のみが表示されています。
右側のペインには選択している要素のプロパティやレイアウトに関する情報が表示されます。
そして、中央が GUI の編集画面になります。
では、ここに Hello, World! を表示するためのラベルを貼ってみましょう。
- ライブラリから Label を選択します
- Label をドラッグして、中央のエディタペインもしくは、左下の階層ペインにドロップします
- Label が左上に表示されているのを確認します
- Label を選択したまま、右側のプロパティペインの Text プロパティを Hello, World! に変更します
- Label の表示位置を中央にするため、VBox を選択します。編集ペインで選択しにくい場合は階層ペインで選択します
- プロパティペインから子ノードの表示位置を指定する Alignment を TOP_LEFT から CENTER に変更します
- ここまでできたら FXML を保存します。メニューバーの [ファイル] - [保存] を選択するか、Ctrl + S で保存します。保存すると、編集ペインの上部に保存したメッセージが表示されます
ここまで完了したら、IDE に戻りましょう。IDE のエディタで HelloView.fxml を表示します。
NetBeans では FXML を右クリックし、ポップアップメニューの [編集] を選択します。IntelliJ IDEA の場合、FXML をダブルクリックします。
編集した HelloView.fxml を以下に示します。
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="hello.HelloViewController"> <children> <Label text="Hello, World!" /> </children> </VBox>
<children> 要素に <Label> 要素が追加され、text 属性が Hello, World! になっていることが分かります。また、<VBox> 要素の alignment 属性が CENTER になっていることも分かります。
ここまでであれば、コントローラクラスの編集は必要ありません。
では、実行してみましょう。
Hello, World! を改造
Hello, World! はできたので、これを改造してみましょう。
ここでは、World の部分を入力した文字列に変更できるようにしてみます。GUI の構造は次のようにしていきます。
ルートコンテナの VBox に、水平方向にノードを並べる HBox を貼ります。HBox には TextField と Button を配置します。
TextField に文字列を入力して、Button をクリックすれば、Label の文字列が更新するようにします。
メインクラスは変更がないので、FXML をまず編集します。
FXML
- Scene Builder のライブラリから HBox を選択し、VBox の上にドロップします
- このままだと Label が HBox の上部に表示されてしまうので、階層ペインで HBox を選択し、Label の上になるようにドラッグ & ドロップします
- HBox も子ノードが中央から配置されるように、Alignment プロパティを CENTER に変更します
- HBox に TextField をドラッグ & ドロップします
- さらに Button もドラッグ & ドロップします
- デフォルトのままだと TextField と Button が接しているため、ノード間の間隔を指定します。これには HBox を指定して、レイアウトの Spacing プロパティを指定します。ここでは 10 にしてみます
- Button の Text を Say Hello に変更します
この状態でセーブを行います。HelloView.fxml は次のようになりました。
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="hello.HelloViewController"> <children> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0"> <children> <TextField prefWidth="200.0" /> <Button mnemonicParsing="false" text="Say Hello" /> </children> </HBox> <Label text="Hello, World!" /> </children> </VBox>
では、実行してみましょう。
無事に起動できることを確認したら、Label の文字列を更新できるように変更してきましょう。
FXML の要素のうち、Java でアクセスしたい要素には fx:id 属性を追加します。また、Button がクリックされたときのイベント処理などで呼び出されるメソッドはイベントの種類に応じた属性で指定します。
たとえば、Button がクリックされた場合は onAction 属性に指定します。
ここではLabel、TextField の fx:id を設定し、TextField と Button の onAction 属性を設定しましょう。
- Label を選択し、右側のコードから fx:id を設定します。ここでは label としました
- TextField も同じように fx:id を設定します。ここでは textField としました。TextField はコントローラクラスに設定を行っていないため、警告が出ますが、ここでは気にしないことにします
- さらに TextField の On Action も設定します。ここではメソッド名を handle としました。同様に警告が出ますが、気にしない、気にしない
- Button の On Action も同様に handle に設定します。Button は直接 Java からはアクセスする必要がないため、fx:id は設定しません
さて、ここで HelloView.fxml がどうなっているか確認してみましょう。
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="hello.HelloViewController"> <children> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0"> <children> <TextField fx:id="textField" onAction="#handle" prefWidth="200.0" /> <Button onAction="#handle" mnemonicParsing="false" text="Say Hello" /> </children> </HBox> <Label fx:id="label" text="Hello, World!" /> </children> </VBox>
TextField と Label に fx:id 属性が設定され、TextField と Button に onAction 属性が追加されました。onAction 属性は前述したようにメソッド名ですが、頭に # をつけて記述する必要があります。
コントローラクラス
ここで、やっとコントローラクラスが登場します。
コントローラクラスはどのようなクラスでもかまいません。ただし、コンストラクタなどは記述できないので、初期化が必要な場合は Initializable インタフェースを実装します。Initializable インタフェースは initialize メソッドを定義しています。
コントロールクラスで FXML と連携するプロパティには @FXML アノテーションを付加して定義します。プロパティ名は fx:id で指定した名前と同一にします。
FXML でイベント処理として指定されたメソッドも @FXML アノテーションを付加して定義します。メソッドも名も FXML で # 以下の名前にします。
とりあえず、HelloView.fxml で定義した fx:id と onAction を HelloViewController クラスに定義していきます。
public class HelloViewController implements Initializable { @FXML private Label label; @FXML private TextField textField; @FXML private void handle(ActionEvent event) { System.out.println(textField.getText()); } @Override public void initialize(URL url, ResourceBundle rb) {} }
handle メソッドの引数は ActionEvent クラスになります。引数は、使用するイベントによって変化します。onAction プロパティの場合、ActionEvent クラスになるということです。
ここでは、handle メソッドで TextField に入力された文字列を標準出力に出力するようにしました。実行して、正しく出力されるか確認してみてください。
このように、fx:id 属性と @FXML アノテーションを設定することで、FXML の要素と JavaFX のオブジェクトをバインドすることができました。
では、Label を変更するようにしてみましょう。そのためには、handle メソッドをつぎのように変更します。
@FXML private void handle(ActionEvent event) { label.setText("Hello, " + textField.getText() + "!"); }
label プロパティも @FXML で修飾しているため、FXML の要素にそのままアクセスすることができます。
では、実行してみましょう。
これで改良版 Hello, World! の完成です。