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

org.controlsfx.control.table.TableRowExpanderColumn Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2016 ControlsFX
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of ControlsFX, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.controlsfx.control.table;

import impl.org.controlsfx.skin.ExpandableTableRowSkin;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.util.Callback;

import java.util.HashMap;
import java.util.Map;

/**
 * The TableRowExpanderColumn enables a TableView to provide an expandable editor below each table row.
 * The column itself contains a toggle button that on click will show an editor for the current row right below the
 * columns. Example:
 *
 * 
 * TableRowExpanderColumn<Customer> expander = new TableRowExpanderColumn<>(param -> {
 *     HBox editor = new HBox(10);
 *     TextField text = new TextField(param.getValue().getName());
 *     Button save = new Button("Save customer");
 *     save.setOnAction(event -> {
 *         save();
 *         param.toggleExpanded();
 *     });
 *     editor.getChildren().addAll(text, save);
 *     return editor;
 * });
 *
 * tableView.getColumns().add(expander);
 * 
* * You can provide a custom cellFactory to customize the toggle button. A typical custom toggle cell implementation * would look like this: * *
 * public class MyCustomToggleCell<S> extends TableCell<S, Boolean> {
 *     private Button button = new Button();
 *
 *     public MyCustomToggleCell(TableRowExpanderColumn<S> column) {
 *         button.setOnAction(event -> column.toggleExpanded(getIndex()));
 *     }
 *
 *     protected void updateItem(Boolean expanded, boolean empty) {
 *         super.updateItem(expanded, empty);
 *         if (expanded == null || empty) {
 *             setGraphic(null);
 *         } else {
 *             button.setText(expanded ? "Collapse" : "Expand");
 *             setGraphic(button);
 *         }
 *     }
 * }
 * 
* * The custom toggle cell utilizes the {@link TableRowExpanderColumn#toggleExpanded(int)} method to toggle * the row expander instead of param.toggleExpanded() like the editor does. * * @param The item type of the TableView */ public final class TableRowExpanderColumn extends TableColumn { private static final String STYLE_CLASS = "expander-column"; private static final String EXPANDER_BUTTON_STYLE_CLASS = "expander-button"; private final Map expandedNodeCache = new HashMap<>(); private final Map expansionState = new HashMap<>(); private Callback, Node> expandedNodeCallback; /** * Returns a Boolean property that can be used to manipulate the expanded state for a row * corresponding to the given item value. * * @param item The item corresponding to a table row * @return The boolean property */ public BooleanProperty getExpandedProperty(S item) { BooleanProperty value = expansionState.get(item); if (value == null) { value = new SimpleBooleanProperty(item, "expanded", false) { /** * When the expanded state change we refresh the tableview. * If the expanded state changes to false we remove the cached expanded node. */ @Override protected void invalidated() { getTableView().refresh(); if (!getValue()) expandedNodeCache.remove(getBean()); } }; expansionState.put(item, value); } return value; } /** * Get or create and cache the expanded node for a given item. * * @param tableRow The table row, used to find the item index * @return The expanded node for the given item */ public Node getOrCreateExpandedNode(TableRow tableRow) { int index = tableRow.getIndex(); if (index > -1 && index < getTableView().getItems().size()) { S item = getTableView().getItems().get(index); Node node = expandedNodeCache.get(item); if (node == null) { node = expandedNodeCallback.call(new TableRowDataFeatures<>(tableRow, this, item)); expandedNodeCache.put(item, node); } return node; } return null; } /** * Return the expanded node for the given item, if it exists. * * @param item The item corresponding to a table row * @return The expanded node, if it exists. */ public Node getExpandedNode(S item) { return expandedNodeCache.get(item); } /** * Create a row expander column that can be added to the TableView list of columns. * * The expandedNodeCallback is expected to return a Node representing the editor that should appear below the * table row when the toggle button within the expander column is clicked. * * Once this column is assigned to a TableView, it will automatically install a custom row factory for the TableView * so that it can configure a TableRow with the {@link impl.org.controlsfx.skin.ExpandableTableRowSkin}. It is within the skin that the actual * rendering of the expanded node occurs. * * @see TableRowExpanderColumn * @see TableRowDataFeatures * * @param expandedNodeCallback */ public TableRowExpanderColumn(Callback, Node> expandedNodeCallback) { this.expandedNodeCallback = expandedNodeCallback; getStyleClass().add(STYLE_CLASS); setCellValueFactory(param -> getExpandedProperty(param.getValue())); setCellFactory(param -> new ToggleCell()); installRowFactoryOnTableViewAssignment(); } /** * Install the row factory on the TableView when this column is assigned to a TableView. */ private void installRowFactoryOnTableViewAssignment() { tableViewProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { getTableView().setRowFactory(param -> new TableRow() { @Override protected Skin createDefaultSkin() { return new ExpandableTableRowSkin<>(this, TableRowExpanderColumn.this); } }); } }); } /** * The default toggle cell creates a button with a + or - sign as the text, * depending on the expanded state of the row it represents. * * You can use this as a starting point to implement a custom toggle cell. */ private final class ToggleCell extends TableCell { private Button button = new Button(); public ToggleCell() { button.setFocusTraversable(false); button.getStyleClass().add(EXPANDER_BUTTON_STYLE_CLASS); button.setPrefSize(16, 16); button.setPadding(new Insets(0)); button.setOnAction(event -> toggleExpanded(getIndex())); } @Override protected void updateItem(Boolean expanded, boolean empty) { super.updateItem(expanded, empty); if (expanded == null || empty) { setGraphic(null); } else { button.setText(expanded ? "-" : "+"); setGraphic(button); } } } /** * Toggle the expanded state of the row at the given index. * * @param index The index of the row you want to toggle expansion for. */ public void toggleExpanded(int index) { BooleanProperty expanded = (BooleanProperty) getCellObservableValue(index); expanded.setValue(!expanded.getValue()); } /** * This object is passed to the expanded node callback when it is time to create a Node to represent the * expanded editor of a certain row. The most important method is {@link #getValue()}} which returns the * object represented by the current row. * * Further more, the {@link #expandedProperty()} returns a boolean property indicating the current expansion * state of the current row. You can use this, or the {@link #toggleExpanded()} method to toggle and inspect * the expanded state of the row, for example if you want an action inside the row editor to contract the editor. * * @param The type of items in the TableView */ public static final class TableRowDataFeatures { private TableRow tableRow; private TableRowExpanderColumn tableColumn; private BooleanProperty expandedProperty; private S value; public TableRowDataFeatures(TableRow tableRow, TableRowExpanderColumn tableColumn, S value) { this.tableRow = tableRow; this.tableColumn = tableColumn; this.expandedProperty = (BooleanProperty) tableColumn.getCellObservableValue(tableRow.getIndex()); this.value = value; } /** * Return the current TableRow. It is safe to assume that the index returned by {@link TableRow#getIndex()} is * correct as long as you use it for the initial node creation. It is not safe to trust the result of this call * at any later time, for example in a button action within the row editor. * * @return The current TableRow */ public TableRow getTableRow() { return tableRow; } /** * Return the TableColumn which contains the toggle button. Normally you would not need to use this directly, * but rather consult the {@link #expandedProperty()} for inspection and mutation of the toggled state of this row. * * @return The TableColumn which contains the toggle button */ public TableRowExpanderColumn getTableColumn() { return tableColumn; } /** * The expanded property can be used to inspect or mutate the toggled state of this row editor. You can also * listen for changes to it's state if needed. * * @return The expanded property */ public BooleanProperty expandedProperty() { return expandedProperty; } /** * Toggle the expanded state of this row editor. */ public void toggleExpanded() { BooleanProperty expanded = expandedProperty(); expanded.setValue(!expanded.getValue()); } /** * Returns a boolean indicating if the current row is expanded or not * * @return A boolean indicating the expanded state of the current editor */ public Boolean isExpanded() { return expandedProperty().getValue(); } /** * Set the expanded state. This will update the {@link #expandedProperty()} accordingly. * * @param expanded Wheter the row editor should be expanded or not */ public void setExpanded(Boolean expanded) { expandedProperty().setValue(expanded); } /** * The value represented by the current table row. It is important that the value has valid equals/hashCode * methods, as the row value is used to keep track of the node editor for each row. * * @return The value represented by the current table row */ public S getValue() { return value; } } }