ドキュメント



JavaFX: JavaFX UIコンポーネントの操作

13 表ビュー

この章では、表の追加、表へのデータの移入、表内の行の編集など、JavaFXアプリケーション内での表の基本的な操作方法を学習します。

JavaFX SDK APIでは、表形式でデータを表すためのクラスがいくつか用意されています。JavaFXアプリケーションでの表の作成に最も重要なクラスはTableViewTableColumn、およびTableCellです。表にはデータ・モデルを実装し、セル・ファクトリを適用することによってデータを移入できます。

表クラスは、データを列でソートし、必要に応じて列のサイズを変更するビルトインの機能を備えています。

図13-1に、アドレス帳の連絡先情報を表す一般的な表を示します。

図13-1 表のサンプル

アドレス帳
「図13-1 表のサンプル」の説明

表の作成

例13-1のコード・フラグメントでは、3つの列のある空の表を作成し、それをアプリケーション・シーンに追加します。

例13-1 表の追加

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
public class TableViewSample extends Application {
 
    private final TableView table = new TableView();
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(300);
        stage.setHeight(500);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("First Name");
        TableColumn lastNameCol = new TableColumn("Last Name");
        TableColumn emailCol = new TableColumn("Email");
        
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
}

表のコントロールはTableViewクラスをインスタンス化して作成されます。例13-1では、VBoxレイアウト・コンテナに追加されていますが、アプリケーション・シーンに直接追加することもできます。

例13-1では、連絡先の姓名、および電子メール・アドレスの情報をアドレス帳に格納する3つの列を定義します。列はTableColumnクラスを使用して作成されます。

TableViewクラスのgetColumnsメソッドは、事前に作成した列を表に追加します。アプリケーションでは、このメソッドを使用して列を動的に追加または削除できます。

このアプリケーションのコンパイルと実装によって、図13-2に示す出力が得られます。

図13-2 データを含まない表

空の表
「図13-2 データを含まない表」の説明

setVisibleメソッドをコールすると、列の表示を管理できます。たとえば、アプリケーションのロジックでユーザーの電子メール・アドレスを非表示にする必要がある場合、このタスクはemailCol.setVisible(false)で実装できます。

データの構造をさらに複雑に表現する必要がある場合は、ネストされた列を作成できます。

たとえば、アドレス帳の連絡先に電子メール・アカウントが2つあると仮定します。その場合、プライマリ電子メール・アドレスとセカンダリ電子メール・アドレスの2つの列を表示する必要があります。例13-2に示すように、2つのサブ列を作成し、emailColgetColumnsメソッドをコールします。

例13-2 ネストされた列の作成

TableColumn firstEmailCol = new TableColumn("Primary");
TableColumn secondEmailCol = new TableColumn("Secondary");

emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);

これらの行を例13-1に追加し、アプリケーション・コードをコンパイルして実行すると、表が図13-3に示すように表示されます。

図13-3 ネストされた列のある表

ネストされた列のある表
「図13-3 ネストされた列のある表」の説明

表はアプリケーションに追加されますが、データが定義されていないため、標準のキャプション表内のコンテンツなしが表示されます。このキャプションのかわりに、setPlaceholderメソッドを使用すると、空の表にNodeオブジェクトを表示するよう指定できます。

データ・モデルの定義

JavaFXアプリケーションで表を作成する場合は、データ・モデルを定義し、メソッドとフィールドを提供して表を使用できるようにするクラスを実装することをお薦めします。例13-3では、Personクラスを作成してアドレス帳にデータを定義します。

例13-3 Personクラスの作成

public static class Person {
    private final SimpleStringProperty firstName;
    private final SimpleStringProperty lastName;
    private final SimpleStringProperty email;
 
    private Person(String fName, String lName, String email) {
        this.firstName = new SimpleStringProperty(fName);
        this.lastName = new SimpleStringProperty(lName);
        this.email = new SimpleStringProperty(email);
    }
 
    public String getFirstName() {
        return firstName.get();
    }
    public void setFirstName(String fName) {
        firstName.set(fName);
    }
        
    public String getLastName() {
        return lastName.get();
    }
    public void setLastName(String fName) {
        lastName.set(fName);
    }
    
    public String getEmail() {
        return email.get();
    }
    public void setEmail(String fName) {
        email.set(fName);
    }
        
}

firstNamelastName、およびemail文字列プロパティが作成され、特定のデータ要素の参照が可能になります。

また、getメソッドとsetメソッドがそれぞれのデータ要素に指定されます。このため、getFirstNameメソッドがfirstNameプロパティの値を返し、setFirstNameメソッドがこのプロパティの値を指定します。

