javajavafxselectionmodel

Custom TableViewSelectionModel not updating


I'm trying to write a custom TableViewSelectionModel to use with my TableView in JavaFX. I've implemented all the abstract methods, and am updating the selected items and selected cells lists. However, the actual selection being shown in the table does not change when I click on another row.

Here is a simplified example, which shows this behavior:

import java.util.*;
import javafx.scene.control.*;
import javafx.scene.control.TableView.TableViewSelectionModel;
import javafx.collections.*;
import javafx.beans.property.ReadOnlyListWrapper;

public class MySelectionModel<S> extends TableViewSelectionModel<S>{
    Set<Integer> selection = new HashSet<>();
    final ObservableList<TablePosition<S, ?>> selectedCells = FXCollections.<TablePosition<S, ?>>observableArrayList();
    final ObservableList<S> selectedItems = FXCollections.<S>observableArrayList();
    final ObservableList<Integer> selectedIndices = FXCollections.<Integer>observableArrayList();

    public MySelectionModel(TableView<S> tableview){
        super(tableview);
        setCellSelectionEnabled(false);
    }
    public ObservableList<S> getSelectedItems(){
        return new ReadOnlyListWrapper<S>(selectedItems);
    }
    public void clearSelection(int row, TableColumn<S, ?> tableColumn){
        selection.remove(Integer.valueOf(row));
        updateSelection();
    }
    public void clearAndSelect(int row, TableColumn<S, ?> tableColumn){
        selection = Collections.singleton(row);
        updateSelection();
    }
    public void select(int row, TableColumn<S, ?> tableColumn){
        selection.add(Integer.valueOf(row));
        updateSelection();
    }
    public boolean isSelected(int row, TableColumn<S, ?> tableColumn){
        return selection.contains(Integer.valueOf(row));
    }
    public ObservableList<TablePosition> getSelectedCells(){
        return new ReadOnlyListWrapper<TablePosition>((ObservableList<TablePosition>)(Object)selectedCells);
    }
    public ObservableList<Integer> getSelectedIndices(){
        return new ReadOnlyListWrapper<Integer>(selectedIndices);
    }
    public void selectBelowCell(){}
    public void selectAboveCell(){}
    public void selectRightCell(){}
    public void selectLeftCell(){}
    public void updateSelection(){
        List<TablePosition<S, ?>> positions = new ArrayList<>();
        List<S> items = new ArrayList<>();
        List<Integer> indices = new ArrayList<>();
        TableView<S> tableView = getTableView();
        for(Integer i : selection){
            positions.add(new TablePosition<S, Object>(tableView, i.intValue(), null));
            items.add(getTableView().getItems().get(i.intValue()));
            indices.add(i);
        }
        selectedCells.setAll(positions);
        selectedItems.setAll(items);
        selectedIndices.setAll(indices);
    }
}

Which is being set on the TableView like this:

TableView<ObservableList<MyData>> table = new TableView<>();
table.setSelectionModel(new MySelectionModel(table));
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

I've confirmed that the new indices are being correctly set by adding ListChangeListeners on the two backing ObservableLists and displaying the Changes. The output is what I'd expect, e.g.

Selected Cells { [TablePosition [ row: 8, column: null, tableView: TableView@11924c25[styleClass=table-view] ]] replaced by [TablePosition [ row: 12, column: null, tableView: TableView@11924c25[styleClass=table-view] ]] at 0 }

Selected Items { [[MyCustomTableView$Cell@1c988ebb, MyCustomTableView$Cell@6b80b2be]] replaced by [[MyCustomTableView$Cell@4a17d76d, MyCustomTableView$Cell@6e48d6d]] at 0 }

Is there anything I'm missing that would is needed for the TableView to update its selection when the TableViewSelectionModel changes its selection?


Solution

  • It seems the method you need to override, in addition to the ones you already have, is

    public void select(int row) ;
    

    which you can implement trivially by delegating to your existing select method:

    @Override
    public void select(int row) {
        select(row, null);
    }
    

    I would also recommend overriding the following:

    @Override
    public void clearAndSelect(int row) {
        clearAndSelect(row, null);
    }
    
    @Override
    public void selectRange(int start, int end) {
        IntStream.range(start, end).forEach(selection::add);
        updateSelection();
    }
    
    @Override
    public boolean isSelected(int row) {
        return isSelected(row, null);
    }
    

    Note also you have one bug. Collections.singleton(...) returns an unmodifiable list, so if you select a single row, then try to add items to the selection, you get an UnsupportedOperationException. Your clearAndSelect needs to be implemented as

    public void clearAndSelect(int row, TableColumn<S, ?> tableColumn){
        selection.clear();
        selection.add(row);
        updateSelection();
    }