RFR: 8185887: TableRowSkinBase fails to correctly virtualize cells in horizontal direction

Nir Lisker nlisker at openjdk.java.net
Mon Apr 12 08:36:04 UTC 2021


On Mon, 24 Feb 2020 07:39:43 GMT, yosbits <github.com+7517141+yososs at openjdk.org> wrote:

> 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();
> 	}
> 
> }

The title of your PR should match the title of the JBS issue. Moreover, #108 already tackles this issue with a different approach and 2 PRs on the same issue can't be intetgrated. Since I'm not familiar with this code, I can't advise on who's solution is more suitable. I suggest you discuss both of the approaches on the mailing list. I saw many JBS issues around this performance issue, probably one of them fits (or a new one can be created).

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

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


More information about the openjfx-dev mailing list