JavaFXアクションイベントの使用

まず、もっとも基本的なGUIである入力フィールドとプッシュボタンを使い、ボタンクリック時のイベント処理、つまりアクションイベントについて説明する。また、Java 8のラムダ式を使った実装についても説明する。

TextFieldとButton

GUIの基本といえば、やはりユーザー入力とコマンド実行である。これらについて、一般的なアプリケーションでもっとも身近なものを挙げるなら、入力フィールドとプッシュボタンがある。今回はこれを利用してみよう。

TextFieldについて

まず入力フィールドである。もっともよく使われるのは、1行のテキスト入力用のものだ。これはjavafx.scene.controlパッケージのTextFieldというクラスになっている。このクラスは次のようにインスタンスを生成する。

new TextField()
new TextField(初期値)

引数なしでnewするのが基本である。String引数を指定すると、そのテキストを初期値としてフィールドに設定してくれる。入力されたテキストは次のように取得したり変更したりできる。

テキストの取得

String 変数 = textField.getText();

テキストの変更

textField.setText(テキスト);

このほかにTextFieldでは「プロンプトテキスト(Prompt Text)」も使用できる。このフィールドに何も書かれておらず、選択もされていないときに、灰色でメッセージを表示するものである。入力フィールドなどで「名前を入力」「住所を入力」のようなガイドになるテキストが薄く表示されているのを見たことがあるだろう。それと同じである。

プロンプトテキストの設定

textField.setPromptText(テキスト);

Buttonについて

プッシュボタンは、javafx.scene.controlパッケージのButtonというクラスになっている。これも次のようにインスタンスを生成できる。

new Button()
new Button(表示テキスト)

引数にテキストとしてStringを指定してnewで生成すると、そのテキストをボタンに表示する。プッシュボタンを使う場合は、「送信」や「クリック」のようにボタンにテキストを表示するのが一般的なので、このテキストを入力するのが基本と考えてよい。また、TextFieldと同じようにgetText/setTextで表示テキストを操作することも可能なので、new Button()でボタンを生成し、後から表示テキストを設定することもできる。

次のソースコード例は、LabelTextFieldButtonといったGUIを使うアプリケーションである。それぞれインスタンスを生成した後、BorderPaneに入れて表示している。

package com.devkuma.javafx;
 
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

setOnActionとEventHandler

プッシュボタン、つまりButtonは、単に表示するためのものではなく、クリックして何らかの処理を実行するために使用する。この処理は、Buttonに含まれている「イベント」を利用して実行する。

イベントとは、さまざまな操作をしてプログラムの状態が変化したときに発生する信号のようなものである。それぞれのコントロール、たとえばTextFieldやButtonなどには、どのイベントに対応するかが決まっており、そのイベント処理のための機能を実行するメソッドが含まれている。

Buttonコントロールをクリックしたときのイベントは「アクションイベント」と呼ばれる。これは、そのコントロールがもっとも一般的な形で利用される場合に処理するためのイベントである。たとえばプッシュボタンなら、「クリック」という動作がもっとも基本的な操作だろう。このような操作をしたときにアクションイベントが発生する。

アクションイベントは、Buttonに含まれている次のようなメソッドを使って設定できる。

button.setOnAction("EventHandler");

発生したイベントの処理には、javafx.eventパッケージにあるEventHandlerインターフェイスを使用する。

EventHandlerの構造

EventHandlerには、イベントが発生したときに呼び出されるメソッドがあらかじめ用意されている。このメソッドをオーバーライドすることで、そのイベント発生時の処理を実装できる。これは次のような形で使用できる。

public void handle (Event e) {
    // ここに処理を用意する
}

使用時には、EventHandlerをimplementsしたクラスを用意するか、匿名クラスとしてnewしてインスタンスを生成し、それを引数としてメソッドに渡す形で実装することになるだろう。

EventHandlerとhandleEvent

EventHandlerは、1つのメソッドだけを含む単純なインターフェイスである。public void handle(Event e)というメソッドを定義するだけで必要な処理を行える。ただし、これをそのまま使うことはほとんどない。

EventHandlerにあるhandleメソッドには、発生したイベントをEventというクラスのインスタンス引数として受け取れるようになっている。このEventはイベントの種類ごとに多くのサブクラスを持っており、EventHandlerではどのイベント用のEventサブクラスが渡されるかをまとめて指定できるようになっている。

たとえば、匿名クラスを利用してEventHandlerを生成する場合は、次のように書く。

new EventHandler() {
    @Override
    public void handle (Event e) {
        // ここに処理コードを書く。
    }
}

アクションイベントでは、EventのサブクラスであるActionEventが引数として渡される。setOnActionは次のように書くのが一般的である。

new EventHandler<ActionEvent>() {
    public void handle (ActionEvent e) {
        // ここに処理コードを書く。
    }
}

一般的な形としてActionEventを指定し、handleメソッドにはActionEventが引数として渡されるように書く。これでアクションイベントを処理するEventHandlerを作れるようになった。

それでは実際に、ButtonEventHandlerを指定したイベント処理の例を作ってみよう。

package com.devkuma.javafx;
 
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        // アクションイベント処理を指定
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                String msg = "you typed: " + field.getText();
                label.setText(msg);
            }
        });
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

表示されたボタンをクリックすると、上部フィールドに入力したテキストを取り出し、メッセージをラベルに表示する例である。setOnActionnew EventHandler<ActionEvent>()を設定していることが分かるだろう。

ラムダ式でもっと簡潔に

これでButtonをクリックしたときのアクションイベントは完成である。しかし、匿名クラスを使った書き方は少し面倒だ。これをもっと簡単に使う方法を説明する。

EventHandlerインターフェイスには、handleというメソッドが1つ含まれている。このような「メソッドが1つしかないインターフェイス」は、Java 8では「関数型インターフェイス」と呼ばれる。これは関数オブジェクト、つまり関数を値として扱うものの代用品のように処理できるようになっている。

それを可能にするのがラムダ式である。Java 8でラムダ式を使ったことがない人も多いだろうから、簡単に整理してみよう。

ラムダ式は、関数型インターフェイスの匿名クラスによるインスタンス実装を非常に簡単に書けるようにした機能である。このsetOnActionの例で見ると、次のように変更できる。

通常の書き方

button.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent e) {
        // ここに処理を書く
    }
});

ラムダ式の書き方

button.setOnAction((ActionEvent e) -> {
    // ここに処理を書く
});

非常にシンプルになることが分かるだろう。ラムダ式ではメソッドを書く必要がない。関数型インターフェイスの場合、呼び出されるメソッドが1つしかないため、そのメソッドが呼び出されると自動的に判断される。

次の例は、前のサンプルをラムダ式に変更して書いたものである。

package com.devkuma.javafx;
 
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
 
public class App extends Application {
    Label label;
    TextField field;
    Button button;
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) throws Exception {
        label = new Label("This is JavaFX!");
        field = new TextField();
        button = new Button("Click");
        // アクションイベント処理を指定
        button.setOnAction((ActionEvent e)-> {
            String msg = "you typed: " + field.getText();
            label.setText(msg);
        });
        BorderPane pane = new BorderPane();
        pane.setTop(label);
        pane.setCenter(field);
        pane.setBottom(button);
        Scene scene = new Scene(pane, 320, 120);
        stage.setScene(scene);
        stage.show();
    }
 
}

イベント処理の実装部分がとても分かりやすくなっているだろう。せっかくJava 8でJavaFXを使うのだから、ラムダ式を使った書き方をぜひ覚えておこう。