且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

创建框架类型列标题以在JavaFX 8中全选

更新时间:2023-11-17 14:10:46

一个可行的解决方案可能是扩展TableViewSkin以添加带有复选框的TableColumn,而此列未随用户模型一起提供,而是在复选框的选择将影响表的选择模型.

A possible solution could be extending TableViewSkin to add a TableColumn with the checkboxes, while this column is not baked with the user's model, but changes on the checkboxes selection will affect the table's selection model.

虽然仅通过复选框进行选择可以很好地工作,但是您不能删除允许选择行的行为,因此您必须侦听这两种情况.

While selecting only through the checkboxes works fine, you can't remove the behavior that allows selecting the rows as well, so you have to listen for both cases.

此代码段有效,但是尚未经过排序和修改模型的测试,因此,这只是自定义TableView的开始.

This snippet works, but it hasn't been tested with sorting and modifying the model, so it will be just a start for a custom 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
}

此自定义控件也将与Scene Builder一起使用.

This custom control will work with Scene Builder as well.