Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8320232

Cells duplicated when table collapsed and expanded

XMLWordPrintable

    • b04
    • generic
    • generic

      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


            mhanl Marius Hanl
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            7 Start watching this issue

              Created:
              Updated:
              Resolved: