TableCell中的CheckBox打破了遍历顺序

时间:2015-11-18 09:58:46

标签: checkbox javafx tableview traversal tablecell

考虑以下示例:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class TestCheckBoxTab extends Application {
    public void start (Stage stage) {
        HBox root = new HBox();
        root.getChildren().addAll(new TextField(), new TextField(), new CheckBox(), new TextField());

        stage.setScene(new Scene(root));
        stage.show();
    }

    public static void main (String[] args) {
        launch();
    }
}

在这里,您可以轻松地使用TAB和SHIFT + TAB命令遍历不同的控件,并且它可以正常工作。

但是如果TableView中的表格单元格中有相同的控件,则CheckBox会中断遍历顺序。以下示例演示了这一点:

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class AlwaysEditableTable extends Application {
    public void start(Stage stage) {

        TableView<ObservableList<StringProperty>> table = new TableView<>();
        table.setEditable(true);
        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setPrefWidth(505);

        // Dummy columns
        ObservableList<String> columns = FXCollections.observableArrayList("Column1", "Column2", "Column3", "Column4",
                "Column5");

        // Dummy data
        ObservableList<StringProperty> row1 = FXCollections.observableArrayList(new SimpleStringProperty("Cell1"),
                new SimpleStringProperty("Cell2"), new SimpleStringProperty("0"), new SimpleStringProperty("Cell4"),
                new SimpleStringProperty("1"));
        ObservableList<ObservableList<StringProperty>> data = FXCollections.observableArrayList();
        data.add(row1);

        for (int i = 0; i < columns.size(); i++) {
            final int j = i;
            TableColumn<ObservableList<StringProperty>, String> col = new TableColumn<>(columns.get(i));
            col.setCellValueFactory(param -> param.getValue().get(j));
            col.setPrefWidth(100);

            if (i == 2 || i == 4) {
                col.setCellFactory(e -> new CheckBoxCell(j));
            } else {
                col.setCellFactory(e -> new AlwaysEditingCell(j));
            }

            table.getColumns().add(col);
        }

        table.setItems(data);

        Scene scene = new Scene(table);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

    /**
     * A cell that contains a text field that is always shown. The text of the
     * text field is bound to the underlying data.
     */
    public static class AlwaysEditingCell extends TableCell<ObservableList<StringProperty>, String> {

        private final TextField textField;

        public AlwaysEditingCell(int columnIndex) {

            textField = new TextField();

            this.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
                if (isNowEmpty) {
                    setGraphic(null);
                } else {
                    setGraphic(textField);
                }
            });

            // The index is not changed until tableData is instantiated, so this
            // ensure the we wont get a NullPointerException when we do the
            // binding.
            this.indexProperty().addListener((obs, oldValue, newValue) -> {

                ObservableList<ObservableList<StringProperty>> tableData = getTableView().getItems();
                int oldIndex = oldValue.intValue();
                if (oldIndex >= 0 && oldIndex < tableData.size()) {
                    textField.textProperty().unbindBidirectional(tableData.get(oldIndex).get(columnIndex));
                }
                int newIndex = newValue.intValue();
                if (newIndex >= 0 && newIndex < tableData.size()) {
                    textField.textProperty().bindBidirectional(tableData.get(newIndex).get(columnIndex));
                    setGraphic(textField);
                } else {
                    setGraphic(null);
                }

            });
        }
    }

    /**
     * A cell containing a checkbox. The checkbox represent the underlying value
     * in the cell. If the cell value is 0, the checkbox is unchecked. Checking
     * or unchecking the checkbox will change the underlying value.
     */
    public static class CheckBoxCell extends TableCell<ObservableList<StringProperty>, String> {

        private final CheckBox box;
        private ObservableList<ObservableList<StringProperty>> tableData;

        public CheckBoxCell(int columnIndex) {

            this.box = new CheckBox();

            this.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
                if (isNowEmpty) {
                    setGraphic(null);
                } else {
                    setGraphic(box);
                }
            });

            this.indexProperty().addListener((obs, oldValue, newValue) -> {

                tableData = getTableView().getItems();

                int newIndex = newValue.intValue();
                if (newIndex >= 0 && newIndex < tableData.size()) {
                    // If active value is "1", the check box will be set to
                    // selected.
                    box.setSelected(tableData.get(getIndex()).get(columnIndex).equals("1"));

                    // We add a listener to the selected property. This will
                    // allow us to execute code every time the check box is
                    // selected or deselected.
                    box.selectedProperty().addListener((observable, oldVal, newVal) -> {
                        if (newVal) {
                            // If newValue is true the checkBox is selected, and
                            // we set the corresponding cell value to "1".
                            tableData.get(getIndex()).get(columnIndex).set("1");
                        } else {
                            // Otherwise we set it to "0".
                            tableData.get(getIndex()).get(columnIndex).set("0");
                        }
                    });

                    setGraphic(box);
                } else {
                    setGraphic(null);
                }

            });
        }
    }
}

在将TextField聚焦到TableCell内时按TAB键时,焦点会正确移动到下一个控件。但是,如果CheckBox内的TableCell被关注,则焦点将移至Control中的第一个TableView,而不是下一个Control。在聚焦CheckBox时SHIFT + TAB会将焦点移动到TableView中的最后一个控件。如果我在TextField之外添加TableView,则在调整CheckBox时,SHIFT + TAB将实际关注TextField,而TAB行为仍至少将焦点保持在TableView内{1}}。 CheckBox以某种方式打破了遍历顺序。

这对我来说很奇怪,因为TextFieldCheckBox似乎实现了相同的TAB功能,因为在第一个示例中遍历顺序是正确的。我猜的东西是从Control类继承的。

有人对此有所了解吗?我试图在EventFilterControlTextField,{{1}的源代码中为TAB和SHIFT + TAB命令寻找某种CheckBox或监听器。 }甚至在TextInputControl来源中,但我无法在任何地方找到它。

我还尝试为Scene单元格实现自己的TAB功能,最终又引发了另一个issue

1 个答案:

答案 0 :(得分:0)

我在同样的问题上苦苦挣扎。我终于通过设置来避免它:

checkBox.setFocusTraversable(false);

this.setFocusTraversable(false);

我自己实现的BooleanCell。 该表遍历可编辑字段,并且在我的实现中始终启用Checkbox。但是设置禁用复选框意味着您必须双击它才能更改值。这将是一种奇怪的行为。