Get selected row from TableView

て烟熏妆下的殇ゞ 提交于 2019-12-29 09:58:07

问题


I have a TableView configured as follows:

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

I can get the selected cells by calling

tableView.getSelectionModel().getSelectedCells()

I can get the selected items by calling

tableView.getSelectionModel().getSelectedItems()

Unfortunately, it seems there is no method like above to get the selected rows..

What I want to achive is a table configured to select cells but anyways highlight the corresponding row.


回答1:


There is no API in the selection model that gives you the actual TableRows. This makes sense, because a model should not be aware of any of the UI elements that are observing it.

The steps here are a little tricky. You need to create an observable collection of some kind that tracks which rows contain a selected cell. Here's a fairly naïve implementation using an observable set:

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);
});

Now you make your table rows observe this set, and update their style accordingly. To do this, use a rowFactory on the table:

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 ;
});

This updates the style by setting a CSS pseudoclass on the row. In an external style sheet you can do something like

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

to highlight these rows.

Here's a SSCCE. The file style.css contains just the CSS above:

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);
    }
}

Finally, a quick performance note. The observable set containing the indices of the rows with selected cells is rebuilt entirely every time the selection changes. This is probably fine, but for very large tables where you might select a large number of cells, the performance could be an issue. A somewhat better performing implementation is to keep track of how many cells are selected for each row, and increment or decrement it:

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);
                }
            }
        }
    }
});

and then the row factory would be updated as follows:

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 ;
});


来源:https://stackoverflow.com/questions/42691019/get-selected-row-from-tableview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!