-
Bug
-
Resolution: Fixed
-
P3
-
8u77
-
x86_64
-
windows_7
FULL PRODUCT VERSION :
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
A table view has many items will reuse the row/cell components, but it seems that after a combination of scrolling and filtering actions, cell indices are not correctly determined, and this prevents selecting / clearing the selection of the remaining rows.
If the user scrolls to the bottom of a table view with the mouse wheel (not by using the scroll bar!), selects a row at the bottom, filters the table data so that the selected row remains visible, and then tries to select (if not selected*), or deselect (if selected*) the row, the user will notice that this selection action will not be completed.
I tried to track the problem, and it seems that TableCellBehaviorBase.doSelect() does not finish the selection because cell index is larger than row count. Even though the number of rows was reduced, the cell index remains equal to the one before filtering.
This issue is noticeable for cell selection, but row selection is unaffected. Clicking in the space next to actual cells delivers the expected result. Also, the scenario cannot be reproduced if the user scrolls by dragging the scroll bar.
_____________
* My sample program has a flag that allows the rows to be deselected before filtering is applied. In this case, after filtering there will be no selection, and if the user tries to select the remaining row, it will be impossible. Alternatively, if the flag is set to false, the selected row is still selected after filtering, but Ctrl+Click in order to clear selection will not work.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. create a table with many items, more than the number of visible rows (the table must have a filterable list of items)
2. scroll with the mouse wheel until the end of the table view (not with the scroll bar!)
3. select the last row
4. filter the items so that only the item corresponding to the selected row remains
5. try to select (if not selected), or deselect (if selected) the remaining row, by clicking table any table cell (not the empty part of the row!)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Selection on the remaining row should function as expected. it should be made or cleared, depending on the state of the row.
ACTUAL -
Selection is unaffected: cells clicks don't produce any result.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.function.Predicate;
import java.util.stream.IntStream;
public class FilteredTableViewScrollingIssue extends Application {
private static final boolean CLEAR_SELECTION_ENABLED = true;
private TableView<Item> tableView;
@Override
public void start(final Stage primaryStage) throws Exception {
final Predicate<Item> acceptPredicate = item -> true;
final Predicate<Item> value2Predicate = item -> item.getIntegerProperty().getValue() == 2;
tableView = new TableView<>();
final TableColumn<Item, Integer> column = new TableColumn<>("Value");
column.setCellValueFactory(param -> param.getValue().getIntegerProperty());
tableView.getColumns().add(column);
final ObservableList<Item> items = FXCollections.observableArrayList();
IntStream.range(0, 20)
.forEach(value -> items.add(new Item(1)));
items.add(new Item(2));
final FilteredList<Item> filteredItems = items.filtered(acceptPredicate);
final SortedList<Item> sortedFilteredItems = filteredItems.sorted();
sortedFilteredItems.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedFilteredItems);
final Button setFilterButton = new Button("Set filter (value = 2)");
setFilterButton.setOnAction(e -> {
processSelection();
filteredItems.setPredicate(value2Predicate);
});
final Button clearFilterButton = new Button("Clear filter");
clearFilterButton.setOnAction(e -> {
processSelection();
filteredItems.setPredicate(acceptPredicate);
});
final HBox buttonBox = new HBox(5, setFilterButton, clearFilterButton);
buttonBox.setPadding(new Insets(10));
buttonBox.setAlignment(Pos.CENTER);
final BorderPane borderPane = new BorderPane();
borderPane.setCenter(tableView);
borderPane.setBottom(buttonBox);
primaryStage.setTitle("Table View Filtering & Scrolling Issue");
primaryStage.setScene(new Scene(borderPane, 600, 400));
primaryStage.setResizable(false); // resizing must be prevented, because it affects the number of allocated rows in the table view
primaryStage.show();
}
private void processSelection() {
if (CLEAR_SELECTION_ENABLED) {
tableView.getSelectionModel().clearSelection();
}
}
private static class Item {
private final SimpleObjectProperty<Integer> integerProperty;
Item(final int value) {
integerProperty = new SimpleObjectProperty<>(value);
}
ObservableValue<Integer> getIntegerProperty() {
return integerProperty;
}
}
public static void main(String[] args) {
launch();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One possible workaround is to acces the virtual flow that backs the table view. Right after applying the new filtering, calling VirtualFlow.requestCellLayout() seems to eliminate the problem, but it might have a performance impact.
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
A table view has many items will reuse the row/cell components, but it seems that after a combination of scrolling and filtering actions, cell indices are not correctly determined, and this prevents selecting / clearing the selection of the remaining rows.
If the user scrolls to the bottom of a table view with the mouse wheel (not by using the scroll bar!), selects a row at the bottom, filters the table data so that the selected row remains visible, and then tries to select (if not selected*), or deselect (if selected*) the row, the user will notice that this selection action will not be completed.
I tried to track the problem, and it seems that TableCellBehaviorBase.doSelect() does not finish the selection because cell index is larger than row count. Even though the number of rows was reduced, the cell index remains equal to the one before filtering.
This issue is noticeable for cell selection, but row selection is unaffected. Clicking in the space next to actual cells delivers the expected result. Also, the scenario cannot be reproduced if the user scrolls by dragging the scroll bar.
_____________
* My sample program has a flag that allows the rows to be deselected before filtering is applied. In this case, after filtering there will be no selection, and if the user tries to select the remaining row, it will be impossible. Alternatively, if the flag is set to false, the selected row is still selected after filtering, but Ctrl+Click in order to clear selection will not work.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. create a table with many items, more than the number of visible rows (the table must have a filterable list of items)
2. scroll with the mouse wheel until the end of the table view (not with the scroll bar!)
3. select the last row
4. filter the items so that only the item corresponding to the selected row remains
5. try to select (if not selected), or deselect (if selected) the remaining row, by clicking table any table cell (not the empty part of the row!)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Selection on the remaining row should function as expected. it should be made or cleared, depending on the state of the row.
ACTUAL -
Selection is unaffected: cells clicks don't produce any result.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.function.Predicate;
import java.util.stream.IntStream;
public class FilteredTableViewScrollingIssue extends Application {
private static final boolean CLEAR_SELECTION_ENABLED = true;
private TableView<Item> tableView;
@Override
public void start(final Stage primaryStage) throws Exception {
final Predicate<Item> acceptPredicate = item -> true;
final Predicate<Item> value2Predicate = item -> item.getIntegerProperty().getValue() == 2;
tableView = new TableView<>();
final TableColumn<Item, Integer> column = new TableColumn<>("Value");
column.setCellValueFactory(param -> param.getValue().getIntegerProperty());
tableView.getColumns().add(column);
final ObservableList<Item> items = FXCollections.observableArrayList();
IntStream.range(0, 20)
.forEach(value -> items.add(new Item(1)));
items.add(new Item(2));
final FilteredList<Item> filteredItems = items.filtered(acceptPredicate);
final SortedList<Item> sortedFilteredItems = filteredItems.sorted();
sortedFilteredItems.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedFilteredItems);
final Button setFilterButton = new Button("Set filter (value = 2)");
setFilterButton.setOnAction(e -> {
processSelection();
filteredItems.setPredicate(value2Predicate);
});
final Button clearFilterButton = new Button("Clear filter");
clearFilterButton.setOnAction(e -> {
processSelection();
filteredItems.setPredicate(acceptPredicate);
});
final HBox buttonBox = new HBox(5, setFilterButton, clearFilterButton);
buttonBox.setPadding(new Insets(10));
buttonBox.setAlignment(Pos.CENTER);
final BorderPane borderPane = new BorderPane();
borderPane.setCenter(tableView);
borderPane.setBottom(buttonBox);
primaryStage.setTitle("Table View Filtering & Scrolling Issue");
primaryStage.setScene(new Scene(borderPane, 600, 400));
primaryStage.setResizable(false); // resizing must be prevented, because it affects the number of allocated rows in the table view
primaryStage.show();
}
private void processSelection() {
if (CLEAR_SELECTION_ENABLED) {
tableView.getSelectionModel().clearSelection();
}
}
private static class Item {
private final SimpleObjectProperty<Integer> integerProperty;
Item(final int value) {
integerProperty = new SimpleObjectProperty<>(value);
}
ObservableValue<Integer> getIntegerProperty() {
return integerProperty;
}
}
public static void main(String[] args) {
launch();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
One possible workaround is to acces the virtual flow that backs the table view. Right after applying the new filtering, calling VirtualFlow.requestCellLayout() seems to eliminate the problem, but it might have a performance impact.