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 TypeMethodDescriptionvoid
autosizeColumn
(VFXTableColumn<T, ?> column) If the current state isVFXTableState.INVALID
then exits immediately.void
This simply callsautosizeColumn(VFXTableColumn)
on all the table's columns.protected double
computeColumnPos
(int index, double prevPos) This is used by theColumnsLayoutCache
to compute the x position of a given column (by its index) and the last know position.protected double
computeColumnWidth
(VFXTableColumn<T, ?> column, boolean isLast) This is used by theColumnsLayoutCache
to compute the width of the given column.protected boolean
computeVisibility
(VFXTableColumn<T, ?> column) This is used by theColumnsLayoutCache
to compute the visibility of a given column (and all its related cells ofc).void
dispose()
Automatically called byVFXTable
when a helper is not needed anymore (changed).int
Always 0.double
getColumnPos
(int layoutIdx, VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.getColumnPos(int)
.double
getColumnWidth
(VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.getColumnWidth(VFXTableColumn)
.protected void
Bindings and listeners should be initialized here, automatically called after the table instance is set.boolean
isInViewport
(VFXTableColumn<T, ?> column) Delegates toColumnsLayoutCache.isInViewport(VFXTableColumn)
.boolean
layoutCell
(int layoutIdx, VFXTableCell<T> cell) Lays out the given cell.boolean
layoutColumn
(int layoutIdx, VFXTableColumn<T, ?> column) Lays out the given column.void
scrollToIndex
(javafx.geometry.Orientation orientation, int index) Scrolls in the viewport, depending on the given direction (orientation) to:int
Always the size ofVFXTable.getColumns()
.int
Depends on the implementation!int
Always 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, visibleRows
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Methods 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 theColumnsLayoutCache
to compute the width of the given column. The value is given byMath.max(minW, prefW)
, where:-
minW
is given byVFXTable.columnsSizeProperty()
-
prefW
is 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)
, wherepartialW
is given byColumnsLayoutCache.getPartialWidth()
. -
computeColumnPos
protected double computeColumnPos(int index, double prevPos) This is used by theColumnsLayoutCache
to 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. TheprevPos
parameter 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 theColumnsLayoutCache
to 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 (null
check on the column's Scene and Parent). If any of these conditions failfalse
is 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.AbstractHelper
Bindings and listeners should be initialized here, automatically called after the table instance is set.- Overrides:
initBindings
in 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.VARIABLE
thelayoutIndex
is 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:
false
if the column was hidden or no layout operation was run,true
otherwise- 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.VARIABLE
thelayoutIndex
is 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 byColumnsLayoutCache
shines. 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.VFXCell
are 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.INVALID
then 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) + extra
where:-
minW
is specified byVFXTable.columnsSizeProperty()
-
prefW
is obtained by callingVFXTableColumn.computePrefWidth(double)
-
extra
is 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)
. TheforceLayout
flag istrue
if 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:VFXTableHelper
Depends 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:VFXTableHelper
Scrolls 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 byVFXTable
when 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:
dispose
in interfaceVFXTableHelper<T>
- Overrides:
dispose
in classVFXTableHelper.AbstractHelper<T>
-