javafx.scene.control.TreeTableColumn Maven / Gradle / Ivy
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.control;
import com.sun.javafx.scene.control.Properties;
import javafx.css.CssMetaData;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.Node;
import javafx.scene.control.skin.*;
import javafx.util.Callback;
import javafx.css.Styleable;
/**
* A {@link TreeTableView} is made up of a number of TreeTableColumn instances. Each
* TreeTableColumn in a {@link TreeTableView} is responsible for displaying
* (and editing) the contents of that column. As well as being responsible for
* displaying and editing data for a single column, a TreeTableColumn also
* contains the necessary properties to:
*
* - Be resized (using {@link #minWidthProperty() minWidth}/
* {@link #prefWidthProperty() prefWidth}/
* {@link #maxWidthProperty() maxWidth} and {@link #widthProperty() width} properties)
*
- Have its {@link #visibleProperty() visibility} toggled
*
- Display {@link #textProperty() header text}
*
- Display any {@link #getColumns() nested columns} it may contain
*
- Have a {@link #contextMenuProperty() context menu} when the user
* right-clicks the column header area
*
- Have the contents of the table be sorted (using
* {@link #comparatorProperty() comparator}, {@link #sortableProperty() sortable} and
* {@link #sortTypeProperty() sortType})
*
*
* When creating a TreeTableColumn instance, perhaps the two most important properties
* to set are the column {@link #textProperty() text} (what to show in the column
* header area), and the column {@link #cellValueFactoryProperty() cell value factory}
* (which is used to populate individual cells in the column). This can be
* achieved using some variation on the following code:
*
* {@code
* firstNameCol.setCellValueFactory(new Callback, ObservableValue>() {
* public ObservableValue call(CellDataFeatures p) {
* // p.getValue() returns the TreeItem instance for a particular TreeTableView row,
* // p.getValue().getValue() returns the Person instance inside the TreeItem
* return p.getValue().getValue().firstNameProperty();
* }
* });
* }}
*
* This approach assumes that the object returned from p.getValue().getValue()
* has a JavaFX {@link ObservableValue} that can simply be returned. The benefit of this
* is that the TableView will internally create bindings to ensure that,
* should the returned {@link ObservableValue} change, the cell contents will be
* automatically refreshed.
*
* In situations where a TableColumn must interact with classes created before
* JavaFX, or that generally do not wish to use JavaFX APIs for properties, it is
* possible to wrap the returned value in a {@link ReadOnlyObjectWrapper} instance. For
* example:
*
*
{@code
* firstNameCol.setCellValueFactory(new Callback, ObservableValue>() {
* public ObservableValue call(CellDataFeatures p) {
* // p.getValue() returns the TreeItem instance for a particular TreeTableView row,
* // p.getValue().getValue() returns the Person instance inside the TreeItem
* return new ReadOnlyObjectWrapper(p.getValue().getValue().getFirstName());
* }
* });
* }}
*
* It is hoped that over time there will be convenience cell value factories
* developed and made available to developers. As of the JavaFX 2.0 release,
* there is one such convenience class: {@link javafx.scene.control.cell.TreeItemPropertyValueFactory}.
* This class removes the need to write the code above, instead relying on reflection to
* look up a given property from a String. Refer to the
* TreeItemPropertyValueFactory
class documentation for more information
* on how to use this with a TableColumn.
*
* Finally, for more detail on how to use TableColumn, there is further documentation in
* the {@link TableView} class documentation.
*
* @param The type of the TableView generic type (i.e. S == TableView<S>)
* @param The type of the content in all cells in this TableColumn.
* @see TableView
* @see TableCell
* @see TablePosition
* @see javafx.scene.control.cell.TreeItemPropertyValueFactory
* @since JavaFX 8.0
*/
public class TreeTableColumn extends TableColumnBase,T> implements EventTarget {
/* *************************************************************************
* *
* Static properties and methods *
* *
**************************************************************************/
/**
* Parent event for any TreeTableColumn edit event.
* @param the type of the TreeTableView generic type
* @param the type of the content in all cells in this TreeTableColumn
* @return the edit event
*/
@SuppressWarnings("unchecked")
public static EventType> editAnyEvent() {
return (EventType>) EDIT_ANY_EVENT;
}
private static final EventType> EDIT_ANY_EVENT =
new EventType<>(Event.ANY, "TREE_TABLE_COLUMN_EDIT");
/**
* Indicates that the user has performed some interaction to start an edit
* event, or alternatively the
* {@link TreeTableView#edit(int, javafx.scene.control.TreeTableColumn)}
* method has been called.
* @param the type of the TreeTableView generic type
* @param the type of the content in all cells in this TreeTableColumn
* @return the edit start event
*/
@SuppressWarnings("unchecked")
public static EventType> editStartEvent() {
return (EventType>) EDIT_START_EVENT;
}
private static final EventType> EDIT_START_EVENT =
new EventType<>(editAnyEvent(), "EDIT_START");
/**
* Indicates that the editing has been canceled, meaning that no change should
* be made to the backing data source.
* @param the type of the TreeTableView generic type
* @param the type of the content in all cells in this TreeTableColumn
* @return the edit cancel event
*/
@SuppressWarnings("unchecked")
public static EventType> editCancelEvent() {
return (EventType>) EDIT_CANCEL_EVENT;
}
private static final EventType> EDIT_CANCEL_EVENT =
new EventType<>(editAnyEvent(), "EDIT_CANCEL");
/**
* Indicates that the editing has been committed by the user, meaning that
* a change should be made to the backing data source to reflect the new
* data.
* @param the type of the TreeTableView generic type
* @param the type of the content in all cells in this TreeTableColumn
* @return the edit commit event
*/
@SuppressWarnings("unchecked")
public static EventType> editCommitEvent() {
return (EventType>) EDIT_COMMIT_EVENT;
}
private static final EventType> EDIT_COMMIT_EVENT =
new EventType<>(editAnyEvent(), "EDIT_COMMIT");
/**
* If no cellFactory is specified on a TreeTableColumn instance, then this one
* will be used by default. At present it simply renders the TableCell item
* property within the {@link TableCell#graphicProperty() graphic} property
* if the {@link Cell#itemProperty() item} is a Node, or it simply calls
* toString()
if it is not null, setting the resulting string
* inside the {@link Cell#textProperty() text} property.
*/
public static final Callback, TreeTableCell,?>> DEFAULT_CELL_FACTORY =
new Callback<>() {
@Override public TreeTableCell,?> call(TreeTableColumn,?> param) {
return new TreeTableCell() {
@Override protected void updateItem(Object item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else if (item instanceof Node) {
super.setText(null);
super.setGraphic((Node)item);
} else {
super.setText(item.toString());
super.setGraphic(null);
}
}
};
}
};
/* *************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a default TreeTableColumn with default cell factory, comparator, and
* onEditCommit implementation.
*/
public TreeTableColumn() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
setOnEditCommit(DEFAULT_EDIT_COMMIT_HANDLER);
// we listen to the columns list here to ensure that widths are
// maintained properly, and to also set the column hierarchy such that
// all children columns know that this TreeTableColumn is their parent.
getColumns().addListener(weakColumnsListener);
treeTableViewProperty().addListener(observable -> {
// set all children of this tableView to have the same TableView
// as this column
for (TreeTableColumn tc : getColumns()) {
tc.setTreeTableView(getTreeTableView());
}
// This code was commented out due to RT-22391, with this enabled
// the parent column will be null, which is not desired
// // set the parent of this column to also have this tableView
// if (getParentColumn() != null) {
// getParentColumn().setTableView(getTableView());
// }
});
}
/**
* Creates a TreeTableColumn with the text set to the provided string, with
* default cell factory, comparator, and onEditCommit implementation.
*
* @param text The string to show when the TreeTableColumn is placed within
* the TreeTableView.
*/
public TreeTableColumn(String text) {
this();
setText(text);
}
/* *************************************************************************
* *
* Listeners *
* *
**************************************************************************/
private EventHandler> DEFAULT_EDIT_COMMIT_HANDLER =
t -> {
TreeItem rowValue = t.getRowValue();
if (rowValue == null) return;
ObservableValue ov = getCellObservableValue(rowValue);
if (ov instanceof WritableValue) {
((WritableValue)ov).setValue(t.getNewValue());
}
};
private ListChangeListener> columnsListener = new ListChangeListener<>() {
@Override public void onChanged(ListChangeListener.Change extends TreeTableColumn> c) {
while (c.next()) {
// update the TreeTableColumn.treeTableView property
for (TreeTableColumn tc : c.getRemoved()) {
// Fix for RT-16978. In TableColumnHeader we add before we
// remove when moving a TreeTableColumn. This means that for
// a very brief moment the tc is duplicated, and we can prevent
// nulling out the tableview and parent column. Without this
// here, in a very special circumstance it is possible to null
// out the entire content of a column by reordering and then
// sorting another column.
if (getColumns().contains(tc)) continue;
tc.setTreeTableView(null);
tc.setParentColumn(null);
}
for (TreeTableColumn tc : c.getAddedSubList()) {
tc.setTreeTableView(getTreeTableView());
}
updateColumnWidths();
}
}
};
private WeakListChangeListener> weakColumnsListener =
new WeakListChangeListener<>(columnsListener);
/* *************************************************************************
* *
* Instance Variables *
* *
**************************************************************************/
// Contains any children columns that should be nested within this column
private final ObservableList> columns = FXCollections.>observableArrayList();
/* *************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- TreeTableView
/**
* The TreeTableView that this TreeTableColumn belongs to.
*/
private ReadOnlyObjectWrapper> treeTableView =
new ReadOnlyObjectWrapper<>(this, "treeTableView");
public final ReadOnlyObjectProperty> treeTableViewProperty() {
return treeTableView.getReadOnlyProperty();
}
final void setTreeTableView(TreeTableView value) { treeTableView.set(value); }
public final TreeTableView getTreeTableView() { return treeTableView.get(); }
// --- Cell value factory
/**
* The cell value factory needs to be set to specify how to populate all
* cells within a single TreeTableColumn. A cell value factory is a {@link Callback}
* that provides a {@link CellDataFeatures} instance, and expects an
* {@link ObservableValue} to be returned. The returned ObservableValue instance
* will be observed internally to allow for updates to the value to be
* immediately reflected on screen.
*
* An example of how to set a cell value factory is:
*
*
{@code
* firstNameCol.setCellValueFactory(new Callback, ObservableValue>() {
* public ObservableValue call(CellDataFeatures p) {
* // p.getValue() returns the TreeItem instance for a particular TreeTableView row,
* // p.getValue().getValue() returns the Person instance inside the TreeItem
* return p.getValue().getValue().firstNameProperty();
* }
* });
* }}
*
* A common approach is to want to populate cells in a TreeTableColumn using
* a single value from a Java bean. To support this common scenario, there
* is the {@link javafx.scene.control.cell.TreeItemPropertyValueFactory} class.
* Refer to this class for more information on how to use it, but briefly
* here is how the above use case could be simplified using the TreeItemPropertyValueFactory class:
*
*
* firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Person,String>("firstName"));
*
*
* @see javafx.scene.control.cell.TreeItemPropertyValueFactory
*/
private ObjectProperty, ObservableValue>> cellValueFactory;
public final void setCellValueFactory(Callback, ObservableValue> value) {
cellValueFactoryProperty().set(value);
}
public final Callback, ObservableValue> getCellValueFactory() {
return cellValueFactory == null ? null : cellValueFactory.get();
}
public final ObjectProperty, ObservableValue>> cellValueFactoryProperty() {
if (cellValueFactory == null) {
cellValueFactory = new SimpleObjectProperty<>(this, "cellValueFactory");
}
return cellValueFactory;
}
// --- Cell Factory
/**
* The cell factory for all cells in this column. The cell factory
* is responsible for rendering the data contained within each TreeTableCell
* for a single TreeTableColumn.
*
* By default TreeTableColumn uses a {@link #DEFAULT_CELL_FACTORY default cell
* factory}, but this can be replaced with a custom implementation, for
* example to show data in a different way or to support editing. There is a
* lot of documentation on creating custom cell factories
* elsewhere (see {@link Cell} and {@link TreeTableView} for example).
*
* Finally, there are a number of pre-built cell factories available in the
* {@link javafx.scene.control.cell} package.
*
*/
private final ObjectProperty, TreeTableCell>> cellFactory =
new SimpleObjectProperty<>(
this, "cellFactory", ((Callback) DEFAULT_CELL_FACTORY)) {
@Override protected void invalidated() {
TreeTableView table = getTreeTableView();
if (table == null) return;
Map