JavaFXプロパティイベント処理

JavaFXのコントロールには、値を管理するプロパティ専用のクラスが用意されており、そこにイベントリスナーを設定して値の変更時に処理を行える。ここではToggleGroupComboBoxSliderに対するプロパティのイベント処理方法を説明する。

ToggleGroupのChangeListener処理

以前、ListViewで選択状態が変更されたときのイベント処理を作成した。これは選択状態を管理するプロパティにイベントリスナーを設定するものだった。

このような「プロパティが変更されたときにChangeListenerでイベント処理を行う」という方式は、JavaFXコントロールにおける基本的なイベント処理方法である。この基本概念がわかれば、他のコントロールでも同じような形でイベント処理を設定できる。

まず「ラジオボタンを操作したときのイベント処理」について考えてみよう。ラジオボタンは、ToggleGroupというグループ管理クラスを使って複数のラジオボタンをまとめて管理していた。ラジオボタンが選択されると、このToggleGroupの選択状態を表すプロパティ値が変更される。

このプロパティはselectedTogglePropertyというもので、ReadOnlyObjectPropertyクラスのインスタンスが設定されている。これにaddListenerChangeListenerを設定することで、選択が変更されたときの処理を行える。

それでは簡単なサンプルを作ってみよう。

<?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.*?>
<?import javafx.collections.FXCollections?>
<?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>
        <VBox>
            <fx:define>
                <ToggleGroup fx:id="group1" />
            </fx:define>
            <RadioButton text="Male" toggleGroup="$group1" userData="남자" selected="true" />
            <RadioButton text="Female" toggleGroup="$group1" userData="여자"/>
        </VBox>
    </center>
    <bottom>
    </bottom>
</BorderPane>

まずFXMLでラジオボタンを用意する。上のように、ToggleGroupを共有する2つのラジオボタンがある。ここではuserDataという属性が含まれている。これはラジオボタンに独自のデータを持たせるためのもので、後で使うために記述しておく。それ以外は特に特殊なことをしていない通常のラジオボタンである。

ToggleGroupにChangeListenerを設定する

それでは、作成したFXMLを読み込み、ToggleGroupのイベント処理を含むコントローラクラスを作ってみよう。

次に簡単なサンプルコードを示す。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ToggleGroup group1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        group1.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> 
            observ, Toggle oldVal, Toggle newVal)->{
            String oldStr = (String)oldVal.getUserData();
            String newStr = (String)newVal.getUserData();
            label1.setText(oldStr + "->" + newStr);
        });
    }
 
}

実行してラジオボタンをクリックしてみよう。するとウィンドウ上部のラベルに「남자 -> 여자」のようなテキストが表示される。選択前と選択後、それぞれのRadioButtonに設定されたuserDataを表示していることがわかる。

ここではToggleGroupselectedTogglePropertyに設定されたReadOnlyObjectPropertyに、次のような形でリスナーを設定している。

ReadOnlyObjectProperty.addListener((ObservableValue<? extends Toggle> observ,
    Toggle oldVal, Toggle newVal) -> {
    // 実行する処理
});

ここではラムダ式を使った設定方法を示している。ChangeListenerにはchangedというメソッドが1つだけ定義されている。これは次のような形をしている。

void changed(ObservableValue<? extends T> observable, T oldValue, T newValue)

ObservableValueにはジェネリック型を設定できる。ToggleGroupselectedTogglePropertyに含まれる場合、ObservableValueToggleクラスを継承する形で渡される。そのほか、oldValuenewValueToggleインスタンスとして渡される。

このToggleというクラス、正確にはインスタンスは、ON/OFFとして扱われる値を管理するためのクラスである。このToggleから変更された値に関する情報を取得する。ここではgetUserDataメソッドを使っている。これにより、そのToggleに設定されているUserDataプロパティの値を取り出す。これはFXMLに追加されているuserData属性の値である。

ReadOnlyObjectPropertyObservableValueToggleなど、見慣れないクラスがいくつか登場して理解しづらいかもしれないが、基本的なプロパティ変更イベントリスナー処理はどれも似た形になっている。このイベント処理に慣れれば、どのコントロールでも同じように扱える。

ComboBoxのSelectionModelイベント処理

続いてComboBoxについて説明する。これは実はListViewがわかっていれば、ほぼ同じ方法で処理できる。

