JavaFX ListViewとSelectionModel

ListViewを利用するには、データをどのように扱うかを理解しておく必要がある。ここではその基本と、選択された項目を管理するSelectionModelについて説明する。

ListViewを作成する

基本的なGUIコントロールの使い方はすでに説明したが、JavaFXにはより複雑なコントロールも用意されている。特に重要なのが「データを扱うコントロール」である。あらかじめ用意したデータをもとに、必要な情報を表示したり操作したりするものだ。

その代表と言えるのが「リスト」である。複数の項目を縦スクロール可能な一覧として表示するGUIで、JavaFXではこれをListViewというコントロールとして提供している。

まずFXMLを使ってListViewを作ってみよう。ListView<ListView>タグを使って作成する。タグ自体はとても簡単だが、これだけでは空のリストになってしまう。中に表示する内容も記述する必要がある。

<ListView>
    <items>
        <FXCollections fx:factory = "データ型">
            ...... データの内容 ......
        </FXCollections>
    </items>
</ListView>

データの内容までFXMLで書くと、このような形になる。ListViewのデータは<items>タグで指定する。その中に<FXCollections>タグを追加し、具体的なデータ内容を記述する。通常はfx:factory属性を指定し、データの種類、つまりFXCollectionsに格納するクラスを指定する。

いろいろな指定ができるが、まずはobservableArrayListを指定しておく。これはオブジェクトを集めたArrayListのサブクラスである。この中に、たとえば<String>タグを使ってテキストを書くと、StringデータのArrayListが作られ、それが表示項目として設定される。

実際の使用例として、複数のテキスト項目を表示するサンプルを示す。

<?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?>
<BorderPane xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.tuyano.libro.AppController">
    <stylesheets>
        <URL value="@app.css" />
    </stylesheets>
    <top>
        <Label fx:id="label1" text="This is FXML!" />
    </top>
    <center>
        <ListView fx:id="list1">
            <items>
                <FXCollections fx:factory="observableArrayList">
                    <String fx:value="Windows" />
                    <String fx:value="Mac OS" />
                    <String fx:value="Linux" />
                </FXCollections>
            </items>
        </ListView>
    </center>
    <bottom>
        <Button text="Click" fx:id="btn1" />
    </bottom>
</BorderPane>

<ListView><items>タグに、次のような形でデータを用意している。

<FXCollections fx:factory="observableArrayList">

そして、その中に<String>タグを使って表示するテキストを指定する。単純なテキスト値であれば、これで表示できる。

JavaコードでListViewの表示内容を作成する

続いて、FXMLではなくJavaソースコード内でListViewの表示内容を作成する方法を見ていこう。

ListViewに表示される項目は、itemsというプロパティで管理されている。これは表示されるすべての項目をまとめて管理するもので、コレクションフレームワークのインスタンスが設定されている。

このitemsの値はgetItemsで取得したり、setItemsで変更したりできる。また、itemsインスタンスに格納されている各項目のデータは、そのコレクションクラスのメソッドを使って管理できる。

実際の使用例は次のとおりである。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
    }
 
}

まず、前のFXMLの<ListView>タグを次のように変更しよう。

<ListView fx:id="list1"></ListView>

これでFXMLには項目が何も追加されていないが、実行すると"One"、“Two”、“Three"という項目がリストに表示されることを確認できる。

ここではInitializableインターフェースを利用し、initializeメソッドで初期化処理を行っている。

list1.setItems(FXCollections.observableArrayList());

FXCollections.observableArrayListを使ってコレクションクラスのインスタンスを作成し、それをsetItemsで設定する。その後は、itemsインスタンスにaddで表示するテキストを追加していくだけである。

list1.getItems().add("One");
list1.getItems().add("Two");
list1.getItems().add ("Three");

使い方さえわかれば、リストの作成はそれほど難しくないことがわかる。

ListViewのクリックイベント処理

ListViewに表示された項目をクリックして何らかの処理を実行したい場合は、どうすればよいだろうか。ListViewにはアクションイベントが用意されていない。そのため、クリック時に何かを行うには別のイベントを用意する必要がある。

ここではMouseClickというイベントを利用してみよう。名前のとおり、マウスでクリックしたときに発生するイベントである。このイベントはsetOnMouseClickedメソッドで設定できる。

listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        // 実行する処理
    }
});

このように書ける。ただしJava 8では、このようなイベント処理はラムダ式で書くのが基本なので、通常は次のように書くだろう。

listView.setOnMouseClicked ((MouseEvent) -> {
    // 実行する処理
}

こちらのほうがずっと簡単である。次は、リスト項目をクリックすると、そのテキストをLabel1に表示する例である。setOnMouseClickedを使えば、リスト項目のクリック処理を簡単に追加できる。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView<String> list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.setOnMouseClicked((MouseEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText(obj.toString());
        });
 
        btn1.setOnAction((AtionEvent)->{
            String obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj + "\".");
        });
    }
 
}

