Class VFXTableHelper.VariableTableHelper<T>
- All Implemented Interfaces:
VFXTableHelper<T>
- Enclosing interface:
VFXTableHelper<T>
VFXTableHelper.AbstractHelper for ColumnsLayoutMode.VARIABLE.
Here the range of rows and columns to display, as well as the viewport position,
the virtual max x and y properties are defined as follows:
- the columns range is always given by the number of columns in the table - 1. If there are no column, then it
will be Utils.INVALID_RANGE.
The computation depends only on the columns' list.
- the rows range is given by the VFXTableHelper.AbstractHelper.firstRow() element minus the buffer size VFXTable.rowsBufferSizeProperty(),
(cannot be negative) and the sum between this start index and the total number of needed rows given by VFXTableHelper.AbstractHelper.totalRows().
It may happen that the number of indexes given by the range end - start + 1 is
lesser than the number of rows we need. In such cases, the range start is corrected to be
end - needed + 1. A typical situation for this is when the table's vertical position reaches the max scroll.
If the viewport's height is 0 or the number of needed rows is 0, then the range will be Utils.INVALID_RANGE.
The computation has the following dependencies: the table's height, the column's size (because it also specifies the
header height, which influences the viewport's height), the vertical position, the rows buffer size, the rows' height
and the items' list size.
- the viewport's position, a computation that is at the core of virtual scrolling. The viewport, which contains
the columns and the cells (even though the table's viewport is a bit more complex), is not supposed to scroll by insane
numbers of pixels both for performance reasons and because it is not necessary.
For the vertical positions, first we get the range of rows to display and the rows' height.
We compute the range to the first visible row, which is given by IntegerRange.of(range.getMin(), first()),
in other words we limit the 'complete' range to the start buffer including the first row after the buffer.
The number of indexes in the newfound range (given by IntegerRange.diff()) is multiplied by the rows' height,
this way we are finding the number of pixels to the first visible row, pixelsToFirst.
At this point, we are missing only one last piece of information: how much of the first row do we actually see?
We call this amount visibleAmountFirst and it's given by vPos % size.
Finally, the viewport's vertical position is given by this formula -(pixelsToFirst + visibleAmountFirst).
Since this layout mode disables virtualization along the x-axis, the horizontal position is simply given by
-hPos.
If a range is equal to Utils.INVALID_RANGE, the respective position will be 0!
While it's true that the calculations are more complex and 'needy', it's important to note that this approach
allows avoiding 'hacks' to correctly lay out the cells in the viewport. No need for special offsets at the top
or bottom anymore.
The viewport's position computation has the following dependencies: the vertical and horizontal positions,
the rows' height and the columns' size.
- the virtual max y property, which gives the total number of pixels on the y-axis. Virtual means that it's not the actual size of the container, rather the size it would have if it was not virtualized. The value is given by the number of rows multiplied by the rows' height. The computation depends on the columns' list, the columns' size (because the viewport height also depends on the height specified by the columns' size property), the table's size (number of items), and the rows' height.
- the virtual max x property, which gives the total number of pixels on the x-axis. Since virtualization is disabled in this axis, the value is simply the sum of all the table's columns (to be precise, the vaòue is given by the cache-binding, see below).
Performance Optimizations Disabling virtualization on a complex 2D structure likeVFXTable is indeed dangerous. While it's safe to
assume that the number of columns is pretty much never going to be a big enough number to cause performance issues,
it's also true that: 1) we can't be sure on how many columns are actually too many; 2) since it is a 2D structure
having n more columns in the viewport does not mean that we will have n more nodes in the scene graph.
Rather, we can affirm that at best we will have n more nodes, but keep in mind that for each column
there are going to be a number of cells equal to the number of rows.
I believe it's worth to optimize the helper as much as possible to mitigate the issue. So, for this reason, this
helper makes use of special cache ColumnsLayoutCache which aims to improve layout operations by avoiding
re-computations when they are not needed. For example, if we compute the width and the position of column,
then we don't need to re-compute it again when laying out corresponding cells, that would be a waste!
The cache can compute columns' widths, their x positions and even check whether they are visible in the viewport. I won't go into many details here on how the cache exactly works, read its docs to know more about it, just know that after the first computation, values will be memorized. Further requests will be as fast as a simple 'getter' method. The cache is also responsible for automatically invalidate the cached values when certain conditions change.
For ColumnsLayoutCache to work properly, this helper defines a series of methods which are actually
responsible for the computations. I decided to keep such methods here rather than defining them in the cache mainly
for two reasons: 1) I strongly believe such operations are the helper's responsibility; 2) By doing so we generalize
the cache class, making it flexible to use, and suitable for more use-cases. These methods are:
computeColumnWidth(VFXTableColumn, boolean), computeColumnPos(int, double), computeVisibility(VFXTableColumn).
-
Property Summary
Properties inherited from class io.github.palexdev.virtualizedfx.table.VFXTableHelper.AbstractHelper
columnsRange, maxHScroll, maxVScroll, rowsRange, viewportPosition, virtualMaxX, virtualMaxY -
Nested Class Summary
Nested classes/interfaces inherited from interface io.github.palexdev.virtualizedfx.table.VFXTableHelper
VFXTableHelper.AbstractHelper<T>, VFXTableHelper.FixedTableHelper<T>, VFXTableHelper.VariableTableHelper<T> -
Field Summary
Fields inherited from class io.github.palexdev.virtualizedfx.table.VFXTableHelper.AbstractHelper
columnsRange, maxHScroll, maxVScroll, rowsRange, table, viewportPosition, virtualMaxX, virtualMaxY -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidautosizeColumn(VFXTableColumn<T, ?> column) If the current state isVFXTableState.INVALIDthen exits immediately.voidThis simply callsautosizeColumn(VFXTableColumn)on all the table's columns.protected doublecomputeColumnPos(int index, double prevPos) This is used by theColumnsLayoutCacheto compute the x position of a given column (by its index) and the last know position.protected doublecomputeColumnWidth(VFXTableColumn<T, ?> column, boolean isLast) This is used by theColumnsLayoutCacheto compute the width of the given column.protected booleancomputeVisibility(VFXTableColumn<T, ?> column) This is used by theColumnsLayoutCacheto compute the visibility of a given column (and all its related cells ofc).voiddispose()Automatically called byVFXTablewhen a helper is not needed anymore (changed).intAlways 0.doublegetColumnPos(int layoutIdx, VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.getColumnPos(int).doublegetColumnWidth(VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.getColumnWidth(VFXTableColumn).protected voidBindings and listeners should be initialized here, automatically called after the table instance is set.booleanisInViewport(VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.isInViewport(VFXTableColumn).booleanlayoutCell(int layoutIdx, VFXTableCell<T> cell) Lays out the given cell.booleanlayoutColumn(int layoutIdx, VFXTableColumn<T, ?> column) Lays out the given column.voidscrollToIndex(javafx.geometry.Orientation orientation, int index) Scrolls in the viewport, depending on the given direction (orientation) to:intAlways the size ofVFXTable.getColumns().intDepends on the implementation!intAlways the size ofVFXTable.getColumns().Methods inherited from class io.github.palexdev.virtualizedfx.table.VFXTableHelper.AbstractHelper
columnsRangeProperty, firstRow, getTable, lastColumn, lastRow, maxHScrollProperty, maxVScrollProperty, rowsRangeProperty, totalRows, viewportPositionProperty, virtualMaxXProperty, virtualMaxYProperty, visibleRowsMethods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface io.github.palexdev.virtualizedfx.table.VFXTableHelper
columnsRange, getMaxHScroll, getMaxVScroll, getViewportHeight, getViewportPosition, getVirtualMaxX, getVirtualMaxY, indexToItem, indexToRow, invalidatePos, isLastColumn, itemToRow, layoutRow, rowsRange, scrollBy, scrollToPixel, totalCells
-
Constructor Details
-
VariableTableHelper
-
-
Method Details
-
computeColumnWidth
This is used by theColumnsLayoutCacheto compute the width of the given column. The value is given byMath.max(minW, prefW), where:-
minWis given byVFXTable.columnsSizeProperty()-
prefWis given byRegion.prefWidth(double)If there's only one column in the table, then the returned value is the maximum between the above formula and the table's width.
If the column is the last one in the list, then the final value is given by
Math.max(Math.max(minW, prefW), tableW - partialW), wherepartialWis given byColumnsLayoutCache.getPartialWidth(). -
computeColumnPos
protected double computeColumnPos(int index, double prevPos) This is used by theColumnsLayoutCacheto compute the x position of a given column (by its index) and the last know position. For example, let's suppose I want to lay out the column at index 1, and the column at index 0 (the previous one) is 100px wider. TheprevPosparameter passed to this method will be 100px. This makes the computation for index 1 easy, as it is simply is the sumprevPos + col1Width. -
computeVisibility
This is used by theColumnsLayoutCacheto compute the visibility of a given column (and all its related cells ofc). There are a lot of requirements for this check but the concept is quite simple.Before getting all the dependencies, we ensure that the column's table instance is not
null, that its index is not negative, that the column is in the scene graph (nullcheck on the column's Scene and Parent). If any of these conditions failfalseis returned.The idea is to use something similar to
Bounds.intersects(Bounds)but only for the width and x position. First we compute the viewport bounds which are given by[hPos, hPos + tableWidth], then we get the column's position and width by usingColumnsLayoutCache.getColumnPos(int)andColumnsLayoutCache.getColumnWidth(VFXTableColumn).The result is given by this formula:
(columnX + columnWidth >= vBounds.getMin()) && (columnX <= vBounds.getMax()) -
initBindings
protected void initBindings()Description copied from class:VFXTableHelper.AbstractHelperBindings and listeners should be initialized here, automatically called after the table instance is set.- Overrides:
initBindingsin classVFXTableHelper.AbstractHelper<T>
-
firstColumn
public int firstColumn()Always 0.- Returns:
- the index of the first visible column
-
visibleColumns
public int visibleColumns()Always the size ofVFXTable.getColumns().- Returns:
- the number of columns visible in the viewport. Not necessarily the same as
VFXTableHelper.totalColumns()
-
totalColumns
public int totalColumns()Always the size ofVFXTable.getColumns().- Returns:
- the total number of columns in the viewport which doesn't include only the number of visible columns but also the number of buffer columns
- See Also:
-
getColumnWidth
Delegates toColumnsLayoutCache.getColumnWidth(VFXTableColumn).- Returns:
- the width for the given column
-
getColumnPos
Delegates toColumnsLayoutCache.getColumnPos(int).- Returns:
- the x position for the given column and its layout index in the viewport
-
isInViewport
Delegates toColumnsLayoutCache.isInViewport(VFXTableColumn).- Returns:
- whether the given column is currently visible in the viewport
-
layoutColumn
Lays out the given column. The layout index is necessary to identify the position of a column among the others (comes before/after). InColumnsLayoutMode.VARIABLEthelayoutIndexis always the same as the column's index.Positions the column at
X: getColumnPos(index, column)andY: 0.Sizes the column to
Additionally, this method makes use of the 'inViewport' functionality to hide and not lay out columns when they are not visible in the viewport. The layout operation is also avoided in case the column is visible and both its x positions and width are already good to go.W: getColumnWidth(column)andH: columnsHeight- Returns:
falseif the column was hidden or no layout operation was run,trueotherwise- See Also:
-
layoutCell
Lays out the given cell. The layout index is necessary to identify the position of a cell among the others (comes before/after). InColumnsLayoutMode.VARIABLEthelayoutIndexis always the same as the column's index.The layout logic is the exact same as described here
layoutColumn(int, VFXTableColumn).Here's where the
Note: the pre/post layout hooks defined byColumnsLayoutCacheshines. Since cells are laid out the exact same way as the corresponding column, the width, positions and visibility computations are already done when invokinglayoutColumn(int, VFXTableColumn). Obviously, to do so, we first need to get the cell's corresponding column fromVFXTable.getColumns()by the given index.VFXCellare called even if the cell will only be set to hidden. This allows implementations to perform actions depending on the visibility state without relying on listeners.- See Also:
-
autosizeColumn
If the current state isVFXTableState.INVALIDthen exits immediately.If the given column's skin is still
null, then we must 'delay' the operation and wait for the skin to be created, so that we can compute the column's width.The first pass is to get the column's ideal width which is given by
Math.max(minW, prefW) + extrawhere:-
minWis specified byVFXTable.columnsSizeProperty()-
prefWis obtained by callingVFXTableColumn.computePrefWidth(double)-
extrais an extra number of pixels added to the final value specified byVFXTable.extraAutosizeWidthProperty()If the state is empty (no rows), the computation ends and the column's width is set to the value found by the above formula.
The second pass is to get the widest cell among the ones in the viewport by using
VFXTableRow.getWidthOf(VFXTableColumn, boolean). TheforceLayoutflag istrueif this operation was 'delayed' before for the aforementioned reasons.Finally, the column's width is set to:
Math.max(Math.max(minW, prefW), maxCellsWidth) + extra.Note: the columns are resized using the method
VFXTableColumn.resize(double).- See Also:
-
autosizeColumns
public void autosizeColumns()This simply callsautosizeColumn(VFXTableColumn)on all the table's columns. -
visibleCells
public int visibleCells()Description copied from interface:VFXTableHelperDepends on the implementation!- Returns:
- the number of cells for which the corresponding column is visible in the viewport
- See Also:
-
scrollToIndex
public void scrollToIndex(javafx.geometry.Orientation orientation, int index) Description copied from interface:VFXTableHelperScrolls in the viewport, depending on the given direction (orientation) to:- the item at the given index if it's
Orientation.VERTICAL- the column at the given index if it's
Orientation.HORIZONTAL -
dispose
public void dispose()Automatically called byVFXTablewhen a helper is not needed anymore (changed). If the helper uses listeners/bindings that may lead to memory leaks, this is the right place to remove them. Sets the table reference tonull. Overridden here to also dispose theColumnsLayoutCache.- Specified by:
disposein interfaceVFXTableHelper<T>- Overrides:
disposein classVFXTableHelper.AbstractHelper<T>
-