ComboBoxにも選択状態を管理するモデルクラスが設定されたプロパティがあり、これはgetSelectionModelで選択モデルクラスを取得できる。

そして、そのselectedItemPropertyから得られるプロパティにaddListenerでイベントリスナーを設定すれば、選択変更時の処理を設定できる。

これも見てみよう。以前のFXMLにある<center>タグの中に、次のように記述する。

<ComboBox fx:id="combo1">
<items>
    <FXCollections fx:factory="observableArrayList">
        <String fx:value="One" />
        <String fx:value="Two" />
        <String fx:value="Three" />
    </FXCollections>
</items>
</ComboBox>

これでString値を項目として持つComboBoxが用意された。あとは、このイベント処理をコントローラクラスで用意するだけである。

次のようにソースコードを書いてみよう。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ComboBox<String> combo1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        combo1.getSelectionModel().selectedItemProperty().
            addListener((ObservableValue<? extends String> observ,
                    String oldVal, String newVal)->{
            label1.setText(oldVal + "->" + newVal);
        });
    }
}

ここではComboBoxにジェネリック型としてStringを指定している。次の形式である。

@FXML ComboBox<String> combo1;

これにより、String値を項目として持つ設定になる。イベントリスナーは次のように設定する。

combo1.getSelectionModel().selectedItemProperty().addListener ......

getSelectionModelで取得した選択モデルのselectedItemPropertyaddListenerを設定する。少し複雑である。イベントリスナー設定は次のような形で定義される。

addListener((ObservableValue<? extends String> observ, String oldVal, String newVal) -> {
    // イベント処理
});

最初の引数はObservableValue<? extends String> observという形で定義されている。ComboBoxの定義にも<String>が指定されているため、ObservableValueextends Stringとして定義される。続いて、変更前と変更後の値がそれぞれStringとして渡される。

ObservableValueが「格納される値をextendsする形で書かれる」というのは、かなり不思議に感じるかもしれない。実際には、ObservableValueスーパークラスの設計によってそう決まっているためである。少し理解しづらいが、「選択モデルに設定されたクラスを継承する形でObservableValueが用意される」と覚えておこう。

SliderのvaluePropertyをイベント処理する

スライダーも、値が変更されたときのイベントを同じような方法で処理できる。Sliderクラスで設定されている値は、valuePropertyというプロパティで処理される。

これはDoublePropertyというクラスのインスタンスとして管理されている。このDoublePropertyaddListenerでイベントリスナーを設定すれば、値が変更されたときの処理を実行できる。

これも簡単なサンプルを作って使ってみよう。以前のFXMLで、<center>タグを次のように書く。

<center>
    <Slider fx:id="slider1" min="0" max="100"/>
</center>

これでスライダーが1つ表示される。ここにイベント処理を設定し、操作するとリアルタイムで表示が更新されるようにしよう。

次のような簡単な例を作成する。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML Slider slider1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
 
        slider1.valueProperty().addListener((ObservableValue<? extends Number> 
                observ, Number oldVal, Number newVal)->{
            double oldnum = oldVal.doubleValue();
            double newnum = newVal.doubleValue();
            label1.setText(oldnum + "->" + newnum);
        });
    }
}

これは変更前と変更後の値を取得して表示するサンプルである。スライダーを操作すると、「12.34567 -> 98.7654」のように、以前の値と新しい値が表示される。

ここではスライダーにイベントリスナーを次のような形で設定している。

slider1.valueProperty().addListener ......

SliderクラスのvaluePropertyから取得したDoublePropertyインスタンスのaddListenerにイベントリスナーを設定する。これは次のような形になっている。

addListener((ObservableValue <? extends Number> observ, Number oldVal, Number newVal) -> {
    // イベント処理
});

ObservableValueNumberのサブクラスとして定義されている。そして渡される値はNumberインスタンスである。あとは渡されたNumberからメソッドを呼び出し、必要な値を取得するだけである。

double oldnum = oldVal.doubleValue();
double newnum = newVal.doubleValue();

ここではdoubleValuedouble値として取得して表示している。整数にしたい場合はintValueメソッドを使えばよい。

イベントリスナーとObservableValueの関係は、これでかなり理解できたはずである。ObservableValueをうまく扱えるようになれば、プロパティのイベント処理はおおむね習得できたと考えてよいだろう。