1

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?

Community
  • 1
  • 1
resueman
  • 10,572
  • 10
  • 31
  • 45
  • Are you implementing `getSelectedIndices()`, or modifying that list when the selection changes? – James_D Nov 15 '15 at 04:23
  • @James_D I had not been, but I've added it in, and it's still showing the same problem. – resueman Nov 15 '15 at 04:28
  • Probably needs a [MCVE]. (I know it's hard to be minimal with a `TableViewSelectionModel` implementation.) – James_D Nov 15 '15 at 05:05
  • @James_D I hoped that wouldn't be necessary. Too many methods to keep it short :P I've added the shortest reproducible version I can. – resueman Nov 15 '15 at 05:38

1 Answers1

1

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();
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Somehow, this works for the example I posted here, but not for my actual code :P. But it should be easy enough to fix now that I have a working example. Thank you for the solution. – resueman Nov 16 '15 at 23:12