在JavaFX 8中为select all创建框架类型列标题

时间:2016-05-19 21:13:45

标签: tableview javafx-8 tablecolumn

我希望创建一个自定义TableColumn,它是一个CheckBox控件,表示是否选中了行。我们不想使用标准SHIFT或CONTROL + CLICK来处理此问题,因为用户往往会无意中点击并失去他们的选择。

我想做的是以下内容:

enter image description here

在这种情况下,列不应该是基础数据模型的一部分,而只是表示选择的内容。它不应该以任何方式依赖于数据模型。如果用户选中列标题中的复选框,则应选择表中的所有记录(不仅仅是可见的记录),如果取消选择,则应取消选择所有记录。

有没有办法以这种方式表示选择模型?

我遇到的第二个问题是,我的第一个尝试在SceneBuilder中不会显示为可选择的(因为我可以收集)TableColumn不是一个Node,因此似乎从导入中被忽略了?

非常感谢任何帮助。

由于

1 个答案:

答案 0 :(得分:5)

一个可能的解决方案是扩展TableViewSkin以添加带有复选框的TableColumn,而此列不会使用用户的模型进行烘焙,但复选框选项的更改将影响表格的选择模型。

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

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

CheckTableView类

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类

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类:

@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类:

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
}

Event

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