从TableView获取选定的行

时间:2017-03-09 09:06:42

标签: javafx tableview selection

我的TableView配置如下:

tableView.getSelectionModel().setCellSelectionEnabled(true);
tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

我可以通过调用

来获取所选单元格
tableView.getSelectionModel().getSelectedCells()

我可以通过调用

来获取所选项目
tableView.getSelectionModel().getSelectedItems()

不幸的是,似乎没有像上面那样的方法来获取所选行..

我想要获得的是一个配置为选择单元格的表,但无论如何突出显示相应的行。

1 个答案:

答案 0 :(得分:1)

选择模型中没有API可以为您提供实际的TableRow。这是有道理的,因为模型不应该知道正在观察它的任何UI元素。

这里的步骤有点棘手。您需要创建某种可观察的集合,以跟踪哪些行包含选定的单元格。这是一个使用可观察集的相当天真的实现:

ObservableSet<Integer> rowsWithSelectedCells = FXCollections.observableSet();
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> c) -> {
    rowsWithSelectedCells.clear();
    Set<Integer> rows = table.getSelectionModel().getSelectedCells().stream()
        .map(pos -> pos.getRow())
        .collect(Collectors.toSet());
    rowsWithSelectedCells.addAll(rows);
});

现在让你的表行观察这个集合,并相应地更新它们的样式。为此,请在表格中使用rowFactory

PseudoClass rowContainsSelectedCell = PseudoClass.getPseudoClass("contains-selection");
table.setRowFactory(tv -> {
    TableRow<Person> row = new TableRow<>();
    BooleanBinding containsSelection = Bindings.createBooleanBinding(
            () -> rowsWithSelectedCells.contains(row.getIndex()), rowsWithSelectedCells, row.indexProperty());
    containsSelection.addListener((obs, didContainSelection, nowContainsSelection) -> 
        row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
    return row ;
});

这会通过在行上设置CSS伪类来更新样式。在外部样式表中,您可以执行类似

的操作
.table-row-cell:contains-selection {
    -fx-background: yellow ;
}

突出显示这些行。

这是一个SSCCE。文件style.css仅包含上述CSS:

import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class TableHighlightRowsWithSelectedCells extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Person> table = new TableView<>();
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        table.getSelectionModel().setCellSelectionEnabled(true);


        ObservableSet<Integer> rowsWithSelectedCells = FXCollections.observableSet();
        table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> c) -> {
            rowsWithSelectedCells.clear();
            Set<Integer> rows = table.getSelectionModel().getSelectedCells().stream().map(pos -> pos.getRow()).collect(Collectors.toSet());
            rowsWithSelectedCells.addAll(rows);
        });

        PseudoClass rowContainsSelectedCell = PseudoClass.getPseudoClass("contains-selection");
        table.setRowFactory(tv -> {
            TableRow<Person> row = new TableRow<>();
            BooleanBinding containsSelection = Bindings.createBooleanBinding(
                    () -> rowsWithSelectedCells.contains(row.getIndex()), rowsWithSelectedCells, row.indexProperty());
            containsSelection.addListener((obs, didContainSelection, nowContainsSelection) -> 
                row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
            return row ;
        });

        table.getColumns().add(column("First Name", Person::firstNameProperty));
        table.getColumns().add(column("Last Name", Person::lastNameProperty));
        table.getColumns().add(column("Email", Person::emailProperty));

        table.getItems().addAll(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com"),
                new Person("Michael", "Brown", "michael.brown@example.com")
        );

        Scene scene = new Scene(table, 600, 600);
        scene.getStylesheets().add("style.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private <S,T> TableColumn<S,T> column(String text, Function<S, ObservableValue<T>> prop) {
        TableColumn<S,T> col = new TableColumn<>(text);
        col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
        return col ;
    }

    public static class Person {

        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();
        private final StringProperty email = new SimpleStringProperty();

        public Person(String firstName, String lastName, String email) {
            setFirstName(firstName);
            setLastName(lastName);
            setEmail(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }


        public final String getFirstName() {
            return this.firstNameProperty().get();
        }


        public final void setFirstName(final String firstName) {
            this.firstNameProperty().set(firstName);
        }


        public final StringProperty lastNameProperty() {
            return this.lastName;
        }


        public final String getLastName() {
            return this.lastNameProperty().get();
        }


        public final void setLastName(final String lastName) {
            this.lastNameProperty().set(lastName);
        }


        public final StringProperty emailProperty() {
            return this.email;
        }


        public final String getEmail() {
            return this.emailProperty().get();
        }


        public final void setEmail(final String email) {
            this.emailProperty().set(email);
        }
    }

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

最后,一个快速的表现说明。每次选择更改时,将完全重建包含具有所选单元格的行的索引的可观察集。这可能很好,但是对于可能选择大量单元格的非常大的表,性能可能是个问题。性能稍好的实现是跟踪每行选择了多少个单元格,并递增或递减它:

ObservableMap<Integer, Integer> selectedCellCountByRow = FXCollections.observableHashMap();
table.getSelectionModel().getSelectedCells().addListener((Change<? extends TablePosition> c) -> {
    while (c.next()) {
        if (c.wasAdded()) {
            for (TablePosition<?,?> p : c.getAddedSubList()) {
                int currentCount = selectedCellCountByRow.getOrDefault(p.getRow(), 0);
                int newCount = currentCount + 1 ;
                selectedCellCountByRow.put(new Integer(p.getRow()), newCount);
                System.out.println("Count now: "+selectedCellCountByRow.get(p.getRow()));
            }
        }
        if (c.wasRemoved()) {
            for (TablePosition<?, ?> p : c.getRemoved()) {
                int currentCount = selectedCellCountByRow.getOrDefault(p.getRow(), 0);
                int newCount = currentCount - 1 ;
                if (newCount <= 0) {
                    selectedCellCountByRow.remove(p.getRow());
                } else {
                    selectedCellCountByRow.put(p.getRow(), newCount);
                }
            }
        }
    }
});

然后行工厂将更新如下:

PseudoClass rowContainsSelectedCell = PseudoClass.getPseudoClass("contains-selection");
table.setRowFactory(tv -> {
    TableRow<Person> row = new TableRow<>();
    BooleanBinding containsSelection = Bindings.createBooleanBinding(
            () -> selectedCellCountByRow.containsKey(row.getIndex()), selectedCellCountByRow, row.indexProperty());
    containsSelection.addListener((obs, didContainSelection, nowContainsSelection) -> 
        row.pseudoClassStateChanged(rowContainsSelectedCell, nowContainsSelection));
    return row ;
});