All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.palexdev.materialfx.controls.MFXTableView Maven / Gradle / Ivy

/*
 * Copyright (C) 2022 Parisi Alessandro
 * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
 *
 * MaterialFX is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MaterialFX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with MaterialFX.  If not, see .
 */

package io.github.palexdev.materialfx.controls;

import io.github.palexdev.materialfx.MFXResourcesLoader;
import io.github.palexdev.materialfx.beans.properties.functional.FunctionProperty;
import io.github.palexdev.materialfx.collections.TransformableList;
import io.github.palexdev.materialfx.collections.TransformableListWrapper;
import io.github.palexdev.materialfx.controls.cell.MFXTableRowCell;
import io.github.palexdev.materialfx.filter.base.AbstractFilter;
import io.github.palexdev.materialfx.selection.MultipleSelectionModel;
import io.github.palexdev.materialfx.selection.base.IMultipleSelectionModel;
import io.github.palexdev.materialfx.skins.MFXTableViewSkin;
import io.github.palexdev.materialfx.utils.ListChangeProcessor;
import io.github.palexdev.materialfx.utils.others.observables.When;
import io.github.palexdev.virtualizedfx.beans.NumberRange;
import io.github.palexdev.virtualizedfx.flow.simple.SimpleVirtualFlow;
import io.github.palexdev.virtualizedfx.utils.ListChangeHelper;
import javafx.beans.InvalidationListener;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;

import java.util.*;
import java.util.function.Function;

