在 JavaFX 8 中为 select 全部创建框架类型列 header

Create a framework type column header for select all in JavaFX 8

我想创建一个自定义 TableColumn,它是一个 CheckBox 控件,表示该行是否 selected。我们不想使用标准的 SHIFT 或 CONTROL + CLICK 来处理这个问题,因为用户往往会不经意地点击并失去他们的 selection。

我想做的是:

在这种情况下,该列不应是基础数据模型的一部分,而只是表示 select 编辑或不编辑的内容。它不应该以任何方式依赖于数据模型。如果用户 select 选中了 header 列中的复选框,它应该 select table 中的所有记录(不仅仅是可见的)并且如果它是 deselected,然后它应该删除select所有记录。

有没有办法用这种方式表示 selection 模型?

我遇到的第二个问题是,我的第一次尝试不会在 SceneBuilder 中显示为 selectable,因为(据我所知)TableColumn 不是节点,因此似乎被忽略了导入?

如有任何帮助,我们将不胜感激。

谢谢

可能的解决方案是扩展 TableViewSkin 以添加带有复选框的 TableColumn,虽然此列未与用户模型一起烘焙,但复选框选择的更改将影响 table的选择模型。

虽然仅通过复选框进行选择可以正常工作,但您无法删除也允许选择行的行为,因此您必须监听这两种情况。

此代码段有效,但尚未通过排序和修改模型进行测试,因此它只是自定义 TableView.

的开始

CheckTableViewclass

public class CheckTableView<T> extends TableView<T> {

    private ObservableList<T> selected;

    public CheckTableView() {
        this(FXCollections.observableArrayList());
    }

    public CheckTableView(ObservableList<T> items) {
        setItems(items);
        setEditable(true);
        getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        skinProperty().addListener(new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                selected = ((CheckTableViewSkin) getSkin()).getSelectedRows();
                skinProperty().removeListener(this);
            }
        });
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new CheckTableViewSkin<>(this);
    }

    public ObservableList<T> getSelectedRows() {
        return selected;
    } 
}

CheckTableViewSkin class

public class CheckTableViewSkin<T> extends TableViewSkin<T> {

    private final TableColumn<T, Boolean> checkColumn;
    private final CheckBox headerCheckBox = new CheckBox();
    private final List<BooleanProperty> colSelected = new ArrayList<>();

    private final ChangeListener<Number> listener = (obs, ov, nv) -> {
        if (nv.intValue() != -1) {
            Platform.runLater(() -> {
                colSelected.get(nv.intValue()).set(!colSelected.get(nv.intValue()).get());
                refreshSelection();
            });
        }
    };
    private final ChangeListener<Boolean> headerListener = (obs, ov, nv) -> {
        Platform.runLater(() -> {
            IntStream.range(0, colSelected.size()).forEach(i -> colSelected.get(i).set(nv));
            refreshSelection();
        });
    };

    public CheckTableViewSkin(CheckTableView<T> control) {
        super(control);

        checkColumn = new TableColumn<>();

        headerCheckBox.selectedProperty().addListener(headerListener);
        checkColumn.setGraphic(headerCheckBox);

        // install listeners in checkboxes
        IntStream.range(0, control.getItems().size()).forEach(i -> {
            final SimpleBooleanProperty simple = new SimpleBooleanProperty();
            simple.addListener((obs, ov, nv) -> refreshSelection());
            colSelected.add(simple);
        });
        checkColumn.setCellFactory(CheckBoxTableCell.forTableColumn(colSelected::get));

        checkColumn.setPrefWidth(60);
        checkColumn.setEditable(true);
        checkColumn.setResizable(false);
        getColumns().add(0, checkColumn);

        getSelectionModel().selectedIndexProperty().addListener(listener);
    }

    private void refreshSelection() {
        // refresh list of selected rows
        getSelectionModel().selectedIndexProperty().removeListener(listener);
        getSelectionModel().clearSelection();
        AtomicInteger count = new AtomicInteger();
        IntStream.range(0, colSelected.size()).forEach(i -> {
            if (colSelected.get(i).get()) {
                getSelectionModel().select(i);
                count.getAndIncrement();
            }
        });
        headerCheckBox.selectedProperty().removeListener(headerListener);
        headerCheckBox.setSelected(count.get() == colSelected.size());
        headerCheckBox.selectedProperty().addListener(headerListener);
        // it may flick, but required to show all selected rows focused 
        getSkinnable().requestFocus();
        getSelectionModel().selectedIndexProperty().addListener(listener);
    }

    public ObservableList<T> getSelectedRows() {
        return getSelectionModel().getSelectedItems();
    }
}

现在的示例,Application class:

@Override
public void start(Stage primaryStage) {

    TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
    firstNameColumn.setCellValueFactory(p -> p.getValue().firstNameProperty());
    TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
    lastNameColumn.setCellValueFactory(p -> p.getValue().lastNameProperty());

    CheckTableView<Person> tableView = new CheckTableView(FXCollections.observableArrayList(
            new Person("Hans", "Muster"), new Person("Ruth", "Mueller"), 
            new Person("Heinz", "Kurz"), new Person("Cornelia", "Meier"), 
            new Person("Anna", "Best"), new Person("Stefan", "Meier")
    ));

    tableView.getColumns().addAll(firstNameColumn, lastNameColumn);
    Scene scene = new Scene(tableView, 600, 400);
    primaryStage.setScene(scene);
    primaryStage.show();

}

对于通常的 Person class:

public class Person {
    private final StringProperty firstName;
    private final StringProperty lastName;

    public Person(String firstName, String lastName) {
        this.firstName = new SimpleStringProperty(firstName);
        this.lastName = new SimpleStringProperty(lastName);
    }
    //getters & setters
}

此自定义控件也适用于 Scene Builder。