Skip to content

Commit 1b12c8a

Browse files
author
Marius Hanl
committed
8185887: TableRowSkinBase fails to correctly virtualize cells in horizontal direction
8276326: Empty Cells in TableView supposedly after using setFixedCellSize() 8252566: TreeTableView: broken row layout for fixedCellSize 8346824: Collapsing tree view causes rendering issues Reviewed-by: angorya, mstrauss
1 parent 8a5e488 commit 1b12c8a

File tree

15 files changed

+1283
-180
lines changed

15 files changed

+1283
-180
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableRowSkin.java

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import com.sun.javafx.scene.control.behavior.BehaviorBase;
3333
import com.sun.javafx.scene.control.behavior.TableRowBehavior;
3434

35-
import javafx.beans.property.DoubleProperty;
3635
import javafx.collections.FXCollections;
3736
import javafx.collections.ObservableList;
3837
import javafx.scene.AccessibleAttribute;
@@ -93,34 +92,41 @@ public TableRowSkin(TableRow<T> control) {
9392
}
9493
});
9594

96-
setupTreeTableViewListeners();
95+
setupTableViewListeners();
9796
}
9897

9998
// FIXME: replace listener to fixedCellSize with direct lookup - JDK-8277000
100-
private void setupTreeTableViewListeners() {
99+
private void setupTableViewListeners() {
101100
TableView<T> tableView = getSkinnable().getTableView();
102101
if (tableView == null) {
103102
registerInvalidationListener(getSkinnable().tableViewProperty(), e -> {
104103
unregisterInvalidationListeners(getSkinnable().tableViewProperty());
105-
setupTreeTableViewListeners();
104+
setupTableViewListeners();
106105
});
107106
} else {
108-
DoubleProperty fixedCellSizeProperty = tableView.fixedCellSizeProperty();
109-
if (fixedCellSizeProperty != null) {
110-
registerChangeListener(fixedCellSizeProperty, e -> {
111-
fixedCellSize = fixedCellSizeProperty.get();
112-
fixedCellSizeEnabled = fixedCellSize > 0;
113-
});
114-
fixedCellSize = fixedCellSizeProperty.get();
115-
fixedCellSizeEnabled = fixedCellSize > 0;
116-
117-
// JDK-8144500:
118-
// When in fixed cell size mode, we must listen to the width of the virtual flow, so
119-
// that when it changes, we can appropriately add / remove cells that may or may not
120-
// be required (because we remove all cells that are not visible).
107+
registerChangeListener(tableView.fixedCellSizeProperty(), e -> {
121108
VirtualFlow<TableRow<T>> virtualFlow = getVirtualFlow();
122109
if (virtualFlow != null) {
123-
registerChangeListener(virtualFlow.widthProperty(), e -> tableView.requestLayout());
110+
unregisterChangeListeners(virtualFlow.widthProperty());
111+
}
112+
113+
updateCachedFixedSize();
114+
});
115+
116+
updateCachedFixedSize();
117+
}
118+
}
119+
120+
private void updateCachedFixedSize() {
121+
TableView<T> tableView = getSkinnable().getTableView();
122+
if (tableView != null) {
123+
fixedCellSize = tableView.getFixedCellSize();
124+
fixedCellSizeEnabled = fixedCellSize > 0;
125+
126+
if (fixedCellSizeEnabled) {
127+
VirtualFlow<TableRow<T>> virtualFlow = getVirtualFlow();
128+
if (virtualFlow != null) {
129+
registerChangeListener(virtualFlow.widthProperty(), e -> getSkinnable().requestLayout());
124130
}
125131
}
126132
}

modules/javafx.controls/src/main/java/javafx/scene/control/skin/TableRowSkinBase.java

Lines changed: 16 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import javafx.collections.ObservableList;
3737
import javafx.css.StyleOrigin;
3838
import javafx.css.StyleableObjectProperty;
39-
import javafx.geometry.Insets;
4039
import javafx.geometry.Pos;
4140
import javafx.scene.Node;
4241
import javafx.scene.Parent;
@@ -95,11 +94,6 @@ public abstract class TableRowSkinBase<T,
9594
*/
9695
static final Map<TableColumnBase<?,?>, Double> maxDisclosureWidthMap = new WeakHashMap<>();
9796

98-
// Specifies the number of times we will call 'recreateCells()' before we blow
99-
// out the cellsMap structure and rebuild all cells. This helps to prevent
100-
// against memory leaks in certain extreme circumstances.
101-
private static final int DEFAULT_FULL_REFRESH_COUNTER = 100;
102-
10397

10498
/* *************************************************************************
10599
* *
@@ -113,21 +107,13 @@ public abstract class TableRowSkinBase<T,
113107
* efficiency we create cells for all columns, even if they aren't visible,
114108
* and we only create new cells if we don't already have it cached in this
115109
* map.
116-
*
117-
* Note that this means that it is possible for this map to therefore be
118-
* a memory leak if an application uses TableView and is creating and removing
119-
* a large number of tableColumns. This is mitigated in the recreateCells()
120-
* function below - refer to that to learn more.
121110
*/
122111
WeakHashMap<TableColumnBase, Reference<R>> cellsMap;
123112

124113
// This observableArrayList contains the currently visible table cells for this row.
125114
final List<R> cells = new ArrayList<>();
126115

127-
private int fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER;
128-
129116
boolean isDirty = false;
130-
boolean updateCells = false;
131117

132118
// FIXME: replace cached values with direct lookup - JDK-8277000
133119
double fixedCellSize;
@@ -152,7 +138,7 @@ public TableRowSkinBase(C control) {
152138
getSkinnable().setPickOnBounds(false);
153139

154140
recreateCells();
155-
updateCells(true);
141+
updateCells();
156142

157143
// init bindings
158144
// watches for any change in the leaf columns observableArrayList - this will indicate
@@ -176,7 +162,7 @@ public TableRowSkinBase(C control) {
176162
* *
177163
**************************************************************************/
178164

179-
private void updateLeafColumns() {
165+
void updateLeafColumns() {
180166
isDirty = true;
181167
getSkinnable().requestLayout();
182168
}
@@ -289,7 +275,6 @@ protected ObjectProperty<Node> graphicProperty() {
289275
// earlier rows to update themselves to take into account
290276
// this increased indentation.
291277
final VirtualFlow<C> flow = getVirtualFlow();
292-
final int thisIndex = getSkinnable().getIndex();
293278
for (int i = 0; i < flow.cells.size(); i++) {
294279
C cell = flow.cells.get(i);
295280
if (cell == null || cell.isEmpty()) continue;
@@ -318,10 +303,13 @@ protected ObjectProperty<Node> graphicProperty() {
318303
int index = control.getIndex();
319304
if (index < 0/* || row >= itemsProperty().get().size()*/) return;
320305

306+
VirtualFlow<C> virtualFlow = getVirtualFlow();
321307
for (int column = 0, max = cells.size(); column < max; column++) {
322308
R tableCell = cells.get(column);
323309
TableColumnBase<T, ?> tableColumn = getTableColumn(tableCell);
324310

311+
width = snapSizeX(tableColumn.getWidth());
312+
325313
boolean isVisible = true;
326314
if (fixedCellSizeEnabled) {
327315
// we determine if the cell is visible, and if not we have the
@@ -333,7 +321,7 @@ protected ObjectProperty<Node> graphicProperty() {
333321
// provided by the developer, and this means that we do not have
334322
// to concern ourselves with the possibility that the height
335323
// may be variable and / or dynamic.
336-
isVisible = isColumnPartiallyOrFullyVisible(tableColumn);
324+
isVisible = isColumnPartiallyOrFullyVisible(x, width, virtualFlow);
337325

338326
y = 0;
339327
height = fixedCellSize;
@@ -342,12 +330,9 @@ protected ObjectProperty<Node> graphicProperty() {
342330
}
343331

344332
if (isVisible) {
345-
if (fixedCellSizeEnabled && tableCell.getParent() == null) {
333+
if (tableCell.getParent() == null) {
346334
getChildren().add(tableCell);
347335
}
348-
// Note: prefWidth() has to be called only after the tableCell is added to the tableRow, if it wasn't
349-
// already. Otherwise, it might not have its skin yet, and its pref width is therefore 0.
350-
width = tableCell.prefWidth(height);
351336

352337
// Added for JDK-8115536, and then updated for JDK-8122708.
353338
// We change the alignment from CENTER_LEFT to TOP_LEFT if the
@@ -419,11 +404,7 @@ protected ObjectProperty<Node> graphicProperty() {
419404
// This does not appear to impact performance...
420405
tableCell.requestLayout();
421406
} else {
422-
width = tableCell.prefWidth(height);
423-
if (fixedCellSizeEnabled) {
424-
// we only add/remove to the scenegraph if the fixed cell
425-
// length support is enabled - otherwise we keep all
426-
// TableCells in the scenegraph
407+
if (tableCell.getParent() != null) {
427408
getChildren().remove(tableCell);
428409
}
429410
}
@@ -473,21 +454,9 @@ boolean isShowRoot() {
473454
return true;
474455
}
475456

476-
void updateCells(boolean resetChildren) {
477-
// To avoid a potential memory leak (when the TableColumns in the
478-
// TableView are created/inserted/removed/deleted, we have a 'refresh
479-
// counter' that when we reach 0 will delete all cells in this row
480-
// and recreate all of them.
481-
if (resetChildren) {
482-
if (fullRefreshCounter == 0) {
483-
recreateCells();
484-
}
485-
fullRefreshCounter--;
486-
}
487-
457+
void updateCells() {
488458
// if clear isn't called first, we can run into situations where the
489459
// cells aren't updated properly.
490-
final boolean cellsEmpty = cells.isEmpty();
491460
cells.clear();
492461

493462
final C skinnable = getSkinnable();
@@ -518,23 +487,7 @@ void updateCells(boolean resetChildren) {
518487
cells.add(cell);
519488
}
520489

521-
// update children of each row
522-
if (fixedCellSizeEnabled) {
523-
// we leave the adding / removing up to the layoutChildren method mostly, but here we remove any children
524-
// cells that refer to columns that are removed or not visible.
525-
List<Node> toRemove = new ArrayList<>();
526-
for (Node cell : getChildren()) {
527-
if (!(cell instanceof IndexedCell)) continue;
528-
TableColumnBase<T, ?> tableColumn = getTableColumn((R) cell);
529-
if (!getVisibleLeafColumns().contains(tableColumn)) {
530-
toRemove.add(cell);
531-
}
532-
}
533-
getChildren().removeAll(toRemove);
534-
}
535-
if (resetChildren || cellsEmpty) {
536-
getChildren().setAll(cells);
537-
}
490+
getChildren().setAll(cells);
538491
}
539492

540493
VirtualFlow<C> getVirtualFlow() {
@@ -630,12 +583,8 @@ VirtualFlow<C> getVirtualFlow() {
630583

631584
final void checkState() {
632585
if (isDirty) {
633-
updateCells(true);
586+
updateCells();
634587
isDirty = false;
635-
updateCells = false;
636-
} else if (updateCells) {
637-
updateCells(false);
638-
updateCells = false;
639588
}
640589
}
641590

@@ -655,33 +604,18 @@ void setDirty(boolean dirty) {
655604
* *
656605
**************************************************************************/
657606

658-
private boolean isColumnPartiallyOrFullyVisible(TableColumnBase col) {
659-
if (col == null || !col.isVisible()) return false;
607+
private boolean isColumnPartiallyOrFullyVisible(double start, double width, VirtualFlow<C> virtualFlow) {
608+
double end = start + width;
660609

661-
final VirtualFlow<?> virtualFlow = getVirtualFlow();
662610
double scrollX = virtualFlow == null ? 0.0 : virtualFlow.getHbar().getValue();
611+
double headerWidth = virtualFlow == null ? 0.0 : virtualFlow.getViewportBreadth();
612+
double virtualFlowWidth = headerWidth + scrollX;
663613

664-
// work out where this column header is, and it's width (start -> end)
665-
double start = 0;
666-
final ObservableList<? extends TableColumnBase> visibleLeafColumns = getVisibleLeafColumns();
667-
for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) {
668-
TableColumnBase<?,?> c = visibleLeafColumns.get(i);
669-
if (c.equals(col)) break;
670-
start += c.getWidth();
671-
}
672-
double end = start + col.getWidth();
673-
674-
// determine the width of the table
675-
final Insets padding = getSkinnable().getPadding();
676-
double headerWidth = getSkinnable().getWidth() - padding.getLeft() + padding.getRight();
677-
678-
return (start >= scrollX || end > scrollX) && (start < (headerWidth + scrollX) || end <= (headerWidth + scrollX));
614+
return (start >= scrollX || end > scrollX) && (start < virtualFlowWidth || end <= virtualFlowWidth);
679615
}
680616

681617
private void requestCellUpdate() {
682-
updateCells = true;
683618
getSkinnable().requestLayout();
684-
685619
// update the index of all children cells (JDK-8119094).
686620
// Note that we do this after the TableRow item has been updated,
687621
// rather than when the TableRow index has changed (as this will be
@@ -713,7 +647,6 @@ private void recreateCells() {
713647
ObservableList<? extends TableColumnBase/*<T,?>*/> columns = getVisibleLeafColumns();
714648

715649
cellsMap = new WeakHashMap<>(columns.size());
716-
fullRefreshCounter = DEFAULT_FULL_REFRESH_COUNTER;
717650
getChildren().clear();
718651

719652
for (TableColumnBase col : columns) {

0 commit comments

Comments
 (0)