/**
 * This is the implementation of a table view following Google's material design guidelines in JavaFX.
 * 

* Extends {@code Control} and provides a new skin since it is built from scratch. * * @param The type of the data within the table. * @see MFXTableViewSkin */ public class MFXTableView extends Control { //================================================================================ // Properties //================================================================================ private final String STYLE_CLASS = "mfx-table-view"; private final String STYLESHEET = MFXResourcesLoader.load("css/MFXTableView.css"); protected final SimpleVirtualFlow> rowsFlow; protected final ReadOnlyBooleanWrapper virtualFlowInitialized = new ReadOnlyBooleanWrapper(); private final ObjectProperty> items = new SimpleObjectProperty<>(); private final ListChangeListener itemsChanged = this::itemsChanged; private final IMultipleSelectionModel selectionModel = new MultipleSelectionModel<>(items); private final ObservableList> tableColumns = FXCollections.observableArrayList(); private final FunctionProperty> tableRowFactory = new FunctionProperty<>(item -> new MFXTableRow<>(this, item)); private final TransformableListWrapper transformableList = new TransformableListWrapper<>(FXCollections.observableArrayList()); private final ObservableList> filters = FXCollections.observableArrayList(); private final InvalidationListener itemsInvalid = invalidated -> transformableList.setAll(getItems()); private final BooleanProperty footerVisible = new SimpleBooleanProperty(true); //================================================================================ // Constructors //================================================================================ public MFXTableView() { this(FXCollections.observableArrayList()); } public MFXTableView(ObservableList items) { setItems(items); rowsFlow = new SimpleVirtualFlow<>( transformableList, getTableRowFactory(), Orientation.VERTICAL ) { @Override public String getUserAgentStylesheet() { return MFXTableView.this.getUserAgentStylesheet(); } }; rowsFlow.cellFactoryProperty().bind(tableRowFactoryProperty()); VBox.setVgrow(rowsFlow, Priority.ALWAYS); initialize(); } //================================================================================ // Methods //================================================================================ private void initialize() { getStyleClass().add(STYLE_CLASS); transformableList.setAll(getItems()); itemsProperty().addListener((observable, oldValue, newValue) -> { if (oldValue != null) { oldValue.removeListener(itemsChanged); oldValue.removeListener(itemsInvalid); } if (newValue != null) { newValue.addListener(itemsChanged); newValue.addListener(itemsInvalid); transformableList.setAll(newValue); } }); getItems().addListener(itemsChanged); getItems().addListener(itemsInvalid); } /** * Responsible for updating the selection when the items list changes. */ protected void itemsChanged(ListChangeListener.Change change) { IMultipleSelectionModel selectionModel = getSelectionModel(); if (selectionModel.getSelection().isEmpty()) return; if (change.getList().isEmpty()) { selectionModel.clearSelection(); return; } ListChangeHelper.Change c = ListChangeHelper.processChange(change, NumberRange.of(0, Integer.MAX_VALUE)); ListChangeProcessor updater = new ListChangeProcessor(new HashSet<>(selectionModel.getSelection().keySet())); c.processReplacement((changed, removed) -> selectionModel.replaceSelection(changed.toArray(Integer[]::new))); c.processAddition((from, to, added) -> { updater.computeAddition(added.size(), from); selectionModel.replaceSelection(updater.getIndexes().toArray(Integer[]::new)); }); c.processRemoval((from, to, removed) -> { updater.computeRemoval(removed, from); getSelectionModel().replaceSelection(updater.getIndexes().toArray(Integer[]::new)); }); } /** * Allows to programmatically update the table. *

* Uses {@link MFXTableRow#updateRow()} on the currently built rows, {@link SimpleVirtualFlow#getCells()}. */ public void update() { rowsFlow.getCells().values().forEach(MFXTableRow::updateRow); } /** * Autosize all the table columns. */ public void autosizeColumns() { tableColumns.forEach(this::autosizeColumn); } /** * Autosizes the column at the given index. *

* This method fails silently if it can not get the column at index. */ public void autosizeColumn(int index) { try { MFXTableColumn column = tableColumns.get(index); autosizeColumn(column); } catch (Exception ignored) { } } /** * Autosizes the given column. */ public void autosizeColumn(MFXTableColumn column) { int index = tableColumns.indexOf(column); if (index == -1) return; Collection> rows = rowsFlow.getCells().values(); List minSizes = new ArrayList<>(); minSizes.add(column.getWidth()); rows.forEach(row -> { ObservableList> rowCells = row.getCells(); if (rowCells.isEmpty()) return; MFXTableRowCell rowCell = rowCells.get(index); rowCell.requestLayout(); minSizes.add(rowCell.computePrefWidth(-1)); }); double max = minSizes.stream().max(Double::compareTo).orElse(-1.0); if (max != -1.0) { column.setMinWidth(max); } } /** * This should be called only if you need to autosize the columns * before the table is laid out/initialized. *

* Calling this afterwards won't have any effect. */ public void autosizeColumnsOnInitialization() { if (isVirtualFlowInitialized()) return; When.onChanged(virtualFlowInitialized) .then((oldValue, newValue) -> autosizeColumns()) .oneShot() .listen(); } //================================================================================ // Delegate Methods //================================================================================ /** * Delegate for {@link SimpleVirtualFlow#getCell(int)}. */ public MFXTableRow getCell(int index) { return rowsFlow.getCell(index); } /** * Delegate for {@link SimpleVirtualFlow#getCells()}. */ public Map> getCells() { return rowsFlow.getCells(); } /** * Delegate for {@link SimpleVirtualFlow#scrollBy(double)}. */ public void scrollBy(double pixels) { rowsFlow.scrollBy(pixels); } /** * Delegate for {@link SimpleVirtualFlow#scrollTo(int)}. */ public void scrollTo(int index) { rowsFlow.scrollTo(index); } /** * Delegate for {@link SimpleVirtualFlow#scrollToFirst()}. */ public void scrollToFirst() { rowsFlow.scrollToFirst(); } /** * Delegate for {@link SimpleVirtualFlow#scrollToLast()}. */ public void scrollToLast() { rowsFlow.scrollToLast(); } /** * Delegate for {@link SimpleVirtualFlow#scrollToPixel(double)}. */ public void scrollToPixel(double pixel) { rowsFlow.scrollToPixel(pixel); } /** * Delegate for {@link SimpleVirtualFlow#setHSpeed(double, double)}. */ public void setHSpeed(double unit, double block) { rowsFlow.setHSpeed(unit, block); } /** * Delegate for {@link SimpleVirtualFlow#setVSpeed(double, double)}. */ public void setVSpeed(double unit, double block) { rowsFlow.setVSpeed(unit, block); } /** * Delegate for {@link SimpleVirtualFlow#features()}. */ public SimpleVirtualFlow>.Features features() { return rowsFlow.features(); } //================================================================================ // Overridden Methods //================================================================================ @Override protected Skin createDefaultSkin() { return new MFXTableViewSkin<>(this, rowsFlow); } @Override public String getUserAgentStylesheet() { return STYLESHEET; } @Override protected void layoutChildren() { super.layoutChildren(); if (!isVirtualFlowInitialized() && rowsFlow.getCellHeight() > 0) virtualFlowInitialized.set(true); } //================================================================================ // Getters/Setters //================================================================================ public ObservableList getItems() { return items.get(); } /** * Specifies the table's {@link ObservableList} containing the items. */ public ObjectProperty> itemsProperty() { return items; } public void setItems(ObservableList items) { this.items.set(items); } /** * @return the selection model used by the table to handle row selection */ public IMultipleSelectionModel getSelectionModel() { return selectionModel; } /** * @return the list containing the table's columns */ public ObservableList> getTableColumns() { return tableColumns; } public Function> getTableRowFactory() { return tableRowFactory.get(); } /** * Specifies the {@link Function} used to generate the table rows. */ public FunctionProperty> tableRowFactoryProperty() { return tableRowFactory; } public void setTableRowFactory(Function> tableRowFactory) { this.tableRowFactory.set(tableRowFactory); } /** * @return the list that is effectively used by the {@link SimpleVirtualFlow} (which contains the table rows). * This list is capable of filtering and sorting. * @see TransformableListWrapper * @see TransformableList */ public TransformableListWrapper getTransformableList() { return transformableList; } /** * @return the list containing the filters' information used by the * {@link MFXFilterPane} to filter the table */ public ObservableList> getFilters() { return filters; } public boolean isFooterVisible() { return footerVisible.get(); } /** * Specifies whether the table's footer is visible */ public BooleanProperty footerVisibleProperty() { return footerVisible; } public void setFooterVisible(boolean footerVisible) { this.footerVisible.set(footerVisible); } public boolean isVirtualFlowInitialized() { return virtualFlowInitialized.get(); } /** * Useful property to inform that the table layout * has been initialized/is ready. *

* For example it is used by {@link #autosizeColumnsOnInitialization()} * to autosize the columns before the table is even laid out by using a * listener. *

* It is considered initialized as soon as the {@link SimpleVirtualFlow} * retrieves the cells' height. */ public ReadOnlyBooleanProperty virtualFlowInitializedProperty() { return virtualFlowInitialized.getReadOnlyProperty(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy