When adding a listener to a table's selection, the ChangeObject passed into the listener will have wasRemoved() == true, but calling getRemoved() will result in an exception.
The following example illustrates the bug: It will create a table of 50 persons. When a column header is clicked, the following exception is thrown:
Exception in thread "JavaFX Application Thread" java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(AbstractList.java:364)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2981)
at java.io.PrintStream.println(PrintStream.java:821)
at prv.rli.codetest.TableViewSelectionBug.selectionChanged(TableViewSelectionBug.java:67)
at prv.rli.codetest.TableViewSelectionBug$$Lambda$97/947243259.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(ReadOnlyUnbackedObservableList.java:75)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.handleSelectedCellsListChangeEvent(TableView.java:2848)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.lambda$new$47(TableView.java:1984)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel$$Lambda$81/1505799527.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.transformation.SortedList.sourceChanged(SortedList.java:108)
at javafx.collections.transformation.TransformationList.lambda$getListener$16(TransformationList.java:106)
at javafx.collections.transformation.TransformationList$$Lambda$83/1095537600.onChanged(Unknown Source)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.clear(ObservableListWrapper.java:157)
at com.sun.javafx.scene.control.SelectedCellsMap.clear(SelectedCellsMap.java:183)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.quietClearSelection(TableView.java:2561)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.clearSelection(TableView.java:2557)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel$3.onChanged(TableView.java:2036)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.clear(ObservableListWrapper.java:157)
at prv.rli.codetest.TableViewSelectionBug.sortOrderChanged(TableViewSelectionBug.java:60)
at prv.rli.codetest.TableViewSelectionBug.lambda$0(TableViewSelectionBug.java:34)
at prv.rli.codetest.TableViewSelectionBug$$Lambda$96/411038885.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.setAll(ModifiableObservableListBase.java:90)
at javafx.collections.ObservableListBase.setAll(ObservableListBase.java:250)
at com.sun.javafx.scene.control.skin.TableColumnHeader.sortColumn(TableColumnHeader.java:793)
at com.sun.javafx.scene.control.skin.TableColumnHeader.lambda$static$54(TableColumnHeader.java:241)
at com.sun.javafx.scene.control.skin.TableColumnHeader$$Lambda$153/1625749590.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3724)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3452)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1728)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2461)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:348)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:273)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:382)
at com.sun.glass.ui.View.handleMouseEvent(View.java:553)
at com.sun.glass.ui.View.notifyMouse(View.java:925)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.lambda$null$45(GtkApplication.java:126)
at com.sun.glass.ui.gtk.GtkApplication$$Lambda$42/1099983479.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Here's the example:
package prv.rli.codetest;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TableViewSelectionBug extends Application {
ObservableList<Person> items = FXCollections.observableArrayList();
TableView<Person> table = new TableView<Person>();
public TableViewSelectionBug() {
}
@Override
public void start(Stage primaryStage) throws Exception {
table.setItems(items);
TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
table.getColumns().setAll(firstNameCol, lastNameCol);
table.setSortPolicy(this::sortPolicy);
table.getSortOrder().addListener((Change<? extends TableColumn<Person, ?>> c) -> { sortOrderChanged(); });
table.getSelectionModel().getSelectedItems().addListener(this::selectionChanged);
addPersons();
Scene scene = new Scene(table, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
// simulate server call, usually this would take sort order property into account
private void addPersons() {
for (int i = 0; i < 50; i++) {
Person p = new Person();
int idx = items.size();
p.setFirstName(idx + " John");
p.setLastName(idx + "Doe");
items.add(p);
}
}
private Boolean sortPolicy(TableView<Person> table) {
return true;
}
private void sortOrderChanged() {
items.clear();
addPersons();
}
private void selectionChanged(Change<? extends Person> c) {
while (c.next()) {
if (c.wasRemoved()) {
System.out.println(c.getRemoved());
// ^^^ will throw Exception in thread "JavaFX Application Thread" java.util.NoSuchElementException, when a column header is clicked
}
}
}
public static void main(String[] args) {
launch(args);
}
public class Person {
private StringProperty firstName;
public void setFirstName(String value) { firstNameProperty().set(value); }
public String getFirstName() { return firstNameProperty().get(); }
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
private StringProperty lastName;
public void setLastName(String value) { lastNameProperty().set(value); }
public String getLastName() { return lastNameProperty().get(); }
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
}
}
The following example illustrates the bug: It will create a table of 50 persons. When a column header is clicked, the following exception is thrown:
Exception in thread "JavaFX Application Thread" java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(AbstractList.java:364)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2981)
at java.io.PrintStream.println(PrintStream.java:821)
at prv.rli.codetest.TableViewSelectionBug.selectionChanged(TableViewSelectionBug.java:67)
at prv.rli.codetest.TableViewSelectionBug$$Lambda$97/947243259.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(ReadOnlyUnbackedObservableList.java:75)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.handleSelectedCellsListChangeEvent(TableView.java:2848)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.lambda$new$47(TableView.java:1984)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel$$Lambda$81/1505799527.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.transformation.SortedList.sourceChanged(SortedList.java:108)
at javafx.collections.transformation.TransformationList.lambda$getListener$16(TransformationList.java:106)
at javafx.collections.transformation.TransformationList$$Lambda$83/1095537600.onChanged(Unknown Source)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.clear(ObservableListWrapper.java:157)
at com.sun.javafx.scene.control.SelectedCellsMap.clear(SelectedCellsMap.java:183)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.quietClearSelection(TableView.java:2561)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel.clearSelection(TableView.java:2557)
at javafx.scene.control.TableView$TableViewArrayListSelectionModel$3.onChanged(TableView.java:2036)
at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at com.sun.javafx.collections.ObservableListWrapper.clear(ObservableListWrapper.java:157)
at prv.rli.codetest.TableViewSelectionBug.sortOrderChanged(TableViewSelectionBug.java:60)
at prv.rli.codetest.TableViewSelectionBug.lambda$0(TableViewSelectionBug.java:34)
at prv.rli.codetest.TableViewSelectionBug$$Lambda$96/411038885.onChanged(Unknown Source)
at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.setAll(ModifiableObservableListBase.java:90)
at javafx.collections.ObservableListBase.setAll(ObservableListBase.java:250)
at com.sun.javafx.scene.control.skin.TableColumnHeader.sortColumn(TableColumnHeader.java:793)
at com.sun.javafx.scene.control.skin.TableColumnHeader.lambda$static$54(TableColumnHeader.java:241)
at com.sun.javafx.scene.control.skin.TableColumnHeader$$Lambda$153/1625749590.handle(Unknown Source)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3724)
at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3452)
at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1728)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2461)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:348)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:273)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:382)
at com.sun.glass.ui.View.handleMouseEvent(View.java:553)
at com.sun.glass.ui.View.notifyMouse(View.java:925)
at com.sun.glass.ui.gtk.GtkApplication._runLoop(Native Method)
at com.sun.glass.ui.gtk.GtkApplication.lambda$null$45(GtkApplication.java:126)
at com.sun.glass.ui.gtk.GtkApplication$$Lambda$42/1099983479.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Here's the example:
package prv.rli.codetest;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class TableViewSelectionBug extends Application {
ObservableList<Person> items = FXCollections.observableArrayList();
TableView<Person> table = new TableView<Person>();
public TableViewSelectionBug() {
}
@Override
public void start(Stage primaryStage) throws Exception {
table.setItems(items);
TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
table.getColumns().setAll(firstNameCol, lastNameCol);
table.setSortPolicy(this::sortPolicy);
table.getSortOrder().addListener((Change<? extends TableColumn<Person, ?>> c) -> { sortOrderChanged(); });
table.getSelectionModel().getSelectedItems().addListener(this::selectionChanged);
addPersons();
Scene scene = new Scene(table, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
// simulate server call, usually this would take sort order property into account
private void addPersons() {
for (int i = 0; i < 50; i++) {
Person p = new Person();
int idx = items.size();
p.setFirstName(idx + " John");
p.setLastName(idx + "Doe");
items.add(p);
}
}
private Boolean sortPolicy(TableView<Person> table) {
return true;
}
private void sortOrderChanged() {
items.clear();
addPersons();
}
private void selectionChanged(Change<? extends Person> c) {
while (c.next()) {
if (c.wasRemoved()) {
System.out.println(c.getRemoved());
// ^^^ will throw Exception in thread "JavaFX Application Thread" java.util.NoSuchElementException, when a column header is clicked
}
}
}
public static void main(String[] args) {
launch(args);
}
public class Person {
private StringProperty firstName;
public void setFirstName(String value) { firstNameProperty().set(value); }
public String getFirstName() { return firstNameProperty().get(); }
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
private StringProperty lastName;
public void setLastName(String value) { lastNameProperty().set(value); }
public String getLastName() { return lastNameProperty().get(); }
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
}
}