JavaFX で Hello, World!

JavaFX シリーズ目次

今日から、JavaFX のプログラミングに入っていきます。

その前に、おととい JavaFX 2.1 の build 12 が公開されました。今日から、この build 12 を使っていきます。

さて、一番はじめのプログラムといえば、やっぱり Hello, World! ですね。

とりあえず、見ていただきましょう。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Hello extends Application {
   
    @Override
    public void start(Stage stage) {
        // ステージのタイトルを設定
        stage.setTitle("Hello World!");
        
        // ルートのコンテナ
        StackPane root = new StackPane();
        
        // シーンの生成
        Scene scene = new Scene(root, 300, 250);

        // シーンをステージに貼る
        stage.setScene(scene);
        
        // ラベル
        Label label = new Label("Hello, World!");

        // コンテナにラベルを貼る
        root.getChildren().add(label);
        
        // ステージの表示
        stage.show();
    }

    public static void main(String[] args) {
        // JavaFX のスレッドを起動
        launch(args);
    }
}

JavaFX のアプリケーションは javafx.application.Application クラスのサブクラスとして定義します。

Application クラスは、かつての Applet と同じようなクラスです。

Applet オブジェクトはそのライフサイクルを外側からコントロールされていて、起動時には start メソッド、終了時には stop メソッドがコールされます。

Application オブジェクトも同じようにライフサイクルを管理されており、起動時には start メソッド、終了時には stop メソッドがコールされます。

start メソッドだけは abstract メソッドになっているので、サブクラスでオーバーライドする必要があります。

ようするに、start メソッドの中で GUI を構築しろということですね。

Application オブジェクトのライフサイクルは外側から管理されると書きましたが、その外側の人を作る必要があります。それが、main メソッドの launch メソッドです。

launch メソッドの中で GUI を描画するスレッドを生成し、その後、GUI 用のスレッドから start メソッドをコールするようになっています。

    public static void main(String[] args) {
        // JavaFX のスレッドを起動
        launch(args);
    }

さて、start メソッドに戻りましょう。

前述したように start メソッドでは GUI の構築を行います。

Swing でもそうでしたが、JavaFX でも GUI の構成というのはあらかた決まっています。ウィンドウに相当するのが javafx.stage.Stage クラスです。

そこからのクラスの関連は次のようになります。

    [Stage] - [Scene] - [Root Container] - [GUI のコンポーネントなど]

Stage オブジェクトに貼るのが javafx.scene.Scene オブジェクトです。Scene クラスは Swing でいうところの JRootPane クラスのような役割です。

そして、Scene にすべての描画要素のルートとなるコンテナを貼ります。Swing でいうところの ContentPane のようなものです。

Swing ではコンテナとレイアウトは別々のクラスとして表現されていましたが、JavaFX ではコンテナはレイアウトを含んでいます。

ここで使用した javafx.scene.layout.StackPane クラスはスタック上に描画要素を積み重ねてレイアウトするコンテナです。

        // ルートのコンテナ
        StackPane root = new StackPane();
        
        // シーンの生成
        Scene scene = new Scene(root, 300, 250);

        // シーンをステージに貼る
        stage.setScene(scene);

後は自由に GUI の描画要素をルートコンテナに貼っていきます。

ここでは Hello, World! という文字列を表示するので、javafx.scene.control.Label クラスを使用しました。

Swing ではコンポーネントと呼ばれていたものは、JavaFX ではコントロールと呼び、javafx.scene.control パッケージで定義されています。

        // ラベル
        Label label = new Label("Hello, World!");

        // コンテナにラベルを貼る
        root.getChildren().add(label);

ルートコンテナに貼る時に、わざわざ getChildren をコールしてから add しているのは、そういうものだと思ってください。これについても、いつか説明したいと思います。

最後にステージを表示するため stage.show メソッドをコールします。

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

f:id:skrb:20120210094700p:image

ビルダー

ここまではクラス名などはことなるものの、Swing のアプリケーションと大差ありません。逆にいえば、JavaFX という新しい技術を導入しても、結局は旧態依然とした書き方しかできないということになります。

詳しくは次回以降に説明しますが、GUI の構造はツリー構造で表すことができます。

でも、Java でツリー構造を書くと、そのツリーがプログラムからはとても読み取りにくいのです。まぁ、当たり前といえば当たり前なのですが...

Swing や上の JavaFX の書き方というのは、ツリー構造をそのままベタに書いているわけです。当然読みにくくなります。

そこで、JavaFX ではツリーをベタに書く以外に 2 種類の方法でツリーを書くことができるようになっています。

  • ビルダー
  • FXML

ビルダーというのはいわゆる流れるようなインタフェース (Fluent Interface) を採用したファクトリのクラスです。このビルダを使用すると、JavaFX 1.x の頃の JavaFX Script にちょっとだけ近い書き方ができます。

もう 1 つの FXML は XML です。ようするに、XMLGUI の構造を書いてしまおうということです。

XML はツリー構造を表すのがXMLGUI の構造を書くというのは、Adobe Flash や Microsoft の XAMLAndroid などで使われています。

まずは、ビルダーからいきましょう。

ビルダーは static メソッドの create でビルダーを生成して、そこにプロパティを設定し、最後に build メソッドで、ターゲットとしているオブジェクトを作ります。

たとえば、X を作る XBuilder があって、X には num と text というプロパティがあったとします。このとき X オブジェクトを作るには次のようになります。

    X x = XBuilder.create()
                  .num(100)
                  .text("Foo Bar!")
                  .build();