Personクラスでデータ・モデルが記述されている場合、ObservableList配列を作成し、表に表示する必要のあるデータ列の数を定義できます。例13-4に示すコード・フラグメントは、このタスクを実装しています。

例13-4 監視可能なリストの表データの定義

final ObservableList<Person> data = FXCollections.observableArrayList(
    new Person("Jacob", "Smith", "jacob.smith@example.com"),
    new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
    new Person("Ethan", "Williams", "ethan.williams@example.com"),
    new Person("Emma", "Jones", "emma.jones@example.com"),
    new Person("Michael", "Brown", "michael.brown@example.com")
);

次のステップでは、データを表の列に関連付けます。これは例13-5に示すように、それぞれのデータ要素に定義したプロパティを介して実行できます。

例13-5 列へのデータ・プロパティの設定

firstNameCol.setCellValueFactory(
    new PropertyValueFactory<>("firstName")
);
lastNameCol.setCellValueFactory(
    new PropertyValueFactory<>("lastName")
);
emailCol.setCellValueFactory(
    new PropertyValueFactory<>("email")
);

setCellValueFactoryメソッドはそれぞれの列のセル・ファクトリを指定します。セル・ファクトリは、表列のfirstNamelastName、およびemailプロパティをPersonクラスの対応するメソッドへの参照として使用するPropertyValueFactoryクラスを使用して実装されます。

データ・モデルが定義され、データが追加されて列に関連付けられると、TableViewクラスのsetItemsメソッド、table.setItems(data)を使用してデータを表に追加できます。

ObservableListオブジェクトによってその要素に対する変更の追跡が有効になるため、TableViewコンテンツはデータ変更に応じて自動的に更新されます。

例13-6に示すアプリケーション・コードを確認します。

例13-6 表の作成および表へのデータの追加

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
public class TableViewSample extends Application {
 
    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com")
        );
   
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(500);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<>("firstName"));
 
        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<>("lastName"));
 
        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<>("email"));
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

このアプリケーション・コードをコンパイルして実行すると、図13-4に示す表が表示されます。

図13-4 データを移入した表

連絡先情報を移入した表
「図13-4 データを移入した表」の説明

新規列の追加

図13-4の表には、5つのデータ行がありますが、これは変更できません。

テキスト・フィールドを使用すると、新しい値を「First Name」、「Last Name」、および「Email」列に入力できます。Text Fieldコントロールは、アプリケーションでのユーザーのテキスト入力受入れを有効にします。例13-7では、3つのテキスト・フィールドを作成し、各フィールドのプロンプト・テキストを定義し、「Add」ボタンを作成します。

例13-7 テキスト・フィールドを使用した表への新規項目の入力

final TextField addFirstName = new TextField();
addFirstName.setPromptText("First Name");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());

final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("Last Name");

final TextField addEmail = new TextField();
addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPromptText("Email");
 
final Button addButton = new Button("Add");
addButton.setOnAction((ActionEvent e) -> {
    data.add(new Person(
        addFirstName.getText(),
        addLastName.getText(),
        addEmail.getText()
    ));
    addFirstName.clear();
    addLastName.clear();
    addEmail.clear();
});

ユーザーが「Add」ボタンをクリックすると、テキスト・フィールドに入力した値がPersonコンストラクタに組み込まれ、data監視可能リストに追加されます。このため、連絡先情報のある新規エントリが表に表示されます。

例13-8に示すアプリケーション・コードを確認します。

例13-8 新規項目を入力するテキスト・フィールドのある表

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
public class TableViewSample extends Application {
 
    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data =
            FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<>("firstName"));
 
        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<>("lastName"));
 
        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<>("email"));
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

このアプリケーションでは、電子メール・アドレスが正しくない形式で入力されていないかなどを確認するフィルタは提供されません。そのような機能は、独自にアプリケーションを開発する際に指定できます。

この時点の実装では、空の値が入力されていないかについても確認しません。値が提供されていない場合は、「Add」ボタンをクリックすると表に空の列が挿入されます。

図13-5はユーザーが新規データ行を追加する方法を示しています。

図13-5 アドレス帳への連絡先情報の追加

新規データを追加するテキスト・フィールドのある表
「図13-5 アドレス帳への連絡先情報の追加」の説明

図13-6に、「Add」ボタンをクリックした後の表を示します。Emma Whiteの連絡先の詳細が表に表示されます。

図13-6 新たに追加されたエントリ

アドレス帳に新たに入力されたデータ
「図13-6 新たに追加されたエントリ」の説明

列データのソート

