おのれ、チェックボックス
こんな画面、ギョームアプリケーションを作ったことがある方なら一度はあると思います。
チェックボックスを選択して、その行に対して操作を行うみたいなやつ。
JavaFXでは、CheckBoxTableCellというチェックボックス入りのテーブルセルクラスが標準であるのでそれを使います。
標準であるのはありがたいんですが、このクラス、選択時にテーブルのOnMouseClickedが動かないし、行を選択したことにもならないのよね。
普通ならテーブルの行をクリックすると、こんな感じで青く色が反転して選択状態になるんだけど。
チェックボックスをクリックすると
行は選択状態にならんのかい… ちなみに、テーブル側でOnMouseClickedを設定しておいても動きません。
チェックボックスを押したときでも、行を選択状態にしたい!!!
というわけで、(ムリヤリ)挙動を変えて使いやすくしましょう。
プログラム
今回もみんな大好きOracleのサンプルプログラムをすこし改変して使います。
また、プログラム全文のせちゃいます。
メインクラス
package application; 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.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 Main extends Application { private TableView<Person> table = new TableView<>(); private ObservableList<Person> data = FXCollections.observableArrayList( new Person(false, "Jacob", "Smith", "jacob.smith@example.com"), new Person(false, "Isabella", "Johnson", "isabella.johnson@example.com"), new Person(false, "Ethan", "Williams", "ethan.williams@example.com"), new Person(false, "Emma", "Jones", "emma.jones@example.com"), new Person(false, "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(515); stage.setHeight(500); final Label label = new Label("Address Book"); label.setFont(new Font("Arial", 20)); CheckBoxColumn checkBoxCol = new CheckBoxColumn(); checkBoxCol.setMinWidth(50); TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name"); firstNameCol.setMinWidth(100); firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName")); TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name"); lastNameCol.setMinWidth(100); lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName")); TableColumn<Person, String> emailCol = new TableColumn<>("Email"); emailCol.setMinWidth(200); emailCol.setCellValueFactory(new PropertyValueFactory<>("email")); // なぜかテーブル全体をeditableにしないとチェックボックスが押せない… table.setEditable(true); // マウスクリック時に動作する table.setOnMouseClicked(event -> { System.out.println("行が選択されたぜ!!"); }); table.setItems(data); table.getColumns().add(checkBoxCol); table.getColumns().add(firstNameCol); table.getColumns().add(lastNameCol); table.getColumns().add(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(); } }
チェックボックスカラムのクラス
package application; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.event.Event; import javafx.scene.control.TableColumn; import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; public class CheckBoxColumn extends TableColumn<Person, Boolean> { public CheckBoxColumn() { // Personのcheckedプロパティと紐づける this.setCellValueFactory(new PropertyValueFactory<>("checked")); this.setCellFactory(column -> { // CheckBoxTableCellの挙動を定義する CheckBoxTableCell<Person, Boolean> cell = new CheckBoxTableCell<Person, Boolean>(index -> { BooleanProperty selected = new SimpleBooleanProperty( this.getTableView().getItems().get(index).isChecked()); selected.addListener((ov, o, n) -> { // チェックボックスの状態が変わったらPersonのデータも更新する this.getTableView().getItems().get(index).setChecked(n); // チェックボックスが押されたので、行を選択したことにする this.getTableView().getSelectionModel().select(index); // テーブルにクリックイベントを飛ばす Event.fireEvent(column.getTableView(), new MouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0, 0, 0, MouseButton.PRIMARY, 1, true, true, true, true, true, true, true, true, true, true, null)); }); return selected; }); return cell; }); } }
テーブルに表示するモデルのクラス
package application; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; public class Person { private SimpleBooleanProperty checked; private SimpleStringProperty firstName; private SimpleStringProperty lastName; private SimpleStringProperty email; public Person(boolean checked, String fName, String lName, String email) { this.checked = new SimpleBooleanProperty(checked); this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public SimpleBooleanProperty checkedProperty() { return checked; } public boolean isChecked() { return checked.get(); } public void setChecked(boolean checked) { this.checked.set(checked); } 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); } }
長々とプログラムを載せましたが、大事なのはチェックボックスカラムクラスのここだけ。
// チェックボックスが押されたので、行を選択したことにする this.getTableView().getSelectionModel().select(index); // テーブルにクリックイベントを飛ばす Event.fireEvent(column.getTableView(), new MouseEvent(MouseEvent.MOUSE_CLICKED, 0, 0, 0, 0, MouseButton.PRIMARY, 1, true, true, true, true, true, true, true, true, true, true, null));
まずは、上の一行のほうから。
テーブルではSelectionModelというクラスを使って、任意に行の選択状態を変更したり、選択されている行のモデルデータを取得することができます。
これが、地味に便利。
今回は、チェックボックスの選択状態が変更されたセルの行番号indexを使って、それと同じ行を選択したことにしています。
次に、見るからに脳みそ筋肉な次の実装。
これは、第一引数のノードで第二引数のイベントを発生させるという処理を書いています。
テーブルにイベントが飛ばないならば、自分でイベントを生成して飛ばしてしまおうというわけです。
実行結果
行が選択されたぜ!!
にしても、テーブル周りの実装が、私には高度すぎて時間がかかる…
androidとかやっている人なら、もうちょっとセンス良く書けるのかしら。
// なぜかテーブル全体をeditableにしないとチェックボックスが押せない… table.setEditable(true);
これな…