さて、ビルダーを使って、先ほどの Hello, World! を作ってみましょう。

    @Override
    public void start(Stage stage) {
        // ステージのタイトルを設定
        stage.setTitle("Hello World!");
     
        // ビルダーを使ってツリーを生成
        stage.setScene(
            SceneBuilder.create()
                .root(
                    StackPaneBuilder.create()
                        .children(
                            LabelBuilder.create()
                                        .text("Hello, World!")
                                        .build()
                        )
                        .build()
                )
                .width(300).height(250)
                .build()
            );
        
        // ステージの表示
        stage.show();
    }

この書き方だと、Stage が Scene を持っていて、Scene が StackPane を持ち、StackPane が Label を持っているという関係性が分かりやすくなります。

でも、その分、書き方はちょっと冗長かもしれません。

シンプルな GUI であればいいのですが、すべてをこの形式で書くと帰ってわかりにくいかもしれないですね。

なお、このビルダーの考え方をより洗練させたのが、GroovyFX です。Groovy ではビルダーを DSL としてよく使用するのですが、それを JavaFX に適用させたライブラリです。

JavaFX 1.x のころの JavaFX Script を使っていた方には GroovyFX が一番違和感がないかもしれません。

FXML

最後の FXML が本命です。

とりあえず、FXML の例を見てもらいましょう。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane>
    <children>
        <Label text="Hello, World!" />
    </children>
</StackPane>

もう一目瞭然ですね。StackPane の子供 (children) として Label があるということがすぐに分かります。

クラスのプロパティは Label の text のように XML の属性として記述してもかまいませんし、StackPane の children のように子要素として記述してもかまいませんん。

驚くことに FXML にはスキーマがありません。要素はそのままクラス名を表しています。クラスを使用できるようにするには import を書く必要があります。

任意のクラスを記述できるので、スキーマを設定できないというのがほんとのところのような気がします。

ただし、今回は使っていないのですが、Java のアプリケーションのやりとりを行うための要素があり、その部分のスキーマは決まっています。

さて、先走りして FXML を出してしまいましたが、NetBeans で FXML を扱ってみましょう。

前々回、NetBeans で JavaFX のカテゴリには 3 つのアプリケーションの種類があるといいました。もう一度出すと、

  • JavaFX アプリ−ケーション
  • JavaFX プレローダー
  • JavaFX FXML アプリケーション

FXML を使用する場合は一番最後の [JavaFX FXML アプリケーション] を使うのですが、これが現状はとてもできが悪い ><

ということで、今回は[JavaFX アプリケーション] を使用して FXML を追加していきます。

今回は使用しませんが、今後 NB の JavaFX Plug-in がアップデートされれば [JavaFX FXML アプリケーション] も使えるようになるかもしれません。

ということで、まずはプロジェクトを作るところから。

メニューバーの [ファイル] -> [新規プロジェクト] を選択します。プロジェクトのカテゴリは [JavaFX]、そして前述したように [JavaFX アプリケーション] を選びます。プロジェクト名などは適当に決めてください。ここでは hello にしました。

まずは、メインクラスから作成します。

public class Hello extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        stage.setTitle("Hello World!");

        // FXML ファイルをロードして、GUI を構築する
        StackPane root = FXMLLoader.load(getClass().getResource("HelloView.fxml"));
        
        stage.setScene(new Scene(root, 300, 250));
        stage.show();
    }

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

javafx.fxml.FXMLoader クラスが FXML ファイルをロードするクラスです。load メソッドの引数は URL なので、クラスローダーから URL を取得しています。

こうやって FXML ロードしているということは、Adobe Flash の MXML などと扱いが違うということが分かります。

Flash では MXML はコンパイルして ActionScript に変換します。Java の JSP みたいな感じですね。これに対し、FXML はコンパイルせずに JavaFX の実行時にパースします。

では、FXML ファイルを作成します。

プロジェクトもしくはパッケージを右クリックして、ポップアップメニューから [新規] を選択します。そして、サブメニューの中から [その他] を選択します。

f:id:skrb:20120211173240p:image



表示されたダイアログの中から、カテゴリ [JavaFX]、ファイルの種類 [FXML テンプレート] を選択して、[次へ] をクリックします。

f:id:skrb:20120211173509p:image



続いて、ファイル名を入力します (クラス名となっているのはご愛敬 ^ ^;;)。

FXML ファイルとメインクラスの名前が同じだと、警告が出るので違う名前にします。ここでは、Hello クラスに記述したように HelloView にしましょう。

f:id:skrb:20120211173802p:image



HelloView.fxml ファイルを作成すると、同時に HelloView.java ファイルも一緒に作成してしまいます。先ほど、メインクラスと同じファイル名にしようとして警告が出たのは、FXML ファイルと同じ Java のファイルを生成してしまうからでした。

MS の blend が XAML ファイルを作成すると、自動的に同じファイル名の C# のファイルを作るのと同じような理由です。

f:id:skrb:20120211174142p:image


この FXML ファイルと同じ名前の Java のクラスは、FXML ファイルに記述したコントロールなどを Java のプログラム中で扱いたい場合や、イベント処理などに使用します。

でも、今回は使いません。FXML についてのもう少し詳しい解説は、また別途おこないたいと思います。

FXML ファイルにはテンプレートというか、ボタンとラベルが並んでいるような FXML が記述されているのですが、それをすべて消して、上述した FXML に書き換えてください。

これで完成です。

実行すると、Java ですべて書いたのとまったく同じステージが表示されます。