-
Bug
-
Resolution: Cannot Reproduce
-
P4
-
7u25
Scrolling and vertical resizing results in VirtualFlow.sheet growing. VirtualFlow.sheet can only be cleared through TableViewSkin.updateRowCount()
TableViewSkin.updateRowCount() is only called when TableViewSkin.rowCountDirty == true
TableViewSkin.rowCountDirty is only set to true if table items change or if TableViewSkin.refreshView() is called.
So, when the items do not change VirtualFlow.sheet can only be cleared through TableViewSkin.refreshView().
TableViewSkin.refreshView() is called when the "TableView.refresh" property change but this property does not change when scrolling and resizing a TableView.
The example below will animate the max height of the TableView and print the VirtualFlow.sheet child count to the console:
---------------------------------------------------------------------------------------
package fxtest;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import com.sun.javafx.scene.control.skin.VirtualFlow;
/**
* When the selected row is removed the selection is rendered on another row but there is no notification
*/
public class TableVFlowMemLeak extends Application {
static class RowItem {
public final String name;
public DoubleProperty val = new SimpleDoubleProperty();
public RowItem(final String n, final double p) {
name = n;
val.set(p);
}
}
private static int nextRowId = 1;
private static RowItem nextItem() {
return new RowItem("Row" + String.valueOf(++nextRowId), nextRowId * 10.0);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final TableView<RowItem> tableView = new TableView<RowItem>();
final Button updateButton = new Button("Set TableView.refresh Property");
updateButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent actionEvent) {
tableView.getProperties().put("TableView.refresh", "true");
}
});
final TableColumn<RowItem, String> nameCol = new TableColumn<RowItem, String>("Item");
final TableColumn<RowItem, Number> valueCol = new TableColumn<RowItem, Number>("Value");
nameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(final CellDataFeatures<RowItem, String> cellData) {
return new ReadOnlyStringWrapper(cellData.getValue().name);
}
});
valueCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, Number>, ObservableValue<Number>>() {
@Override
public ObservableValue<Number> call(final CellDataFeatures<RowItem, Number> cellData) {
return cellData.getValue().val;
};
});
tableView.getColumns().addAll(nameCol, valueCol);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
for (int i = 0; i < 100; i++) {
tableView.getItems().add(nextItem());
}
final VBox tb = VBoxBuilder.create().children(tableView).build();
final VBox vb = VBoxBuilder.create().children(tb, updateButton).build();
VBox.setVgrow(tb, Priority.ALWAYS);
VBox.setVgrow(tableView, Priority.ALWAYS);
primaryStage.setScene(new Scene(vb));
primaryStage.setHeight(500.0);
primaryStage.show();
VirtualFlow flow = (VirtualFlow) tableView.lookup("VirtualFlow");
Region clip = (Region) flow.getChildrenUnmodifiable().get(0);
System.out.println("ClippedContainer: " + clip.getClass().getName());
final Group clipNode = (Group) clip.getChildrenUnmodifiable().get(0);
final Group sheet = (Group) clipNode.getChildrenUnmodifiable().get(0);
System.out.println("Sheet child count: " + sheet.getChildren().size());
sheet.getChildren().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
System.out.println("Sheet child count: " + sheet.getChildren().size());
}
});
Timeline heightAnim = TimelineBuilder.create()
.cycleCount(50)
.keyFrames(
new KeyFrame(Duration.seconds(0.0), new KeyValue(tableView.maxHeightProperty(), 0.0)),
new KeyFrame(Duration.seconds(0.5), new KeyValue(tableView.maxHeightProperty(), 0.0)),
new KeyFrame(Duration.seconds(1.0), new KeyValue(tableView.maxHeightProperty(), 800.0)),
new KeyFrame(Duration.seconds(1.5), new KeyValue(tableView.maxHeightProperty(), 800.0))
)
.build();
heightAnim.play();
}
public static void main(final String args[]) {
launch(args);
}
}
TableViewSkin.updateRowCount() is only called when TableViewSkin.rowCountDirty == true
TableViewSkin.rowCountDirty is only set to true if table items change or if TableViewSkin.refreshView() is called.
So, when the items do not change VirtualFlow.sheet can only be cleared through TableViewSkin.refreshView().
TableViewSkin.refreshView() is called when the "TableView.refresh" property change but this property does not change when scrolling and resizing a TableView.
The example below will animate the max height of the TableView and print the VirtualFlow.sheet child count to the console:
---------------------------------------------------------------------------------------
package fxtest;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import com.sun.javafx.scene.control.skin.VirtualFlow;
/**
* When the selected row is removed the selection is rendered on another row but there is no notification
*/
public class TableVFlowMemLeak extends Application {
static class RowItem {
public final String name;
public DoubleProperty val = new SimpleDoubleProperty();
public RowItem(final String n, final double p) {
name = n;
val.set(p);
}
}
private static int nextRowId = 1;
private static RowItem nextItem() {
return new RowItem("Row" + String.valueOf(++nextRowId), nextRowId * 10.0);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final TableView<RowItem> tableView = new TableView<RowItem>();
final Button updateButton = new Button("Set TableView.refresh Property");
updateButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(final ActionEvent actionEvent) {
tableView.getProperties().put("TableView.refresh", "true");
}
});
final TableColumn<RowItem, String> nameCol = new TableColumn<RowItem, String>("Item");
final TableColumn<RowItem, Number> valueCol = new TableColumn<RowItem, Number>("Value");
nameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(final CellDataFeatures<RowItem, String> cellData) {
return new ReadOnlyStringWrapper(cellData.getValue().name);
}
});
valueCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<RowItem, Number>, ObservableValue<Number>>() {
@Override
public ObservableValue<Number> call(final CellDataFeatures<RowItem, Number> cellData) {
return cellData.getValue().val;
};
});
tableView.getColumns().addAll(nameCol, valueCol);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
for (int i = 0; i < 100; i++) {
tableView.getItems().add(nextItem());
}
final VBox tb = VBoxBuilder.create().children(tableView).build();
final VBox vb = VBoxBuilder.create().children(tb, updateButton).build();
VBox.setVgrow(tb, Priority.ALWAYS);
VBox.setVgrow(tableView, Priority.ALWAYS);
primaryStage.setScene(new Scene(vb));
primaryStage.setHeight(500.0);
primaryStage.show();
VirtualFlow flow = (VirtualFlow) tableView.lookup("VirtualFlow");
Region clip = (Region) flow.getChildrenUnmodifiable().get(0);
System.out.println("ClippedContainer: " + clip.getClass().getName());
final Group clipNode = (Group) clip.getChildrenUnmodifiable().get(0);
final Group sheet = (Group) clipNode.getChildrenUnmodifiable().get(0);
System.out.println("Sheet child count: " + sheet.getChildren().size());
sheet.getChildren().addListener(new InvalidationListener() {
@Override
public void invalidated(Observable arg0) {
System.out.println("Sheet child count: " + sheet.getChildren().size());
}
});
Timeline heightAnim = TimelineBuilder.create()
.cycleCount(50)
.keyFrames(
new KeyFrame(Duration.seconds(0.0), new KeyValue(tableView.maxHeightProperty(), 0.0)),
new KeyFrame(Duration.seconds(0.5), new KeyValue(tableView.maxHeightProperty(), 0.0)),
new KeyFrame(Duration.seconds(1.0), new KeyValue(tableView.maxHeightProperty(), 800.0)),
new KeyFrame(Duration.seconds(1.5), new KeyValue(tableView.maxHeightProperty(), 800.0))
)
.build();
heightAnim.play();
}
public static void main(final String args[]) {
launch(args);
}
}