Search code examples
javafxtableviewselecteditemmultipleselectionchangelistener

JavaFX TableView with ChangeListener for multiple selection


I want to listen for selection changes on a table view in JavaFX 8. If I add a ChangeListener to the selectedItemProperty (or selectedIndexProperty) of the MultipleSelectionModel it does not fire in the following case: Multiple lines are already selected and now I select a new single line. If this line is the last line that has been selected before, it does not react. This is because the selectedItemProperty contains only one item and not the list of all that are selected. But unfortunately there is no selectedItemsProperty which is strange because the method getSelectedItems() exists. I don't know where to add the listener instead to make it work in any case.

Here is an example code:

import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class MultipleSelectionTest extends Application
{
    private ListView<Integer> list = new ListView<>();
    private TableView<String> table = new TableView<>();
    private TableColumn<String, String> column1 = new TableColumn<>("Numbers");

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        HBox box = new HBox(list, table);
        primaryStage.setScene(new Scene(box, 300, 500));
        initializeTable();
        initializeList();
        primaryStage.show();
    }

    private void initializeTable()
    {
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        table.getColumns().add(column1);
        column1.setCellValueFactory(s -> new SimpleStringProperty(s.getValue()));
        table.setItems(FXCollections.observableArrayList("One", "Two", "Three", "Four", "Five", "Six", "Seven"));
    }

    private void initializeList()
    {
        table.getSelectionModel().selectedItemProperty().addListener((property, oldValue, newValue) ->
        {
            ObservableList<Integer> convertedItems = FXCollections.observableList(
                table.getSelectionModel().getSelectedItems()
                .stream()
                .map(this::convertNumber)
                .collect(Collectors.toList()));

            list.setItems(convertedItems);
        });
    }

    private int convertNumber(String expression)
    {
        switch (expression)
        {
            case "One": return 1;
            case "Two": return 2;
            case "Three": return 3;
            case "Four": return 4;
            case "Five": return 5;
            case "Six": return 6;
            case "Seven": return 7;
            default: throw new IllegalArgumentException();
        }
    }

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

Note that the ListView in this sample is only for visual purposes.


Solution

  • You don't need a selectedItemsProperty - getSelectedItems returns an ObservableList to which you can add a listener.

    Having a property for this would imply the list object changed, but what in fact happens is that the list stays the same object, but it's contents change .