Class ColumnsLayoutCache<T>

java.lang.Object
javafx.beans.binding.NumberExpressionBase
javafx.beans.binding.DoubleExpression
javafx.beans.binding.DoubleBinding
io.github.palexdev.virtualizedfx.table.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>

public class ColumnsLayoutCache<T> extends javafx.beans.binding.DoubleBinding
Complex cache mechanism to simplify and vastly improve layout performance for 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 extends DoubleBinding

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).

How data is stored

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.

Listeners

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.

Computing functions and initialization

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:
  • Property Summary

    Properties
    Type
    Property
    Description
    javafx.beans.property.ReadOnlyBooleanProperty
    Specifies whether any of the ColumnsLayoutCache<T>.LayoutInfo objects in getCacheMap() was invalidated.
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    class 
    Wrapper class for layout data related to a specific VFXTableColumn.
    class 
    Nothing special, just an extension of HashMap to store data about columns' layout as ColumnsLayoutCache<T>.LayoutInfo objects.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    boolean
     
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    javafx.beans.property.ReadOnlyBooleanProperty
    Specifies whether any of the ColumnsLayoutCache<T>.LayoutInfo objects in getCacheMap() was invalidated.
    protected double
     
    void
    Disposes the cache making it not usable anymore.
     
    double
    getColumnPos(int index)
    The position of the column at the given index.
    double
    protected VFXTableColumn<T,?>
     
    double
    Delegates to getColumnWidth(VFXTableColumn) by passing the last column in the table.
    double
     
     
    If preInitCheck() 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 the anyChanged property.
    boolean
    Queries the map to check whether the given column is visible.
    Sets the BiFunction responsible for computing a column's position.
    Sets the Function responsible for computing a column's width.
    Sets the BiFunction responsible for computing a column's width.
    int
     
     

    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

  • Field Details

    • sortToString

      public boolean sortToString
  • Constructor Details

    • ColumnsLayoutCache

      public ColumnsLayoutCache(VFXTable<T> table)
  • Method Details

    • init

      public ColumnsLayoutCache<T> init()
      If preInitCheck() 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

      public double getColumnWidth(VFXTableColumn<T,?> column)
      Returns:
      either the cached or computed width for the given column
    • getLastColumnWidth

      public double getLastColumnWidth()
      Delegates to getColumnWidth(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

      public boolean isInViewport(VFXTableColumn<T,?> column)
      Queries the map to check whether the given column is visible.

      If the ColumnsLayoutCache<T>.LayoutInfo mapped to the column returns a null 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 the ColumnsLayoutCache<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 class javafx.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 interface javafx.beans.binding.Binding<T>
      Overrides:
      dispose in class javafx.beans.binding.DoubleBinding
      See Also:
      • clear()
    • toString

      public String toString()
      Overrides:
      toString in class javafx.beans.binding.DoubleBinding
    • getTable

      public VFXTable<T> getTable()
      Returns:
      the VFXTable instance this cache is related to
    • getCacheMap

      protected Map<VFXTableColumn<T,?>,ColumnsLayoutCache<T>.LayoutInfo> getCacheMap()
      Returns:
      the map containing the columns' layout data as ColumnsLayoutCache<T>.LayoutInfo objects
    • getLastColumn

      protected VFXTableColumn<T,?> getLastColumn()
      Returns:
      the local reference to the last column in the table
    • isAnyChanged

      public boolean isAnyChanged()
      Gets the value of the anyChanged property.
      Property description:
      Specifies whether any of the ColumnsLayoutCache<T>.LayoutInfo objects in getCacheMap() was invalidated.
      Returns:
      the value of the anyChanged property
      See Also:
    • anyChangedProperty

      public javafx.beans.property.ReadOnlyBooleanProperty anyChangedProperty()
      Specifies whether any of the ColumnsLayoutCache<T>.LayoutInfo objects in getCacheMap() was invalidated.
      Returns:
      the anyChanged property
      See Also:
    • setWidthFunction

      public ColumnsLayoutCache<T> setWidthFunction(BiFunction<VFXTableColumn<T,?>,Boolean,Double> widthFn)
      Sets the BiFunction 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

      public ColumnsLayoutCache<T> setPositionFunction(BiFunction<Integer,Double,Double> xPosFn)
      Sets the BiFunction 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

      public ColumnsLayoutCache<T> setVisibilityFunction(Function<VFXTableColumn<T,?>,Boolean> vFn)
      Sets the Function 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.