JavaFX FXMLアクションイベントの実装

JavaFXでは、さまざまな方法でイベント処理を実装できる。ここではその基本として、「タグによる実装」と「コントローラによる実装」について説明する。

<fx:script>でイベント処理を作成する

FXMLではXMLベースでGUIを作成できる。しかし実際に作るとき、「これでイベント処理はどう作るのだろう」と思う人もいるかもしれない。XMLはJavaから分離された形でGUIを作るものなので、それをJavaで実行されるコードに接続する仕組みが必要になる。

方法はいくつかある。まずはもっとも簡単な<fx:script>タグを使った実装から試してみよう。

FXMLには、実はイベント処理コードまで書くことができる。これは次のような形になる。

<fx:script>
function 関数(引数) {
    // 処理を記述
}
</fx script>

このような形で、実行する処理を関数として定義する。そして、この関数を使用するコントロールに属性として指定する。アクションイベントなら次のように書く。

onAction="関数(引数);"

このようにコントロールのタグに属性を記述すると、そのコントロールでアクションイベントが発生したときに、指定した関数が呼び出される。

「ところでfunctionはJavaでは見つからない」と思うかもしれない。実はこれはJavaではなくJavaScriptの関数である。つまりFXMLを使うと、Javaを使わずにJavaScriptでイベント処理を書ける。少しの処理を素早く実装したい場合には、とても便利な方法である。

<fx:script>でアクションを実装する

それでは実際に、<fx:script>タグを使ったアクションイベント処理を作ってみよう。以前使ったプロジェクトをそのまま使い、記述を変更する形で試す。

FXMLファイルapp.fxmlを開き、次のようにソースコードを書き換える。他のもの、たとえばJavaソースコードなどは一切変更する必要がない。

<?xml version="1.0" encoding="UTF-8"?>
 
<?language javascript?>
<?import java.lang.*?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
 
<BorderPane xmlns="http://javafx.com/javafx"
        xmlns:fx="http://javafx.com/fxml">
    <fx:script>
    function doAction(event){
        var str = field1.getText();
        str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
        label1.setText(str);
    }
    </fx:script>
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <TextField fx:id="field1" />
    </center>
    <bottom>
        <Button onAction="doAction(event);" text="Click" />
    </bottom>
</BorderPane>

では実行してみよう。

この例では、ButtonをクリックするとTextFieldに入力されたテキストを取り出し、Labelに簡単なメッセージを表示する。とても単純だが、アクション実装の基本はわかるだろう。

ここにはいくつかのポイントがある。順番に整理してみる。

<?language javascript?>

最初にこのようなタグが追加されている。これは<fx:script>で記述するコードがJavaScriptであることを指定するものだ。「では他の言語も使えるのか」と聞かれれば、もちろん使える。

GroovyやClojureなど、Java仮想マシン上で動作するスクリプト言語として実装されているものがあれば、それを指定することもできる。ただしJava 8に標準で内蔵されているのはJavaScriptだけなので、まずはこれを使うと考えよう。

<fx:script>

<BorderPane>タグの中に<fx:script>タグが含まれている。FXMLではPaneタグがルートタグとして書かれるため、<fx:script>タグは必ずその中に含まれていなければならない。この<fx:script>タグには、ごく普通のJavaScriptスクリプトが書かれている。

注目してほしいのは、ここで使われているオブジェクトである。label1text1というオブジェクトが使われているが、これらは後で書かれている<Label><TextField>fx:id名であることがわかる。

<fx:script>に書かれたスクリプト内では、コントロールはfx:idで指定された名前の変数としてアクセスできる。そのため、使用するコントロールには必ずfx:idを付けておく必要がある。この点さえ守れば、スクリプト内で自由にコントロールを利用できる。

getTextとsetText

スクリプト内ではgetTextでテキストを取得し、setTextで設定している。これはすでに見たことのあるメソッドである。Javaソースコード内でコントロールのインスタンスを使うときに利用していたものだ。

スクリプトで使われるコントロールのオブジェクトもJavaインスタンスと同じメソッドを持っているため、同じ感覚で呼び出せる。新しく覚える必要はほとんどない。

スクリプト外部ファイルについて

ここでは<fx:script>タグの中にスクリプトを書いたが、スクリプトが長くなる場合は別ファイルに分離したほうがよい。その場合はsource属性を指定する。たとえば次のように書く。

<fx:script source="script.js"/>

この形で書くと、FXMLファイルと同じ場所にあるscript.jsをロードして利用できる。実行するスクリプトが長くなったら、sourceを使って別ファイルに分けてみよう。

FXMLでコントローラを使う

JavaScriptは手軽で便利だが、具体的な処理はJavaで書きたいという人も当然多い。そのような場合は、Javaクラスに処理を定義しておき、それをFXMLのコントロールに割り当てられる。

