Details
-
Bug
-
Resolution: Fixed
-
P3
-
8u20
-
Java 8u-dev repo tip, linux
Description
We have many ListSelectionListeners that do not inspect the passed Change parameter since we are simply always responding to any change the same way. Attempting to avoid premature optimization...
With a very recently built jfxrt.jar I am noticing an infinite loop in a ListSelectionListener on my TableView's SelectionModel's selected items list.
To reproduce run the following test class:
1. Change selection and notice that the selected item is printed to the console from the listener.
2. Hold down shift and click with the mouse to select a different row. The app will become unresponsive and the console should show an infinite loop of changes being printed.
As you can see the only difference is whether Change.next() is called inside the listener notification. If I had to guess it looks like this might have been caused by the fixes forRT-37395.
*********************************** Test Class **********************************************
import javafx.application.Application;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TableSelectionBug extends Application {
private boolean shiftDown_ = false;
// boilerplate to build table, add it to a stage, and show stage
public static void main(String[] args) {
launch(args);
}
@Override public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add( buildTable() );
primaryStage.setScene(new Scene(root, 250, 250));
EventHandler<KeyEvent> handler = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
shiftDown_ = event.isShiftDown();
}
};
primaryStage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, handler);
primaryStage.getScene().addEventHandler(KeyEvent.KEY_RELEASED, handler);
primaryStage.show();
}
ObjectProperty<Integer> offset_ = new SimpleObjectProperty<Integer>(0);
private TableView<?> buildTable() {
// create table with a bunch of rows and 1 column...
TableView<Integer> table = new TableView<>();
for ( int i = 1; i <= 50; i++ ) {
table.getItems().add(i);
}
final TableColumn<Integer, Integer> column = new TableColumn<>("Column");
table.getColumns().add( column );
column.setPrefWidth( 150 );
// each cell displays x, where x = "cell row number + offset"
column.setCellValueFactory( cdf -> new ObjectBinding<Integer>() {
{ super.bind( offset_ ); }
@Override protected Integer computeValue() {
return cdf.getValue() + offset_.get();
}
});
table.getSelectionModel().getSelectedItems().addListener(
new ListChangeListener<Integer>() {
public void onChanged(
ListChangeListener.Change<? extends Integer> c) {
if ( !shiftDown_ ) {
while ( c.next() ) {
}
}
System.out.println(
table.getSelectionModel().getSelectedItems());
}
});
return table;
}
}
With a very recently built jfxrt.jar I am noticing an infinite loop in a ListSelectionListener on my TableView's SelectionModel's selected items list.
To reproduce run the following test class:
1. Change selection and notice that the selected item is printed to the console from the listener.
2. Hold down shift and click with the mouse to select a different row. The app will become unresponsive and the console should show an infinite loop of changes being printed.
As you can see the only difference is whether Change.next() is called inside the listener notification. If I had to guess it looks like this might have been caused by the fixes for
*********************************** Test Class **********************************************
import javafx.application.Application;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TableSelectionBug extends Application {
private boolean shiftDown_ = false;
// boilerplate to build table, add it to a stage, and show stage
public static void main(String[] args) {
launch(args);
}
@Override public void start(Stage primaryStage) {
StackPane root = new StackPane();
root.getChildren().add( buildTable() );
primaryStage.setScene(new Scene(root, 250, 250));
EventHandler<KeyEvent> handler = new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
shiftDown_ = event.isShiftDown();
}
};
primaryStage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, handler);
primaryStage.getScene().addEventHandler(KeyEvent.KEY_RELEASED, handler);
primaryStage.show();
}
ObjectProperty<Integer> offset_ = new SimpleObjectProperty<Integer>(0);
private TableView<?> buildTable() {
// create table with a bunch of rows and 1 column...
TableView<Integer> table = new TableView<>();
for ( int i = 1; i <= 50; i++ ) {
table.getItems().add(i);
}
final TableColumn<Integer, Integer> column = new TableColumn<>("Column");
table.getColumns().add( column );
column.setPrefWidth( 150 );
// each cell displays x, where x = "cell row number + offset"
column.setCellValueFactory( cdf -> new ObjectBinding<Integer>() {
{ super.bind( offset_ ); }
@Override protected Integer computeValue() {
return cdf.getValue() + offset_.get();
}
});
table.getSelectionModel().getSelectedItems().addListener(
new ListChangeListener<Integer>() {
public void onChanged(
ListChangeListener.Change<? extends Integer> c) {
if ( !shiftDown_ ) {
while ( c.next() ) {
}
}
System.out.println(
table.getSelectionModel().getSelectedItems());
}
});
return table;
}
}