ListViewで項目が選択されたときのイベントを処理するには、実はMouseClickは最適ではない。後で説明するChangeListenerを使うほうがよい。

SelectionModel

前の例では、setOnMouseClickで選択された項目のテキストを取り出すために、次のような処理が必要だった。

String obj = list1.getSelectionModel().getSelectedItem();

getSelectionModelを呼び出し、返されたインスタンスのgetSelectedItemメソッドを呼び出している。これで選択された項目のオブジェクトを取得できる。何をしているのか理解するには、ListViewSelectionModelを理解する必要がある。

SelectionModelとは、選択された項目を管理するモデルクラスである。JavaFXでは、さまざまなデータを管理するために「モデル」という概念を導入している。モデルは動的に操作されるデータを管理するためのものだ。

選択された項目はSelectionModelというモデルクラスで管理される。ListViewgetSelectionModelメソッドを呼び出すことで、そのListViewに含まれるSelectionModelインスタンスを取得できる。

SelectionModelクラスには、選択項目に関するさまざまなメソッドが用意されている。ここではgetSelectedItemメソッドを使っている。これは選択された項目のインスタンスを返すメソッドである。これにより項目のオブジェクトを取得し、そこから表示テキストなどを取り出せる。

保存する値とジェネリック型

ListViewはジェネリック型をサポートしている。先ほどのサンプルコードを見ると、ListViewの値を保存するフィールドは次のようになっている。

@FXML ListView<String> list1;

このように<String>を追加することで、保存する値をStringに限定できる。getSelectedItemで取得した値を直接String変数に代入できたのも、ジェネリック型を使っていたためである。

SelectionModelにChangeListenerを設定する

このSelectionModelには、プロパティの変更に対応したイベント処理機能がある。たとえばSelectionModelにはSelectedItemというプロパティがあり、選択された項目はgetSelectedItemメソッドで取り出せた。

このSelectedItemプロパティにChangeListenerというイベントリスナーを設定することで、値が変更されたときのイベントを検知して処理できる。ChangeListenerは名前のとおり、値が変更されたときに発生するイベントを処理するリスナーである。

これは、SelectionModelselectedItemPropertyメソッドで取得したReadOnlyObjectPropertyクラスのaddListenerメソッドで設定できる。基本形は次のとおりである。

selectionModel.selectedItemProperty().addListener(new ChangeListener() {

    @Override
    public void changed(ObservableValue observable, Object oldVal, Object newVal) {
        // 実行する処理
    }
});

ChangeListenerにはchangedというメソッドが1つだけ含まれている。このメソッドには、ObservableValueのインスタンス、変更前の値、変更後の値が引数として渡される。

「メソッドが1つだけ」という話を覚えている人もいるだろう。Java 8では、メソッドが1つだけのインターフェースはラムダ式で置き換えられる。addListenerによるイベントリスナー設定をラムダ式にすると、次のようになる。

selectionModel.selectedItemProperty().addListener (
    (ObservableValue observable, Object oldVal, Object newVal) -> {
        // 実行する処理
    }
);

こちらのほうがわかりやすい。このようにChangeListenerを設定すれば、値が変更されたとき、つまりSelectedItemの値が変わったとき、言い換えれば選択状態が変わったときの処理を行える。

簡単な使用例を作ってみよう。

package com.devkuma.javafx;
 
import java.net.URL;
import java.util.ResourceBundle;
 
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
 
 
public class AppController implements Initializable {
    @FXML Label label1;
    @FXML ListView list1;
    @FXML Button btn1;
     
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        list1.setItems(FXCollections.observableArrayList());
        list1.getItems().add("One");
        list1.getItems().add("Two");
        list1.getItems().add("Three");
         
        list1.getSelectionModel().selectedItemProperty().addListener(
            (ObservableValue observable, Object oldVal, Object newVal) -> {
                label1.setText(oldVal + " -> " + newVal);
            }
        );
         
        btn1.setOnAction((AtionEvent)->{
            Object obj = list1.getSelectionModel().getSelectedItem();
            label1.setText("you selected: \"" + obj.toString() + "\".");
        });
    }
 
}

リストの項目を選択すると、「以前の値 -> 新しい値」という形式で値の変化がlabel1に表示される。MouseClickを使うよりスマートではないだろうか。MouseClickはキーボード操作などで選択項目を変更した場合にはイベントが発生しないが、ChangeListenerを使えば、どのような形で選択状態が変わってもすぐにイベントが発生し、それに応じた処理を実行できる。