TableViewクラスは列のデータをソートするビルトインの機能を提供します。ユーザーは列ヘッダーをクリックしてデータの順序を変更できます。最初のクリックでソート順序が昇順、2回目のクリックで降順、3回目のクリックでソートが無効になります。デフォルトでは、ソートは適用されていません。

ユーザーは表内の複数の列をソートでき、ソート操作での各列の優先度を指定できます。複数の列をソートするには、[Shift]キーを押しながら、ソートするそれぞれの列のヘッダーをクリックします。

図13-7では、名に昇順のソート順序が適用されていますが、姓は降順でソートされています。最初の列が2番目の列より優先されます。

図13-7 複数列のソート

データが列でソートされている表
「図13-7 複数列のソート」の説明

アプリケーション開発者は、setSortTypeメソッドを適用して、アプリケーション内のそれぞれの列にソートのプリファレンスを設定できます。昇順タイプと降順タイプのどちらも指定できます。たとえば、次のコード行では、emailCol列のソートを昇順タイプに設定します。emailCol.setSortType(TableColumn.SortType.DESCENDING);

また、TableColumnインスタンスをTableView.sortOrder監視可能リストに追加、またはリストから削除して、ソートする列を指定できます。このリストの列の順序は、ソートの優先度を表します(たとえば、ゼロ項目は最初の項目より優先されます)。

データのソートを禁止するには、その列にsetSortable(false)メソッドをコールします。

表内のデータの編集

TableViewクラスは表形式データを表示するだけでなく、その編集も可能にします。setEditableメソッドを使用して、表のコンテンツの編集を有効にします。

setCellFactoryメソッドを使用すると、TextFieldTableCellクラスによって表セルをテキスト・フィールドとして実装できます。setOnEditCommitメソッドは編集を処理し、更新した値を該当する表のセルに割り当てます。例13-9では、「First Name」、「Last Name」および「Email」列でのセル編集を処理するこのようなメソッドの割当て方法を示します。

例13-9 セル編集の実装

firstNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
firstNameCol.setOnEditCommit(
    (CellEditEvent<Person, String> t) -> {
        ((Person) t.getTableView().getItems().get(
            t.getTablePosition().getRow())
            ).setFirstName(t.getNewValue());
});

lastNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
lastNameCol.setOnEditCommit(
    (CellEditEvent<Person, String> t) -> {
        ((Person) t.getTableView().getItems().get(
            t.getTablePosition().getRow())
            ).setLastName(t.getNewValue());
});

emailCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());       
emailCol.setOnEditCommit(
(CellEditEvent<Person, String> t) -> {
    ((Person) t.getTableView().getItems().get(
        t.getTablePosition().getRow())
        ).setEmail(t.getNewValue());
});

例13-10にアプリケーションの完全なコードを示します。

例13-10 セル編集が有効なTableViewSample

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
public class TableViewSample extends Application {
 
    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data =
            FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn<Person, String> firstNameCol = 
            new TableColumn<>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
            new PropertyValueFactory<>("firstName"));
        
        firstNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
        firstNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setFirstName(t.getNewValue());
        });
 
 
        TableColumn<Person, String> lastNameCol = 
            new TableColumn<>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
            new PropertyValueFactory<>("lastName"));
       lastNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
       lastNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setLastName(t.getNewValue());
        });
 
        TableColumn<Person, String> emailCol = new TableColumn<>("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
            new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());       
        emailCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setEmail(t.getNewValue());
        });
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
}

図13-8では、ユーザーはMichael Brownの姓を編集しています。表のセルを編集するには、ユーザーは新しい値をセルに入力し、[Enter]キーを押します。[Enter]キーを押すまでセルは変更されません。この動作はTextFieldクラスの実装によって決まります。

図13-8 表のセルの編集

編集した表。
「図13-8 表のセルの編集」の説明

TextFieldコントロールのデフォルト実装では、編集をコミットするには、ユーザーが[Enter]キーを押す必要があります。TextFieldの動作は、予想されるユーザー操作性に従い、フォーカスの変更時に編集をコミットするように再定義できます。このような代替の動作を実装するよう、コードを変更してみます。

例13-11 セル編集の代替ソリューション

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
 
public class TableViewSample extends Application {
 
    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data =
            FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
        
        Callback<TableColumn<Person, String>, 
            TableCell<Person, String>> cellFactory
                = (TableColumn<Person, String> p) -> new EditingCell();
 
