Class ColumnsLayoutCache<T>
- All Implemented Interfaces:
javafx.beans.binding.Binding<Number>
,javafx.beans.binding.NumberBinding
,javafx.beans.binding.NumberExpression
,javafx.beans.Observable
,javafx.beans.value.ObservableDoubleValue
,javafx.beans.value.ObservableNumberValue
,javafx.beans.value.ObservableValue<Number>
ColumnsLayoutMode.VARIABLE
.
This mode essentially disables virtualization along the x-axis which makes some computations way more expensive.
Example 1: A columns width must be 'asked' to the column itself rather than using the value specified by
VFXTable.columnsSizeProperty()
Example 2: A columns position cannot be determined by a simple multiplication, but it's the sum of all previous columns' widths (a loop)
Also, keep in mind that such computations are not needed only for the columns, but also for their corresponding cells (can't rely on JavaFX bounds because sometimes they are messed up, garbage framework).
This cache implementation tries to mitigate this by caching columns' data such as their width, position and visibility in the viewport. Listeners and bindings will automatically invalidate the data as needed, and re-compute it once requested, which in other words means that the cache is 'lazy'.
Why this extendsDoubleBinding
When I decided to create this special cache, it was mainly to improve the computation speed of the VFXTable.virtualMaxXProperty()
(in VARIABLE mode ofc), because it requires summing every column's width. So, I came up with a simple extension of
DoubleBinding
which would invalidate the cached widths and thus re-compute upon request their sum.
It was then that I decided to expand the cache to also have positions and visibility checks, because the three pieces
of information are tightly coupled. Visibility depends on the width and the position, the latter depends on the width.
So, besides making such computations faster, this also still allows computing the virtualMaxX
much faster.
There's even a special width value given by getPartialWidth()
which is the sum of all column's widths excluding
the last one. This is useful to compute the last column's width, as it may need to be bigger than expected to fill
the table (such value could be given by tableWidth - partialWidth
).
The cache makes use of a Map
and a wrapper class ColumnsLayoutCache<T>.LayoutInfo
to gather all the computations in one place.
Each table's column will have an entry in the map like this: [Column -> LayoutInfo]. When something needs to be
invalidated, setters are called on the appropriate ColumnsLayoutCache<T>.LayoutInfo
object.
To manage invalidations and columns changes in the table, this uses a series of listeners.
1) A ListChangeListener
ensures the above-mentioned map stays always updated, more info here handleColumns(ListChangeListener.Change)
2) An InvalidationListener
watches for VFXTable.columnsSizeProperty()
changes and by iterating over
the ColumnsLayoutCache<T>.LayoutInfo
stored in the map, performs the following actions: a) resets both the positions and visibility
flags; b) invalidates the width if it's below the new value specified by the property; c) at the end it also invalidates
the width for the last column (if it wasn't done before). This is important to ensure that the last column takes all
the available space
3) An InvalidationListener
added on both the Region.widthProperty()
and VFXTable.hPosProperty()
.
This listener is responsible for clearing, thus forcing the re-computation when requested, of the visibility cache
4) Lastly, there an InvalidationListener
for each column in the map to watch for Region.prefWidthProperty()
changes. This is managed by each ColumnsLayoutCache<T>.LayoutInfo
, more info there.
For the cache to work, the user must specify the three functions used to compute:
1) the widths, setWidthFunction(BiFunction)
2) the positions, setPositionFunction(BiFunction)
3) the visibility, setVisibilityFunction(Function)
To avoid cluttering the constructors, and for other reasons, the cache won't be active until you call the
init()
method. Both the setters and the init methods follow the fluent API pattern. Beware, if any
of the three functions is not set, it will throw an exception!
- See Also:
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionclass
Wrapper class for layout data related to a specificVFXTableColumn
.class
Nothing special, just an extension ofHashMap
to store data about columns' layout asColumnsLayoutCache<T>.LayoutInfo
objects. -
Property Summary
PropertiesTypePropertyDescriptionjavafx.beans.property.ReadOnlyBooleanProperty
Specifies whether any of theColumnsLayoutCache<T>.LayoutInfo
objects ingetCacheMap()
was invalidated. -
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionjavafx.beans.property.ReadOnlyBooleanProperty
Specifies whether any of theColumnsLayoutCache<T>.LayoutInfo
objects ingetCacheMap()
was invalidated.protected double
void
dispose()
Disposes the cache making it not usable anymore.protected Map
<VFXTableColumn<T, ?>, ColumnsLayoutCache<T>.LayoutInfo> double
getColumnPos
(int index) The position of the column at the given index.double
getColumnWidth
(VFXTableColumn<T, ?> column) protected VFXTableColumn
<T, ?> double
Delegates togetColumnWidth(VFXTableColumn)
by passing the last column in the table.double
getTable()
init()
IfpreInitCheck()
does not throw any exception, initializes the cache by adding the needed listeners to the appropriate properties, as well as creating the cache mappings for each column in the table.boolean
Gets the value of theanyChanged
property.boolean
isInViewport
(VFXTableColumn<T, ?> column) Queries the map to check whether the given column is visible.setPositionFunction
(BiFunction<Integer, Double, Double> xPosFn) Sets theBiFunction
responsible for computing a column's position.setVisibilityFunction
(Function<VFXTableColumn<T, ?>, Boolean> vFn) Sets theFunction
responsible for computing a column's width.setWidthFunction
(BiFunction<VFXTableColumn<T, ?>, Boolean, Double> widthFn) Sets theBiFunction
responsible for computing a column's width.int
size()
toString()
Methods inherited from class javafx.beans.binding.DoubleBinding
addListener, addListener, bind, get, getDependencies, invalidate, isValid, onInvalidating, removeListener, removeListener, unbind
Methods inherited from class javafx.beans.binding.DoubleExpression
add, add, add, add, add, asObject, divide, divide, divide, divide, divide, doubleExpression, doubleExpression, doubleValue, floatValue, getValue, intValue, longValue, multiply, multiply, multiply, multiply, multiply, negate, subtract, subtract, subtract, subtract, subtract
Methods inherited from class javafx.beans.binding.NumberExpressionBase
asString, asString, asString, greaterThan, greaterThan, greaterThan, greaterThan, greaterThan, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, lessThan, lessThan, lessThan, lessThan, lessThan, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, numberExpression
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
Methods inherited from interface javafx.beans.binding.NumberExpression
add, add, add, add, add, asString, asString, asString, divide, divide, divide, divide, divide, greaterThan, greaterThan, greaterThan, greaterThan, greaterThan, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, greaterThanOrEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, isNotEqualTo, lessThan, lessThan, lessThan, lessThan, lessThan, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, lessThanOrEqualTo, multiply, multiply, multiply, multiply, multiply, negate, subtract, subtract, subtract, subtract, subtract
Methods inherited from interface javafx.beans.Observable
subscribe
Methods inherited from interface javafx.beans.value.ObservableNumberValue
doubleValue, floatValue, intValue, longValue
Methods inherited from interface javafx.beans.value.ObservableValue
flatMap, getValue, map, orElse, subscribe, subscribe, when
-
Property Details
-
anyChanged
public javafx.beans.property.ReadOnlyBooleanProperty anyChangedPropertySpecifies whether any of theColumnsLayoutCache<T>.LayoutInfo
objects ingetCacheMap()
was invalidated.- See Also:
-
-
Field Details
-
sortToString
public boolean sortToString
-
-
Constructor Details
-
ColumnsLayoutCache
-
-
Method Details
-
init
IfpreInitCheck()
does not throw any exception, initializes the cache by adding the needed listeners to the appropriate properties, as well as creating the cache mappings for each column in the table.Further calls to this method won't do anything if the cache has already been initialized before.
-
getColumnWidth
- Returns:
- either the cached or computed width for the given column
-
getLastColumnWidth
public double getLastColumnWidth()Delegates togetColumnWidth(VFXTableColumn)
by passing the last column in the table. -
getPartialWidth
public double getPartialWidth()- Returns:
- the sum of all columns' widths excluding the last one
-
getColumnPos
public double getColumnPos(int index) The position of the column at the given index. This method is recursive!Detailing the internals:
// Let's suppose we want to compute the position of the column at index 2 (so third one) // First we convert the index to the corresponding column VFXTableColumn c = ...; // Then we query the map and get the known position for that column double pos = map.getPos(c); // Index 0 is a special case and we handle it as follows if (index == 0) { map.setPos(c, 0); // Column 0 is always at x = 0 return 0; } // If 'pos' is lesser than 0, then it either means it was never been computed before or it was invalidated // We need to ask the position function to compute the value as follows... if (pos < 0) { pos = posFn.apply(index -1, getColumnPos(index -1)); // Here's where the method calls itself map.setPos(index, pos); // Store the found pos in the cache so we don't fall in this 'if' again until invalidated } return pos; // Why the recursion? // In general, to compute a column's position, we can simply get the position of the previous column + its width. // So, for the third one, we need the second one's position, and so on... // The recursion doesn't happen if the previous value is known, so the method acts almost like a simple getter // The recursion stops at column 0, because it's position is always 0.
-
isInViewport
Queries the map to check whether the given column is visible.If the
ColumnsLayoutCache<T>.LayoutInfo
mapped to the column returns anull
value, then it means that the visibility check was either never done before or invalidated. In this case, the visibility function will compute it and theColumnsLayoutCache<T>.LayoutInfo
object updated. -
size
public int size()- Returns:
- the number of entries in the cache's map. This should always be equal to the size of
VFXTable.getColumns()
-
computeValue
protected double computeValue()- Specified by:
computeValue
in classjavafx.beans.binding.DoubleBinding
- Returns:
- the sum of all columns' widths, each given by
ColumnsLayoutCache.LayoutInfo.getWidth()
-
dispose
public void dispose()Disposes the cache making it not usable anymore.- Specified by:
dispose
in interfacejavafx.beans.binding.Binding<T>
- Overrides:
dispose
in classjavafx.beans.binding.DoubleBinding
- See Also:
-
toString
- Overrides:
toString
in classjavafx.beans.binding.DoubleBinding
-
getTable
-
getCacheMap
- Returns:
- the map containing the columns' layout data as
ColumnsLayoutCache<T>.LayoutInfo
objects
-
getLastColumn
- Returns:
- the local reference to the last column in the table
-
isAnyChanged
public boolean isAnyChanged()Gets the value of theanyChanged
property.- Property description:
- Specifies whether any of the
ColumnsLayoutCache<T>.LayoutInfo
objects ingetCacheMap()
was invalidated. - Returns:
- the value of the
anyChanged
property - See Also:
-
anyChangedProperty
public javafx.beans.property.ReadOnlyBooleanProperty anyChangedProperty()Specifies whether any of theColumnsLayoutCache<T>.LayoutInfo
objects ingetCacheMap()
was invalidated.- Returns:
- the
anyChanged
property - See Also:
-
setWidthFunction
public ColumnsLayoutCache<T> setWidthFunction(BiFunction<VFXTableColumn<T, ?>, Boolean, Double> widthFn) Sets theBiFunction
responsible for computing a column's width. The function gives the following parameters: 1) the column to compute the width for; 2) whether it is the last column in the table which may need special handling.You can check
VFXTableHelper.VariableTableHelper.computeColumnWidth(VFXTableColumn, boolean)
for an example. -
setPositionFunction
Sets theBiFunction
responsible for computing a column's position. The function gives the following parameters: 1) the previous column's index; 2) the previous column's width.To understand the why of those parameters, read
getColumnPos(int)
.You can check
VFXTableHelper.VariableTableHelper.computeColumnPos(int, double)
for an example. -
setVisibilityFunction
Sets theFunction
responsible for computing a column's width. The function gives the column for which compute the visibility as the parameter.You can check
VFXTableHelper.VariableTableHelper.computeVisibility(VFXTableColumn)
for an example.
-