13 表ビュー
この章では、表の追加、表へのデータの移入、表内の行の編集など、JavaFXアプリケーション内での表の基本的な操作方法を学習します。
JavaFX SDK APIでは、表形式でデータを表すためのクラスがいくつか用意されています。JavaFXアプリケーションでの表の作成に最も重要なクラスはTableView
、TableColumn
、およびTableCell
です。表にはデータ・モデルを実装し、セル・ファクトリを適用することによってデータを移入できます。
表クラスは、データを列でソートし、必要に応じて列のサイズを変更するビルトインの機能を備えています。
図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に示す出力が得られます。
setVisible
メソッドをコールすると、列の表示を管理できます。たとえば、アプリケーションのロジックでユーザーの電子メール・アドレスを非表示にする必要がある場合、このタスクはemailCol.setVisible(false)
で実装できます。
データの構造をさらに複雑に表現する必要がある場合は、ネストされた列を作成できます。
たとえば、アドレス帳の連絡先に電子メール・アカウントが2つあると仮定します。その場合、プライマリ電子メール・アドレスとセカンダリ電子メール・アドレスの2つの列を表示する必要があります。例13-2に示すように、2つのサブ列を作成し、emailCol
にgetColumns
メソッドをコールします。
例13-2 ネストされた列の作成
TableColumn firstEmailCol = new TableColumn("Primary"); TableColumn secondEmailCol = new TableColumn("Secondary"); emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);
これらの行を例13-1に追加し、アプリケーション・コードをコンパイルして実行すると、表が図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); } }
firstName
、lastName
、および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
メソッドはそれぞれの列のセル・ファクトリを指定します。セル・ファクトリは、表列のfirstName
、lastName
、および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の表には、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-6に、「Add」ボタンをクリックした後の表を示します。Emma Whiteの連絡先の詳細が表に表示されます。
列データのソート
TableView
クラスは列のデータをソートするビルトインの機能を提供します。ユーザーは列ヘッダーをクリックしてデータの順序を変更できます。最初のクリックでソート順序が昇順、2回目のクリックで降順、3回目のクリックでソートが無効になります。デフォルトでは、ソートは適用されていません。
ユーザーは表内の複数の列をソートでき、ソート操作での各列の優先度を指定できます。複数の列をソートするには、[Shift]キーを押しながら、ソートするそれぞれの列のヘッダーをクリックします。
図13-7では、名に昇順のソート順序が適用されていますが、姓は降順でソートされています。最初の列が2番目の列より優先されます。
アプリケーション開発者は、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
クラスの実装によって決まります。
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に示す出力が得られます。
TreeTableView
はJavaFXの表データ・コントロールをさらに拡張したものです。このUIコントロールの詳細は、「ツリー表ビュー」の章を参照してください。
関連APIドキュメント