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

TableView scrollTo(lastRow) displays the last row at the top of the viewport

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      MacOS 12.6.1, x86_64, Java 11 LTS, JavaFX 19.0.1.

      A DESCRIPTION OF THE PROBLEM :
      Normally, calling tableView.scrollTo(tableView.size() - 1) will scroll the table to the bottom, leaving the last row at the bottom of the viewport. As of Java 19, if the displayed list is "big enough", the call will scroll so that the last row is displayed at the *top* of the viewport, leaving the rest of the viewport blank. Scrolling via key or scrollbar mouse-click will then restore it to its normal appearance.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      See the source code below. It creates a small JavaFX app containing a TableView and two buttons, "Add 100 Entries" and "Scroll to Bottom". Press "Add 100 Entries" 3 times, and then press "Scroll to Bottom". This results in the error 100% of the time in our experience.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      "Scroll to bottom" should do nothing if the last row is already visible, and should scroll so that it is displayed at the bottom of the viewport if not. (This is the historical behavior.)
      ACTUAL -
      The last row is displayed at the top of the viewport, leaving the rest of the viewport blank.

      In this case we see this error:

      Nov 09, 2022 9:47:44 AM javafx.scene.control.skin.VirtualFlow addTrailingCells
      INFO: index exceeds maxCellCount. Check size calculations for class javafx.scene.control.TableRow

      ---------- BEGIN SOURCE ----------
      /*
       * This Java source file was generated by the Gradle 'init' task.
       */
      package app;

      import javafx.application.Application;
      import javafx.collections.FXCollections;
      import javafx.collections.ObservableList;
      import javafx.geometry.Insets;
      import javafx.scene.Scene;
      import javafx.scene.control.*;
      import javafx.scene.control.cell.PropertyValueFactory;
      import javafx.scene.layout.VBox;
      import javafx.stage.Stage;

      /**
       * This app illustrates a TableView bug. If the TableView contains enough
       * rows (somewhere between 200 and 300 in my experience), then a call to
       * TableView::scrollTo for the index of the last row will cause the
       * last row to appear at the top of the view port with blank space below,
       * instead of at the bottom of the view port as expected.
       *
       * <p>To see the bug: press "Add 100 Items" followed by "Scroll to Bottom"
       * repeatedly.</p>
       */
      public class App extends Application {
          private final ObservableList<Record> records =
              FXCollections.observableArrayList();
          private final TableView<Record> tableView = new TableView<>();

          public void start(Stage stage) {
              VBox hull = new VBox();

              ToolBar toolbar = new ToolBar();

              Button add100Button = new Button("Add 100 Items");
              add100Button.setOnAction(evt -> addOneHundredItems());
              toolbar.getItems().add(add100Button);

              Button scrollButton = new Button("Scroll to Bottom");
              scrollButton.setOnAction(evt -> {
                  if (!tableView.getItems().isEmpty()) {
                      int ndx = tableView.getItems().size() - 1;
                      System.out.println("TableView contains " +
                          tableView.getItems().size() + " elements.");
                      System.out.println("Scrolling to index " + ndx);
                      tableView.scrollTo(ndx);
                  }
              });
              toolbar.getItems().add(scrollButton);
              hull.getChildren().add(toolbar);

              tableView.setItems(records);

              TableColumn<Record,String> nameColumn = new TableColumn<>();
              nameColumn.setText("Name");
              nameColumn.setPrefWidth(150);
              nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
              tableView.getColumns().add(nameColumn);

              TableColumn<Record,String> valueColumn = new TableColumn<>();
              valueColumn.setText("Value");
              valueColumn.setPrefWidth(150);
              valueColumn.setCellValueFactory(new PropertyValueFactory<>("value"));
              tableView.getColumns().add(valueColumn);

              hull.getChildren().add(tableView);

              // NEXT, set up the scene.
              Scene scene = new Scene(hull, 400, 300);
              stage.setTitle("TableView Scrolling");
              stage.setScene(scene);

              // FINALLY, show the GUI.
              stage.show();
          }

          private void addOneHundredItems() {
              for (int i = 0; i < 100; i++) {
                  int size = records.size();
                  records.add(new Record("Name " + size, "Value " + size));
              }
              System.out.println("Table contains " + records.size() + " items");
          }


          public static class Record {
              private final String name;
              private final String value;

              Record(String name, String value) {
                  this.name = name;
                  this.value = value;
              }

              public String getName() { return name; }
              public String getValue() { return value; }
          }

          public static void main(String[] args) {
              launch(args);
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      None.

      FREQUENCY : always


        1. App.java
          3 kB
        2. capture.pptx
          13.82 MB

            jvos Johan Vos
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: