ADDITIONAL SYSTEM INFORMATION :
Windows 11
Java 11
JavaFX 17.0.9
A DESCRIPTION OF THE PROBLEM :
Whenever a TableView is collapsed to zero height and then re-expanded, such as when it is inside a TitledPane, the table's cells are duplicated. The old cells are no longer in the table's VirtualFlow, but they still exist (likely a memory leak) and respond to mouse events, which causes unwanted side effects. In our use case, a custom text field is used to display a selection dialog whenever the cell is edited, so this duplicated cell issue leads to multiple dialogs being displayed when a cell is edited. However, the problem can also be seen with stock table cells such as TextFieldTableCell.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the included example and perform the following steps:
1. Edit the 'Number' cell for 'Alpha', changing the value from 1 to 5.
2. Collapse the TitledPane
3. Expand the TitledPane
4. Edit the 'Number' cell for 'Alpha', changing the value from 5 to 6.
5. Collapse the TitledPane
6. Expand the TitledPane
7. Edit the 'Number' cell for 'Alpha', changing the value from 6 to 7.
8. Collapse the TitledPane
9. Expand the TitledPane
10. Edit the 'Number' cell for 'Alpha', changing the value from 7 to 8.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The output of the program should be:
startEdit number: 1
changed number: 5
titledPane collapsed
titledPane expanded
startEdit number: 5
changed number: 6
titledPane collapsed
titledPane expanded
startEdit number: 6
changed number: 7
titledPane collapsed
titledPane expanded
startEdit number: 7
changed number: 8
ACTUAL -
The output of the program actually is:
startEdit number: 1
changed number: 5
titledPane collapsed
titledPane expanded
startEdit number: 5
startEdit number: 5
changed number: 6
titledPane collapsed
titledPane expanded
startEdit number: 5
startEdit number: 6
startEdit number: 6
changed number: 7
titledPane collapsed
titledPane expanded
startEdit number: 6
startEdit number: 6
startEdit number: 7
startEdit number: 7
changed number: 8
You can see here that each time the TitledPane is collapsed and expanded again, additional 'startEdit' events are shown in the output. These additional events come from the duplicated cells.
---------- BEGIN SOURCE ----------
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.util.Arrays;
public class JavaFXSample extends Application
{
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage)
{
StackPane root = new StackPane();
root.setAlignment(Pos.TOP_CENTER);
Scene scene = new Scene(root, 250, 250);
createNodes(root);
stage.setTitle("JavaFX Sample");
stage.setScene(scene);
stage.show();
}
private void createNodes(Pane root)
{
TableColumn<SimpleTableItem, String> nameTableColumn = new TableColumn<>("Name");
nameTableColumn.setCellValueFactory(p -> p.getValue().nameProperty());
TableColumn<SimpleTableItem, Integer> numberTableColumn = new TableColumn<>("Number");
numberTableColumn.setCellValueFactory(p -> p.getValue().numberProperty().asObject());
numberTableColumn.setCellFactory(column -> new TestTableCell<>());
TableView<SimpleTableItem> tableView = new TableView<>(FXCollections.observableArrayList(
new SimpleTableItem("Alpha", 1),
new SimpleTableItem("Beta", 2),
new SimpleTableItem("Gamma", 3),
new SimpleTableItem("Delta", 4)));
tableView.getColumns().setAll(Arrays.asList(nameTableColumn, numberTableColumn));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setEditable(true);
TitledPane titledPane = new TitledPane("TitledPane", tableView);
titledPane.expandedProperty().addListener((observable, oldValue, newValue) ->
System.out.println("titledPane " + (newValue ? "expanded" : "collapsed")));
root.getChildren().add(titledPane);
}
static class TestTableCell<S> extends TextFieldTableCell<S, Integer>
{
TestTableCell()
{
super(new IntegerStringConverter());
}
@Override
public void startEdit()
{
System.out.println("startEdit number: " + getItem());
super.startEdit();
}
}
private static class SimpleTableItem
{
SimpleTableItem(String name, int number)
{
this.name.set(name);
this.number.set(number);
numberProperty().addListener((observable, oldValue, newValue) ->
System.out.println("changed number: " + newValue));
}
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name");
ReadOnlyStringProperty nameProperty()
{
return name.getReadOnlyProperty();
}
private final IntegerProperty number = new SimpleIntegerProperty(this, "discrete");
IntegerProperty numberProperty()
{
return number;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
None found so far.
FREQUENCY : always
Windows 11
Java 11
JavaFX 17.0.9
A DESCRIPTION OF THE PROBLEM :
Whenever a TableView is collapsed to zero height and then re-expanded, such as when it is inside a TitledPane, the table's cells are duplicated. The old cells are no longer in the table's VirtualFlow, but they still exist (likely a memory leak) and respond to mouse events, which causes unwanted side effects. In our use case, a custom text field is used to display a selection dialog whenever the cell is edited, so this duplicated cell issue leads to multiple dialogs being displayed when a cell is edited. However, the problem can also be seen with stock table cells such as TextFieldTableCell.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the included example and perform the following steps:
1. Edit the 'Number' cell for 'Alpha', changing the value from 1 to 5.
2. Collapse the TitledPane
3. Expand the TitledPane
4. Edit the 'Number' cell for 'Alpha', changing the value from 5 to 6.
5. Collapse the TitledPane
6. Expand the TitledPane
7. Edit the 'Number' cell for 'Alpha', changing the value from 6 to 7.
8. Collapse the TitledPane
9. Expand the TitledPane
10. Edit the 'Number' cell for 'Alpha', changing the value from 7 to 8.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The output of the program should be:
startEdit number: 1
changed number: 5
titledPane collapsed
titledPane expanded
startEdit number: 5
changed number: 6
titledPane collapsed
titledPane expanded
startEdit number: 6
changed number: 7
titledPane collapsed
titledPane expanded
startEdit number: 7
changed number: 8
ACTUAL -
The output of the program actually is:
startEdit number: 1
changed number: 5
titledPane collapsed
titledPane expanded
startEdit number: 5
startEdit number: 5
changed number: 6
titledPane collapsed
titledPane expanded
startEdit number: 5
startEdit number: 6
startEdit number: 6
changed number: 7
titledPane collapsed
titledPane expanded
startEdit number: 6
startEdit number: 6
startEdit number: 7
startEdit number: 7
changed number: 8
You can see here that each time the TitledPane is collapsed and expanded again, additional 'startEdit' events are shown in the output. These additional events come from the duplicated cells.
---------- BEGIN SOURCE ----------
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;
import java.util.Arrays;
public class JavaFXSample extends Application
{
public static void main(String[] args) {
launch();
}
@Override
public void start(Stage stage)
{
StackPane root = new StackPane();
root.setAlignment(Pos.TOP_CENTER);
Scene scene = new Scene(root, 250, 250);
createNodes(root);
stage.setTitle("JavaFX Sample");
stage.setScene(scene);
stage.show();
}
private void createNodes(Pane root)
{
TableColumn<SimpleTableItem, String> nameTableColumn = new TableColumn<>("Name");
nameTableColumn.setCellValueFactory(p -> p.getValue().nameProperty());
TableColumn<SimpleTableItem, Integer> numberTableColumn = new TableColumn<>("Number");
numberTableColumn.setCellValueFactory(p -> p.getValue().numberProperty().asObject());
numberTableColumn.setCellFactory(column -> new TestTableCell<>());
TableView<SimpleTableItem> tableView = new TableView<>(FXCollections.observableArrayList(
new SimpleTableItem("Alpha", 1),
new SimpleTableItem("Beta", 2),
new SimpleTableItem("Gamma", 3),
new SimpleTableItem("Delta", 4)));
tableView.getColumns().setAll(Arrays.asList(nameTableColumn, numberTableColumn));
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setEditable(true);
TitledPane titledPane = new TitledPane("TitledPane", tableView);
titledPane.expandedProperty().addListener((observable, oldValue, newValue) ->
System.out.println("titledPane " + (newValue ? "expanded" : "collapsed")));
root.getChildren().add(titledPane);
}
static class TestTableCell<S> extends TextFieldTableCell<S, Integer>
{
TestTableCell()
{
super(new IntegerStringConverter());
}
@Override
public void startEdit()
{
System.out.println("startEdit number: " + getItem());
super.startEdit();
}
}
private static class SimpleTableItem
{
SimpleTableItem(String name, int number)
{
this.name.set(name);
this.number.set(number);
numberProperty().addListener((observable, oldValue, newValue) ->
System.out.println("changed number: " + newValue));
}
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name");
ReadOnlyStringProperty nameProperty()
{
return name.getReadOnlyProperty();
}
private final IntegerProperty number = new SimpleIntegerProperty(this, "discrete");
IntegerProperty numberProperty()
{
return number;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
None found so far.
FREQUENCY : always
- links to
-
Commit(master) openjdk/jfx/28e3ccc7
-
Review(master) openjdk/jfx/1521