Why do I have to copy the list of selected items from a JavaFX TableView?

I have a TableView backed by an ObservableList:

private ObservableList<Renderer> renderers = FXCollections.observableArrayList();

@FXML
private TableView<Renderer> renderersTable;
@FXML
private TableColumn<Renderer, String> nameColumn;
@FXML
private TableColumn<Renderer, Boolean> approvedColumn;

@FXML
private void initialize() {
    nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
    approvedColumn.setCellValueFactory(new PropertyValueFactory<>("approved"));
    renderersTable.setItems(this.renderers);
}

The Renderer objects are very simple and look like this:

@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class Renderer extends Model {
    private String name;
    private boolean approved;
    // ...
}

In this piece of code I get all the selected items from the TableView and process them:

private void approveSelectedRenderers() {
    ObservableList<Renderer> selectedRenderers = renderersTable.getSelectionModel().getSelectedItems();

    for (Renderer renderer : selectedRenderers) {
        renderer.setApproved(true);
        renderers.set(renderers.indexOf(renderer), renderer);
    }
}

I have enabled multiple select and indeed selectedRenderers shows the appropriate count, but the loop is only executed once.

If instead I make a copy, like this:

private void approveSelectedRenderers() {        
    // Get all the selected renderers but copy them.
    List<Renderer> selectedRenderers = new ArrayList<>();          
    selectedRenderers.addAll(renderersTable.getSelectionModel().getSelectedItems());

    for (Renderer renderer : selectedRenderers) {
        renderer.setApproved(true);
        renderers.set(renderers.indexOf(renderer), renderer);
    }
}

it works correct and all items are processed. What's going on? What's the appropriate way of dealing with this?

1 answer

  • answered 2017-10-23 21:10 n247s

    Judging from your other question you are actually modifying the backing (observable) datastructure of the TableView. This means you are resetting the complete selected (observable) List be resetting the Renderer in the backing datastructure. Im kinda suprised it doesnt throw a ConcurrentModificationException.

    Solution: Simply dont modify the backing datastructure when looping over the selected List. It is also not nessecary to reset them after updating. If you change the value type (boolean) to SimpleBooleanProperty, you can pass it directly to the TableView (using the CellFactory). Changing the value of the SimpleBooleanProperty will automatically update the table for you.

    When using a SimpleBooleanProperty is not possible, you could also use the TableView#refresh() method. Be aware though that it will repopulate the entire TableView instead of just the modified elements.

    Further reading: Simular SO question