Class VFXTableSkin<T>
VFXTable, extends MFXSkinBase and expects behaviors of type
VFXTableManager.
The table is organized in columns, rows and cells. This architecture leads to more complex layout compared to other
containers because it comprises many more nodes. The 'viewport' node wraps two Panes:
1) one contains the table's columns, can be selected in CSS as '.columns'
2) the other contains the rows, can be selected in CSS as '.rows'
The table's height and the viewport height are different here. The latter is given by the table's height minus
the columns pane height, specified by VFXTable.columnsSizeProperty(). The rows are given by the
VFXTable.stateProperty() and depends on the viewport's height. Each column produces one cell per row.
Columns and cells are kept aligned by the layout methods defined in VFXTableHelper.
A: scrolling in a table is a bit peculiar because: vertical scrolling should affect only the rows, while horizontal scrolling should affect both rows and columns. For such reason, a clip node is set on the rows container and avoids rows overflow on vertical scroll.
As all skins typically do, this is also responsible for catching any change in the component's properties. The computation that leads to a new state is delegated to the controller/behavior, which is theVFXTableManager.
Read this addListeners() to check which changes are handled.
Last but not least, by design, this skin makes the component always be at least 100px tall and wide. You can change this
by overriding the DEFAULT_SIZE variable.-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected voidAdds listeners on the component's properties which need to produce a newVFXTableStateupon changing.protected doublecomputeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) protected doublecomputeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) voiddispose()protected VFXTableManager<T> protected voidlayout()This method redefines the viewport node layout.protected voidThis is responsible for sizing and positioning the columns specified by the currentVFXTableState.getColumnsRange().protected voidThis is responsible for sizing and positioning both the rows and their cells.protected voidonLayoutCompleted(boolean done) This must be called after processing aViewportLayoutRequestto reset theVFXTable.needsViewportLayoutProperty()toViewportLayoutRequest.NULL.protected voidThere are certain situations in which it's not necessary to re-compute the whole table layout, but it's enough to only compute it partially, starting from a specific column.protected voidupdateColumnIndex(VFXTableColumn<T, ?> column, int index) This can be called during layout or other operations to update the given column'sVFXTableColumn.indexProperty()to the given index.Methods inherited from class io.github.palexdev.mfxcore.controls.MFXSkinBase
events, getBehaviorAs, getControl, listeners, registerBehaviorMethods inherited from class javafx.scene.control.SkinBase
computeBaselineOffset, computeMaxHeight, computeMaxWidth, computePrefHeight, computePrefWidth, consumeMouseEvents, executeAccessibleAction, getChildren, getClassCssMetaData, getCssMetaData, getNode, getSkinnable, layoutChildren, layoutInArea, layoutInArea, layoutInArea, positionInArea, positionInArea, pseudoClassStateChanged, queryAccessibleAttribute, registerChangeListener, registerInvalidationListener, registerListChangeListener, snappedBottomInset, snappedLeftInset, snappedRightInset, snappedTopInset, snapPosition, snapPositionX, snapPositionY, snapSize, snapSizeX, snapSizeY, snapSpace, snapSpaceX, snapSpaceY, unregisterChangeListeners, unregisterInvalidationListeners, unregisterListChangeListeners
-
Field Details
-
DEFAULT_SIZE
protected double DEFAULT_SIZE
-
-
Constructor Details
-
VFXTableSkin
-
-
Method Details
-
addListeners
protected void addListeners()Adds listeners on the component's properties which need to produce a newVFXTableStateupon changing.Here's the list:
- Listener on
VFXTable.getColumns(), will invokeVFXTableManager.onColumnsChanged(ListChangeListener.Change). The method is also invoked the first time here withnullas parameter to ensure that columns are initialized for the first time.- Listener on
VFXTable.stateProperty(), this is crucial to update the columns and rows containers' children, invokeVFXTable.requestViewportLayout()ifVFXTableState.isLayoutNeeded()istrue. Skips everything if the current state was cloned,VFXTableState.isClone()- Listener on
VFXTable.needsViewportLayoutProperty(), this is crucial because invokes bothlayoutColumns()andlayoutRows(). The layout is performed only ifViewportLayoutRequest.isValid()returnstrue. Also, if the request carries a specific column (ViewportLayoutRequest.column()), the layout will be computed only partially by invokingpartialLayout()instead.- Listener on
VFXTable.helperProperty(), this is crucial because it's responsible for binding the viewport's translateX and rows container's translateY properties to theVFXContainerHelper.viewportPositionProperty(). By translating the viewport, we give the illusion of scrolling (virtual scrolling)- Listener on
Region.widthProperty(), will invokeVFXTableManager.onGeometryChanged(GeometryChangeType)- Listener on
Region.heightProperty(), will invokeVFXTableManager.onGeometryChanged(GeometryChangeType)- Listener on
VFXTable.columnsBufferSizeProperty(), will invokeVFXTableManager.onGeometryChanged(GeometryChangeType)- Listener on
VFXTable.rowsBufferSizeProperty(), will invokeVFXTableManager.onGeometryChanged(GeometryChangeType)- Listener on
VFXTable.vPosProperty(), will invokeVFXTableManager.onPositionChanged(Orientation)- Listener on
VFXTable.hPosProperty(), will invokeVFXTableManager.onPositionChanged(Orientation)- Listener on
VFXTable.itemsProperty(), will invokeVFXTableManager.onItemsChanged()- Listener on
VFXTable.rowFactoryProperty(), will invokeVFXTableManager.onRowFactoryChanged()- Listener on
VFXTable.rowsHeightProperty(), will invokeVFXTableManager.onRowHeightChanged()- Listener on
VFXTable.columnsSizeProperty(), will invokeVFXTableManager.onColumnsSizeChanged()- Listener on
Note: in JavaFX there is no way to prioritize a listener over another, rather, the priority is given by which is added first (behind the scenes there must be a plain for loop running to call all the listeners). This causes a nasty bug regarding the table's width when using theVFXTable.columnsLayoutModeProperty(), will invokeVFXTableManager.onColumnsLayoutModeChanged()ColumnsLayoutMode.VARIABLE. In that mode we cannot proceed with theVFXTableManager.onGeometryChanged(GeometryChangeType)method before theColumnsLayoutCacheis invalidated. A simple workaround for this, is to use aChangeListenerinstead of a plainInvalidationListenerfor theRegion.widthProperty(), because the latter type will ALWAYS be invoked BEFORE the other listeners type. In my opinion, this mechanism is stupid and broken, bindings invalidation should ALWAYS happen before anything else! -
layout
protected void layout()This method redefines the viewport node layout. It's responsible for positioning and sizing both the columns and rows containers.- See Also:
-
layoutColumns
protected void layoutColumns()This is responsible for sizing and positioning the columns specified by the currentVFXTableState.getColumnsRange().If the state is
VFXTableState.INVALIDexits immediately.The columns are actually laid out by using
VFXTableHelper.layoutColumn(int, VFXTableColumn). The layout index is given by an external 'i' counter which starts at 0 and is incremented at each loop iteration.This is also responsible for updating the
VFXTableColumn.indexProperty()by callingupdateColumnIndex(VFXTableColumn, int). Why here? Because this core method will ensure all columns will always have the correct index set.- See Also:
-
layoutRows
protected void layoutRows()This is responsible for sizing and positioning both the rows and their cells.If the current
VFXTableStateis eitherVFXTableState.INVALIDorVFXTableState.isEmpty()then exits and callsonLayoutCompleted(boolean)withfalseas parameter.The layout is computed by iterating over the rows given by
VFXTableState.getRowsByIndex(). Each row is laid out byVFXTableHelper.layoutRow(int, VFXTableRow), and on each rowVFXTableRow.layoutCells()is called (this is actually responsible for the cells' layout). The layout index is given by an external 'i' counter which starts at 0 and is incremented at each loop iteration.If the loop completes successfully,
onLayoutCompleted(boolean)is invoked withtrueas parameter. -
partialLayout
protected void partialLayout()There are certain situations in which it's not necessary to re-compute the whole table layout, but it's enough to only compute it partially, starting from a specific column. This is indeed a good optimization, especially when using theColumnsLayoutMode.VARIABLEmode.Examples of when this may happen: 1) when in
So, how does this work?FIXEDmode, the table's width exceeds thevirtualMaxX, which means that only the last column (and all its related cells) needs to be resized to fill the space; 2) when inVARIABLEmode, for a column that changes its width, we need to recompute the layout only for the column itself and the others that come after.If using the
If usingColumnsLayoutMode.FIXED, we simply callVFXTableHelper.layoutColumn(int, VFXTableColumn)on the column given byViewportLayoutRequest.column()(which is expected to be the last column in the table). Then iterates on all the rows in the state,VFXTableState.getRowsByIndex(), resize each of them because thevirtualMaxXhas probably changed, then from each row retrieves the column's related cell and callVFXTableHelper.layoutRow(int, VFXTableRow). Note: the layout index is given byIntegerRange.diff()onVFXTableState.getColumnsRange().ColumnsLayoutMode.VARIABLEtwo things can happen:1) if the column carried by
ViewportLayoutRequest.column()is the last one in the table, then we re-compute the whole layout. The issue is that there are some edge cases that may not be easy to manage, so the strategy here is to go for stability rather than performance (also because handling all the edge cases may actually harm it).2) for any other column we can actually optimize. First, it loops over the columns starting from the index of the changed column. Each column is resized and repositioned by
VFXTableHelper.layoutColumn(int, VFXTableColumn). Then iterates over the rows given byVFXTableState.getRowsByIndex(), iterates over then and resizes all of them by usingVFXTableHelper.layoutRow(int, VFXTableRow). In a nested loop, for each row, it updates only the cells from the aforementioned start index, usesVFXTableHelper.layoutCell(int, VFXTableCell).Finally calls
onLayoutCompleted(boolean)withtrueas parameter. -
onLayoutCompleted
protected void onLayoutCompleted(boolean done) This must be called after processing aViewportLayoutRequestto reset theVFXTable.needsViewportLayoutProperty()toViewportLayoutRequest.NULL. -
updateColumnIndex
This can be called during layout or other operations to update the given column'sVFXTableColumn.indexProperty()to the given index. This is indeed a strange place to do so, but as it turns out, layout methods are the most reliable to ensure columns will always have the correct index. -
computeMinWidth
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) - Overrides:
computeMinWidthin classSkinBase<VFXTable<T>>
-
computeMinHeight
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) - Overrides:
computeMinHeightin classSkinBase<VFXTable<T>>
-
dispose
-
getBehavior
-