diff --git a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Properties.java b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Properties.java index 50c497a5bd7..f6e57843e2b 100644 --- a/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Properties.java +++ b/modules/javafx.controls/src/main/java/com/sun/javafx/scene/control/Properties.java @@ -82,12 +82,12 @@ public static String getColorPickerString(String key) { /*************************************************************************** * - * ListView, TableView + * Virtualized controls * **************************************************************************/ - public static final String REFRESH = "refreshKey"; public static final String RECREATE = "recreateKey"; + public static final String REBUILD = "rebuildKey"; diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/ListView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/ListView.java index c0e1c3a356a..e88643d2b41 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/ListView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/ListView.java @@ -1032,16 +1032,14 @@ public final ObjectProperty>> onScrollToProp } /** - * Calling {@code refresh()} forces the ListView control to recreate and - * repopulate the cells necessary to populate the visual bounds of the control. - * In other words, this forces the ListView to update what it is showing to - * the user. This is useful in cases where the underlying data source has - * changed in a way that is not observed by the ListView itself. + * Forces the ListView to update what it is showing to the user. + * This is useful in cases where the underlying data source has changed in a way + * that is not observed by the ListView itself. * * @since JavaFX 8u60 */ public void refresh() { - getProperties().put(Properties.RECREATE, Boolean.TRUE); + getProperties().put(Properties.REBUILD, Boolean.TRUE); } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java index 951a3eaaef5..2203d7e176b 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TableView.java @@ -1763,16 +1763,14 @@ public void sort() { } /** - * Calling {@code refresh()} forces the TableView control to recreate and - * repopulate the cells necessary to populate the visual bounds of the control. - * In other words, this forces the TableView to update what it is showing to - * the user. This is useful in cases where the underlying data source has - * changed in a way that is not observed by the TableView itself. + * Forces the TableView to update what it is showing to the user. + * This is useful in cases where the underlying data source has changed in a way + * that is not observed by the TableView itself. * * @since JavaFX 8u60 */ public void refresh() { - getProperties().put(Properties.RECREATE, Boolean.TRUE); + getProperties().put(Properties.REBUILD, Boolean.TRUE); } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java index fbc96dbd4e1..d80669c9660 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeTableView.java @@ -2079,16 +2079,14 @@ public void sort() { } /** - * Calling {@code refresh()} forces the TreeTableView control to recreate and - * repopulate the cells necessary to populate the visual bounds of the control. - * In other words, this forces the TreeTableView to update what it is showing to - * the user. This is useful in cases where the underlying data source has - * changed in a way that is not observed by the TreeTableView itself. + * Forces the TreeTableView to update what it is showing to the user. + * This is useful in cases where the underlying data source has changed in a way + * that is not observed by the TreeTableView itself. * * @since JavaFX 8u60 */ public void refresh() { - getProperties().put(Properties.RECREATE, Boolean.TRUE); + getProperties().put(Properties.REBUILD, Boolean.TRUE); } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java index 17da98d5378..4a35540714d 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/TreeView.java @@ -1109,16 +1109,14 @@ public int getTreeItemLevel(TreeItem node) { } /** - * Calling {@code refresh()} forces the TreeView control to recreate and - * repopulate the cells necessary to populate the visual bounds of the control. - * In other words, this forces the TreeView to update what it is showing to - * the user. This is useful in cases where the underlying data source has - * changed in a way that is not observed by the TreeView itself. + * Forces the TreeView to update what it is showing to the user. + * This is useful in cases where the underlying data source has changed in a way + * that is not observed by the TreeView itself. * * @since JavaFX 8u60 */ public void refresh() { - getProperties().put(Properties.RECREATE, Boolean.TRUE); + getProperties().put(Properties.REBUILD, Boolean.TRUE); } diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java index 4854ece4ffc..0737dc3161a 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/ListViewSkin.java @@ -101,7 +101,6 @@ public class ListViewSkin extends VirtualContainerBase, ListCell< private ObservableList listViewItems; - private boolean needCellsRebuilt = true; private boolean needCellsReconfigured = false; private int itemCount = -1; @@ -117,10 +116,9 @@ public class ListViewSkin extends VirtualContainerBase, ListCell< private MapChangeListener propertiesMapListener = c -> { if (! c.wasAdded()) return; - if (Properties.RECREATE.equals(c.getKey())) { - needCellsRebuilt = true; - getSkinnable().requestLayout(); - getSkinnable().getProperties().remove(Properties.RECREATE); + if (Properties.REBUILD.equals(c.getKey())) { + requestRebuildCells(); + getSkinnable().getProperties().remove(Properties.REBUILD); } }; @@ -230,7 +228,7 @@ public ListViewSkin(final ListView control) { control.itemsProperty().addListener(weakItemsChangeListener); final ObservableMap properties = control.getProperties(); - properties.remove(Properties.RECREATE); + properties.remove(Properties.REBUILD); properties.addListener(weakPropertiesMapListener); // Register listeners @@ -281,13 +279,10 @@ public ListViewSkin(final ListView control) { final double w, final double h) { super.layoutChildren(x, y, w, h); - if (needCellsRebuilt) { - flow.rebuildCells(); - } else if (needCellsReconfigured) { + if (needCellsReconfigured) { flow.reconfigureCells(); } - needCellsRebuilt = false; needCellsReconfigured = false; if (getItemCount() == 0) { diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java index 54f899ee20b..5518a781f12 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableViewSkinBase.java @@ -152,7 +152,6 @@ public abstract class TableViewSkinBase properties = control.getProperties(); - properties.remove(Properties.REFRESH); properties.remove(Properties.RECREATE); + properties.remove(Properties.REBUILD); lh.addMapChangeListener(properties, (c) -> { if (!c.wasAdded()) { return; } - if (Properties.REFRESH.equals(c.getKey())) { - refreshView(); - getSkinnable().getProperties().remove(Properties.REFRESH); - } else if (Properties.RECREATE.equals(c.getKey())) { - needCellsRecreated = true; - refreshView(); + if (Properties.RECREATE.equals(c.getKey())) { + requestRecreateCells(); getSkinnable().getProperties().remove(Properties.RECREATE); + } else if (Properties.REBUILD.equals(c.getKey())) { + requestRebuildCells(); + getSkinnable().getProperties().remove(Properties.REBUILD); } }); @@ -425,13 +423,10 @@ public void dispose() { super.layoutChildren(x, y, w, h); - if (needCellsRecreated) { - flow.recreateCells(); - } else if (needCellsReconfigured) { + if (needCellsReconfigured) { flow.reconfigureCells(); } - needCellsRecreated = false; needCellsReconfigured = false; final double baselineOffset = table.getLayoutBounds().getHeight() / 2; diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java index efac1c0b38b..b11c44c2af4 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java @@ -102,9 +102,9 @@ public class TreeViewSkin extends VirtualContainerBase, TreeCell< private MapChangeListener propertiesMapListener = c -> { if (! c.wasAdded()) return; - if (Properties.RECREATE.equals(c.getKey())) { + if (Properties.REBUILD.equals(c.getKey())) { requestRebuildCells(); - getSkinnable().getProperties().remove(Properties.RECREATE); + getSkinnable().getProperties().remove(Properties.REBUILD); } }; @@ -184,7 +184,7 @@ public TreeViewSkin(final TreeView control) { flow.getHbar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); final ObservableMap properties = control.getProperties(); - properties.remove(Properties.RECREATE); + properties.remove(Properties.REBUILD); properties.addListener(propertiesMapListener); // init the behavior 'closures' diff --git a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java index ec90b021214..0af9ad83cb0 100644 --- a/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java +++ b/modules/javafx.controls/src/main/java/javafx/scene/control/skin/VirtualContainerBase.java @@ -191,4 +191,8 @@ void requestRebuildCells() { flow.rebuildCells(); } + void requestRecreateCells() { + flow.recreateCells(); + } + } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/ListViewTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/ListViewTest.java index 6a5ae16591e..2825762bb6e 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/ListViewTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/ListViewTest.java @@ -43,6 +43,7 @@ import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.binding.Bindings; @@ -2726,6 +2727,65 @@ public void fixListViewCrash_JDK_8303680() { // But we wan't to ensure, that the VirtualFlow "Doesn't crash" - which was the case before. } + @Test + void testRefreshShouldNotResetCells() { + final AtomicInteger creationCounter = new AtomicInteger(); + + ListView listView = new ListView<>(); + listView.setItems(FXCollections.observableArrayList(new Person("name"))); + listView.setCellFactory(_ -> { + creationCounter.incrementAndGet(); + return new ListCell<>(); + }); + + stageLoader = new StageLoader(listView); + Toolkit.getToolkit().firePulse(); + + assertTrue(creationCounter.get() > 0); + creationCounter.set(0); + + listView.refresh(); + Toolkit.getToolkit().firePulse(); + + assertEquals(0, creationCounter.get()); + } + + @Test + void testRefreshShouldReflectChangeInCell() { + String initialName = "Initial"; + Person person = new Person(initialName); + + ListView listView = new ListView<>(); + listView.setItems(FXCollections.observableArrayList(person)); + listView.setCellFactory(_ -> new ListCell<>() { + @Override + protected void updateItem(Person item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setText(null); + } else { + setText(item.getFirstName()); + } + } + }); + + stageLoader = new StageLoader(listView); + Toolkit.getToolkit().firePulse(); + + String newName = "Other Name"; + person.setFirstName(newName); + + IndexedCell cell = VirtualFlowTestUtils.getCell(listView, 0); + assertEquals(initialName, cell.getText()); + + listView.refresh(); + Toolkit.getToolkit().firePulse(); + + cell = VirtualFlowTestUtils.getCell(listView, 0); + assertEquals(newName, cell.getText()); + } + private static double toViewportLength(double prefHeight) { // it would be better to calculate this from listView but there is no API for this return prefHeight - 2; diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewTest.java index 85095cb7805..31f7badbb0d 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TableViewTest.java @@ -6427,4 +6427,68 @@ public void testScrollingXIsSnapped() { assertEquals(-snappedNewValue, rootHeader.getLayoutX(), 0); } + + @Test + void testRefreshShouldNotResetCells() { + final AtomicInteger cellCreationCounter = new AtomicInteger(); + final AtomicInteger rowCreationCounter = new AtomicInteger(); + + TableColumn firstNameCol = new TableColumn<>("First Name"); + firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName")); + + firstNameCol.setCellFactory(_ -> { + cellCreationCounter.incrementAndGet(); + return new TableCell<>(); + }); + + TableView table = new TableView<>(); + table.setRowFactory(_ -> { + rowCreationCounter.incrementAndGet(); + return new TableRow<>(); + }); + table.setItems(FXCollections.observableArrayList(new Person("name"))); + table.getColumns().add(firstNameCol); + + stageLoader = new StageLoader(table); + Toolkit.getToolkit().firePulse(); + + assertTrue(cellCreationCounter.get() > 0); + assertTrue(rowCreationCounter.get() > 0); + rowCreationCounter.set(0); + cellCreationCounter.set(0); + + table.refresh(); + Toolkit.getToolkit().firePulse(); + + assertEquals(0, rowCreationCounter.get()); + assertEquals(0, cellCreationCounter.get()); + } + + @Test + void testRefreshShouldReflectChangeInCell() { + String initialName = "Initial"; + Person person = new Person(initialName); + + TableColumn firstNameCol = new TableColumn<>("First Name"); + firstNameCol.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().getFirstName())); + + TableView table = new TableView<>(); + table.setItems(FXCollections.observableArrayList(person)); + table.getColumns().add(firstNameCol); + + stageLoader = new StageLoader(table); + Toolkit.getToolkit().firePulse(); + + String newName = "Other Name"; + person.setFirstName(newName); + + IndexedCell cell = VirtualFlowTestUtils.getCell(table, 0, 0); + assertEquals(initialName, cell.getText()); + + table.refresh(); + Toolkit.getToolkit().firePulse(); + + cell = VirtualFlowTestUtils.getCell(table, 0, 0); + assertEquals(newName, cell.getText()); + } } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewTest.java index d5946742da3..aa4703a0e9d 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeTableViewTest.java @@ -7650,4 +7650,68 @@ public void testScrollingXIsSnapped() { assertEquals(-snappedNewValue, rootHeader.getLayoutX(), 0); } + + @Test + void testRefreshShouldNotResetCells() { + final AtomicInteger cellCreationCounter = new AtomicInteger(); + final AtomicInteger rowCreationCounter = new AtomicInteger(); + + TreeTableColumn firstNameCol = new TreeTableColumn<>("First Name"); + firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<>("firstName")); + + firstNameCol.setCellFactory(_ -> { + cellCreationCounter.incrementAndGet(); + return new TreeTableCell<>(); + }); + + TreeTableView table = new TreeTableView<>(); + table.setRowFactory(_ -> { + rowCreationCounter.incrementAndGet(); + return new TreeTableRow<>(); + }); + table.setRoot(new TreeItem<>(new Person("name"))); + table.getColumns().add(firstNameCol); + + stageLoader = new StageLoader(table); + Toolkit.getToolkit().firePulse(); + + assertTrue(cellCreationCounter.get() > 0); + assertTrue(rowCreationCounter.get() > 0); + rowCreationCounter.set(0); + cellCreationCounter.set(0); + + table.refresh(); + Toolkit.getToolkit().firePulse(); + + assertEquals(0, rowCreationCounter.get()); + assertEquals(0, cellCreationCounter.get()); + } + + @Test + void testRefreshShouldReflectChangeInCell() { + String initialName = "Initial"; + Person person = new Person(initialName); + + TreeTableColumn firstNameCol = new TreeTableColumn<>("First Name"); + firstNameCol.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().getValue().getFirstName())); + + TreeTableView table = new TreeTableView<>(); + table.setRoot(new TreeItem<>(person)); + table.getColumns().add(firstNameCol); + + stageLoader = new StageLoader(table); + Toolkit.getToolkit().firePulse(); + + String newName = "Other Name"; + person.setFirstName(newName); + + IndexedCell cell = VirtualFlowTestUtils.getCell(table, 0, 0); + assertEquals(initialName, cell.getText()); + + table.refresh(); + Toolkit.getToolkit().firePulse(); + + cell = VirtualFlowTestUtils.getCell(table, 0, 0); + assertEquals(newName, cell.getText()); + } } diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeViewTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeViewTest.java index c598073f103..802736aa61d 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeViewTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/TreeViewTest.java @@ -4239,6 +4239,65 @@ void testGraphicShouldNotDisappear() { } } + @Test + void testRefreshShouldNotResetCells() { + final AtomicInteger creationCounter = new AtomicInteger(); + + TreeView treeView = new TreeView<>(); + treeView.setRoot(new TreeItem<>(new Person("text"))); + treeView.setCellFactory(_ -> { + creationCounter.incrementAndGet(); + return new TreeCell<>(); + }); + + stageLoader = new StageLoader(treeView); + Toolkit.getToolkit().firePulse(); + + assertTrue(creationCounter.get() > 0); + creationCounter.set(0); + + treeView.refresh(); + Toolkit.getToolkit().firePulse(); + + assertEquals(0, creationCounter.get()); + } + + @Test + void testRefreshShouldReflectChangeInCell() { + String initialName = "Initial"; + Person person = new Person(initialName); + + TreeView treeView = new TreeView<>(); + treeView.setRoot(new TreeItem<>(person)); + treeView.setCellFactory(_ -> new TreeCell<>() { + @Override + protected void updateItem(Person item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setText(null); + } else { + setText(item.getFirstName()); + } + } + }); + + stageLoader = new StageLoader(treeView); + Toolkit.getToolkit().firePulse(); + + String newName = "Other Name"; + person.setFirstName(newName); + + IndexedCell cell = VirtualFlowTestUtils.getCell(treeView, 0); + assertEquals(initialName, cell.getText()); + + treeView.refresh(); + Toolkit.getToolkit().firePulse(); + + cell = VirtualFlowTestUtils.getCell(treeView, 0); + assertEquals(newName, cell.getText()); + } + public static class MisbehavingOnCancelTreeCell extends TreeCell { @Override diff --git a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableRowSkinTest.java b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableRowSkinTest.java index 752a815f2fd..8895019c29a 100644 --- a/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableRowSkinTest.java +++ b/modules/javafx.controls/src/test/java/test/javafx/scene/control/skin/TreeTableRowSkinTest.java @@ -352,20 +352,6 @@ public void cellsShouldBeAddedInRowFixedCellSize() { assertEquals(5, row.getChildrenUnmodifiable().stream().filter(TreeTableCell.class::isInstance).count()); } - /** TreeTableView.refresh() must release all discarded cells JDK-8307538 */ - @Test - public void cellsMustBeCollectableAfterRefresh() { - IndexedCell row = VirtualFlowTestUtils.getCell(treeTableView, 0); - assertNotNull(row); - WeakReference ref = new WeakReference<>(row); - row = null; - - treeTableView.refresh(); - Toolkit.getToolkit().firePulse(); - - JMemoryBuddy.assertCollectable(ref); - } - /** TreeTableView.setRowFactory() must release all discarded cells JDK-8307538 */ @Test public void cellsMustBeCollectableAfterRowFactoryChange() { diff --git a/tests/performance/control/src/table/TableRefreshTest.java b/tests/performance/control/src/table/TableRefreshTest.java new file mode 100644 index 00000000000..303b5fde1df --- /dev/null +++ b/tests/performance/control/src/table/TableRefreshTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package table; + +import javafx.application.Application; +import javafx.beans.property.SimpleStringProperty; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; +import javafx.scene.control.skin.TableViewSkin; +import javafx.scene.control.skin.VirtualFlow; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.stage.Stage; + +/** + * Testing the refresh() method of a TableView with many columns and rows. + * This takes a little bit since all rows and cells need to be refreshed, + * that is the updateItem(..) method is called. + */ +public class TableRefreshTest { + + public static void main(String[] args) { + Application.launch(FxApp.class, args); + } + + public static class FxApp extends Application { + + @Override + public void start(Stage primaryStage) { + TableView tv = new TableView<>(); + tv.setSkin(new CTableViewSkin<>(tv)); + + for (int i = 0; i < 1000; i++) { + tv.getItems().add("str: " + i); + } + + for (int index = 0; index < 100; index++) { + TableColumn tc = new TableColumn<>("title: " + index); + tc.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue())); + + tv.getColumns().add(tc); + } + + BorderPane root = new BorderPane(); + root.setPadding(new Insets(4)); + root.setCenter(tv); + + Button refreshBtn = new Button("Refresh"); + refreshBtn.setOnAction(_ -> tv.refresh()); + Button recreateBtn = new Button("Recreate"); + recreateBtn.setOnAction(_ -> tv.getProperties().put("recreateKey", true)); + root.setBottom(new HBox(4, refreshBtn, recreateBtn)); + + Scene scene = new Scene(root, 1800, 960); + primaryStage.setScene(scene); + primaryStage.show(); + } + } + + private static class CTableViewSkin extends TableViewSkin { + + CTableViewSkin(TableView control) { + super(control); + } + + @Override + protected VirtualFlow> createVirtualFlow() { + return new CVirtualFlow<>(); + } + } + + private static class CVirtualFlow extends VirtualFlow { + + @Override + protected void layoutChildren() { + long startTime = System.nanoTime(); + super.layoutChildren(); + System.out.println("Took: " + ((System.nanoTime() - startTime) / 1_000_000) + " ms"); + } + } + +}