        TableColumn<Person, String> firstNameCol = 
            new TableColumn<>("First Name");
        TableColumn<Person, String> lastNameCol = 
            new TableColumn<>("Last Name");
        TableColumn<Person, String> emailCol = 
            new TableColumn<>("Email");
 
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
            new PropertyValueFactory<>("firstName"));
        firstNameCol.setCellFactory(cellFactory);        
        firstNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setFirstName(t.getNewValue());
        });
 
 
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
            new PropertyValueFactory<>("lastName"));
        lastNameCol.setCellFactory(cellFactory);
        lastNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setLastName(t.getNewValue());
        });
 
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
            new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(cellFactory);
        emailCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setEmail(t.getNewValue());
        });
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
 
    class EditingCell extends TableCell<Person, String> {
 
        private TextField textField;
 
        public EditingCell() {
        }
 
        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.selectAll();
            }
        }
 
        @Override
        public void cancelEdit() {
            super.cancelEdit();
 
            setText((String) getItem());
            setGraphic(null);
        }
 
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
 
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(null);
                }
            }
        }
 
        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
            textField.focusedProperty().addListener(
                (ObservableValue<? extends Boolean> arg0, 
                Boolean arg1, Boolean arg2) -> {
                    if (!arg2) {
                        commitEdit(textField.getText());
                    }
            });
        }
 
        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }
    }
}

TextFieldTableCellの実装によってユーザー操作性が向上するため、将来のリリースではこのアプローチは不要になる可能性があります。

表へのマップ・データの追加

Mapデータを表に追加できます。例13-12に示すように、MapValueFactoryクラスを使用して表に学生IDのマップを表示します。

例13-12 表へのマップ・データの追加

import java.util.HashMap;
import java.util.Map;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.MapValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;
 
public class TableViewSample extends Application {
 
    public static final String Column1MapKey = "A";
    public static final String Column2MapKey = "B";
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(300);
        stage.setHeight(500);
        
        final Label label = new Label("Student IDs");
        label.setFont(new Font("Arial", 20));
 
        TableColumn<Map, String> firstDataColumn = new TableColumn<>("Class A");
        TableColumn<Map, String> secondDataColumn = new TableColumn<>("Class B");
 
        firstDataColumn.setCellValueFactory(new MapValueFactory(Column1MapKey));
        firstDataColumn.setMinWidth(130);
        secondDataColumn.setCellValueFactory(new MapValueFactory(Column2MapKey));
        secondDataColumn.setMinWidth(130);
 
        TableView tableView = new TableView<>(generateDataInMap());
 
        tableView.setEditable(true);
        tableView.getSelectionModel().setCellSelectionEnabled(true);
        tableView.getColumns().setAll(firstDataColumn, secondDataColumn);
        Callback<TableColumn<Map, String>, TableCell<Map, String>>
            cellFactoryForMap = (TableColumn<Map, String> p) -> 
                new TextFieldTableCell(new StringConverter() {
                    @Override
                        public String toString(Object t) {
                        return t.toString();
                    }
                    @Override
                    public Object fromString(String string) {
                        return string;
                    }
            });
        firstDataColumn.setCellFactory(cellFactoryForMap);
        secondDataColumn.setCellFactory(cellFactoryForMap);
 
        final VBox vbox = new VBox();
 
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, tableView);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
 
        stage.show();
    }
 
    private ObservableList<Map> generateDataInMap() {
        int max = 10;
        ObservableList<Map> allData = FXCollections.observableArrayList();
        for (int i = 1; i < max; i++) {
            Map<String, String> dataRow = new HashMap<>();
 
            String value1 = "A" + i;
            String value2 = "B" + i;
 
            dataRow.put(Column1MapKey, value1);
            dataRow.put(Column2MapKey, value2);
 
            allData.add(dataRow);
        }
        return allData;
    }
}

MapValueFactoryクラスは、特に表の列のセル・ファクトリ内での使用を意図して設計されているCallbackインタフェースを実装します。例13-12では、dataRowのハッシュ・マップがTableViewオブジェクトの単一行を表します。マップには2つのStringキー、Column1MapKeyおよびColumn2MapKeyがあり、該当する値を最初の列と2番目の列にマップします。表の列に対してコールされたsetCellValueFactoryメソッドが、特定のキーと一致するデータを列に移入するため、最初の列には「A」キーに対応する値が含まれ、2番目の列には「B」キーに対応する値が含まれます。

このアプリケーションをコンパイルして実行すると、図13-9に示す出力が得られます。

図13-9 マップ・データのある表ビュー

図13-9の説明が続きます
「図13-9 マップ・データのある表ビュー」の説明

TreeTableViewはJavaFXの表データ・コントロールをさらに拡張したものです。このUIコントロールの詳細は、「ツリー表ビュー」の章を参照してください。

関連APIドキュメント

ウィンドウを閉じる

目次

JavaFX: JavaFX UIコンポーネントの操作

展開 | 縮小