If there are many columns, the current TableView will stall scrolling. 
Resolving this performance issue requires column virtualization. Virtualization 
mode is enabled when the row height is fixed by the following method.

`tableView.setFixedCellSize(height)`

This proposal includes a fix because the current code does not correctly 
implement column virtualization.

The improvement of this proposal can be seen in the following test program.

``` Java
import java.util.Arrays;
import java.util.Collections;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ObservableList;
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;

public class BigTableViewTest2 extends Application {
        private static final boolean USE_WIDTH_FIXED_SIZE = false;
        private static final boolean USE_HEIGHT_FIXED_SIZE = true;
//      private static final int COL_COUNT=30;
//      private static final int COL_COUNT=300;
//      private static final int COL_COUNT=600;
        private static final int COL_COUNT = 1000;
        private static final int ROW_COUNT = 1000;

        @Override
        public void start(final Stage primaryStage) throws Exception {
                final TableView<String[]> tableView = new TableView<>();

//          tableView.setTableMenuButtonVisible(true); //too heavy
                if (USE_HEIGHT_FIXED_SIZE) {
                        tableView.setFixedCellSize(24);
                }

                final ObservableList<TableColumn<String[], ?>> columns = 
tableView.getColumns();
                for (int i = 0; i < COL_COUNT; i++) {
                        final TableColumn<String[], String> column = new 
TableColumn<>("Col" + i);
                        final int colIndex = i;
                        column.setCellValueFactory((cell) -> new 
SimpleStringProperty(cell.getValue()[colIndex]));
                        columns.add(column);
                        if (USE_WIDTH_FIXED_SIZE) {
                                column.setPrefWidth(60);
                                column.setMaxWidth(60);
                                column.setMinWidth(60);
                        }
                }

                final Button load = new Button("load");
                load.setOnAction(e -> {
                        final ObservableList<String[]> items = 
tableView.getItems();
                        items.clear();
                        for (int i = 0; i < ROW_COUNT; i++) {
                                final String[] rec = new String[COL_COUNT];
                                for (int j = 0; j < rec.length; j++) {
                                        rec[j] = i + ":" + j;
                                }
                                items.add(rec);
                        }
                });

                final Button reverse = new Button("reverse columns");
                reverse.setOnAction(e -> {
                        final TableColumn<String[], ?>[] itemsArray = 
columns.toArray(new TableColumn[0]);
                        Collections.reverse(Arrays.asList(itemsArray));
                        tableView.getColumns().clear();
                        
tableView.getColumns().addAll(Arrays.asList(itemsArray));
                });

                final Button hide = new Button("hide % 10");
                hide.setOnAction(e -> {
                        for (int i = 0, n = columns.size(); i < n; i++) {
                                if (i % 10 == 0) {
                                        columns.get(i).setVisible(false);
                                }
                        }
                });

                final BorderPane root = new BorderPane(tableView);
                root.setTop(new HBox(8, load, reverse, hide));

                final Scene scene = new Scene(root, 800, 800);
                primaryStage.setScene(scene);
                primaryStage.show();
                this.prepareTimeline(scene);
        }

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

        private void prepareTimeline(final Scene scene) {
                new AnimationTimer() {
                        @Override
                        public void handle(final long now) {
                                final double fps = 
com.sun.javafx.perf.PerformanceTracker.getSceneTracker(scene).getInstantFPS();
                                ((Stage) scene.getWindow()).setTitle("FPS:" + 
(int) fps);
                        }
                }.start();
        }
}


``` Java
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;

public class BigTreeTableViewTest extends Application {

        private static final boolean USE_HEIGHT_FIXED_SIZE = true;
//      private static final int COL_COUNT = 900;
        private static final int COL_COUNT = 800;
//      private static final int COL_COUNT = 600;
//      private static final int COL_COUNT = 500;
//      private static final int COL_COUNT = 400;
//      private static final int COL_COUNT = 300;
//      private static final int COL_COUNT = 200;
//      private static final int COL_COUNT = 100;

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

        @Override
        public void start(final Stage stage) {
                final TreeItem<String> root = new TreeItem<>("Root");
                final TreeTableView<String> treeTableView = new 
TreeTableView<>(root);
                if (USE_HEIGHT_FIXED_SIZE) {
                        treeTableView.setFixedCellSize(24);
                }
                treeTableView.setPrefWidth(800);
                treeTableView.setPrefHeight(500);
                stage.setWidth(800);
                stage.setHeight(500);

                Platform.runLater(() -> {
                        for (int i = 0; i < 100; i++) {
                                TreeItem<String> child = this.addNodes(root);
                                child = this.addNodes(child);
                                child = this.addNodes(child);
                                child = this.addNodes(child);
                        }
                });

                final TreeTableColumn<String, String>[] cols = new 
TreeTableColumn[COL_COUNT + 1];
                final TreeTableColumn<String, String> column = new 
TreeTableColumn<>("Column");
                column.setPrefWidth(150);
                column.setCellValueFactory(
                                (final CellDataFeatures<String, String> p) -> 
new ReadOnlyStringWrapper(p.getValue().getValue()));
                cols[0] = column;

                for (int i = 0; i < COL_COUNT; i++) {
                        final TreeTableColumn<String, String> col = new 
TreeTableColumn<>(Integer.toString(i));
                        col.setPrefWidth(60);
                        col.setCellValueFactory(val -> new 
ReadOnlyStringWrapper(val.getValue().getValue()+":"+val.getTreeTableColumn().getText()));
                        cols[i + 1] = col;
                }
                treeTableView.getColumns().addAll(cols);

                final Scene scene = new Scene(treeTableView, 800, 500);
                stage.setScene(scene);
                stage.show();
                this.prepareTimeline(scene);
        }

        private TreeItem<String> addNodes(final TreeItem<String> parent) {

                final TreeItem<String>[] childNodes = new TreeItem[20];
                for (int i = 0; i < childNodes.length; i++) {
                        childNodes[i] = new TreeItem<>("N" + i);
                }
                final TreeItem<String> root = new TreeItem<>("dir");
                root.setExpanded(true);
                root.getChildren().addAll(childNodes);
                parent.setExpanded(true);
                parent.getChildren().add(root);
                return root;
        }

        private void prepareTimeline(final Scene scene) {
                new AnimationTimer() {
                        @Override
                        public void handle(final long now) {
                                final double fps = 
com.sun.javafx.perf.PerformanceTracker.getSceneTracker(scene).getInstantFPS();
                                ((Stage) scene.getWindow()).setTitle("FPS:" + 
(int) fps);
                        }
                }.start();
        }

}

-------------

Commit messages:
 - 8185886: Fix scroll performance of TableView with many columns
 - 8185886: Fix scroll performance of TableView with many columns

Changes: https://git.openjdk.java.net/jfx/pull/125/files
 Webrev: https://webrevs.openjdk.java.net/?repo=jfx&pr=125&range=00
  Issue: https://bugs.openjdk.java.net/browse/JDK-8185887
  Stats: 237 lines in 5 files changed: 113 ins; 62 del; 62 mod
  Patch: https://git.openjdk.java.net/jfx/pull/125.diff
  Fetch: git fetch https://git.openjdk.java.net/jfx pull/125/head:pull/125

PR: https://git.openjdk.java.net/jfx/pull/125

Reply via email to