このような「具体的なイベント処理を行うクラス」は、一般にコントローラと呼ばれる。コントローラクラスは基本的にPOJO、つまり何かを継承しない単純なクラスとして定義する。

コントローラは、FXMLのPaneクラスにfx:controllerという属性で指定する。すると、そのクラスがコントローラとして構成され、そのクラスにあるメソッドをdoActionなどのイベント処理属性にそのまま指定できるようになる。

ではコントローラを作ってみよう。まずFXMLを次のように書き換える。

<?xml version="1.0" encoding="UTF-8"?>
 
<?language javascript?>
<?import java.lang.*?>
<?import java.net.URL ?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
 
<BorderPane xmlns="http://javafx.com/javafx"
        xmlns:fx="http://javafx.com/fxml"
        fx:controller="com.devkuma.javafx.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <TextField fx:id="field1" />
    </center>
    <bottom>
        <Button fx:id="btn1" onAction="#doAction" text="Click" />
    </bottom>
</BorderPane>

今回はAppControllerというコントローラクラスを作って使用する形になっている。Paneタグを見ると、次のように書かれている。

<BorderPane xmlns="http://javafx.com/javafx" 
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.devkuma.javafx.AppController">

fx:controller="com.devkuma.javafx.AppController"によって、このAppControllerクラスがコントローラとして指定される。

また、<Button>タグを見ると、onAction属性が少し変更されていることがわかる。

<Button onAction="#doAction" text="Click" />

コントローラのメソッドは#メソッド名の形で指定する。すると、AppControllerクラスのdoActionメソッドが、このButtonのアクションイベント処理として設定される。

コントローラを実装する

ではコントローラクラスを作成しよう。今回はcom.devkuma.javafxパッケージにAppController.javaというファイル名でファイルを作成し、次のソースコードを書く。

package com.devkuma.javafx;
 
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
 
public class AppController {
    @FXML Label label1;
    @FXML TextField field1;
     
    @FXML
    protected void doAction(ActionEvent ev){
        String str = field1.getText();
        str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
        label1.setText(str);
    }
}

これで、先ほどの<fx:script>タグを使った場合と同じ効果を得られる。

ソースコードの主なポイントを見ていこう。

@FXML Label label1;
@FXML TextField field1;

クラスにはLabelTextFieldがインスタンスフィールドとして使われている。しかし、これらの変数にインスタンスを代入する処理はどこにも見当たらない。これは@FXMLというアノテーションを付けることで、FXML内の同じ名前のfx:idを持つインスタンスがバインドされるためである。

@FXML
protected void doAction (ActionEvent ev) {...}

アクションメソッドも同じく@FXMLアノテーションによってバインドされる。メソッドはprotectedアクセスにする必要があるので注意しよう。また、引数にはEventクラスのインスタンスを受け取る。アクションイベントであればActionEventインスタンスが渡される。

これ以外に特に注意すべき点はない。@FXMLを指定したフィールドにインスタンスが割り当てられるため、Javaコードでそれらを操作する処理を普通に書けばよい。これでJavaで処理を書けるようになった。

setOnActionでアクションイベントを実装する

コントローラを使ったアクションイベントの実装はとても簡単だが、FXML側にonActionを書いておく必要がある。AWTやSwingでは、Javaコードでイベント処理を実装するのが基本だった。

以前、FXMLを使わずにApplicationクラスですべての処理を書いた場合には、setOnActionメソッドでアクションイベントを登録できた。FXMLとコントローラを使う場合も、この点は同じである。ただし、setOnActionを行うのがApplicationクラスではなくコントローラ側になる点が異なる。

では、この方式でも実装してみよう。まずFXMLの<Button>タグからonAction属性を削除し、コントローラクラスのソースコードを次のように変更する。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML TextField field1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        btn1.setOnAction((AtionEvent)->{
            String str = field1.getText();
            str = "당신은 쓴 글은 '" + str + "' 이것입니다.";
            label1.setText(str);
        });
    }
 
}

これを実行してボタンをクリックすると、処理が正しく実行されることを確認できる。

この例では、以前のコントローラとは少しクラス定義が変化していることがわかる。javafx.fxmlInitializableインターフェースをimplementsしている。これはFXMLの初期化処理に関する機能を追加するためのものである。

クラスにはinitializeメソッドが追加されている。ここにFXML関連の初期化処理を書く。このメソッドが呼び出される時点では、@FXMLアノテーションが付いたインスタンスフィールドは生成されて割り当てられているため、そのままButtonsetOnActionを呼び出してアクションイベントを割り当てられる。

これでFXMLベースでアクションイベントを実装できるようになった。実際にいろいろな処理を動かして、動作を確認してみよう。