/* * Copyright (c) 2010, 2017, 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 javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.shape.Rectangle; import javafx.css.PseudoClass; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.value.WritableValue; import javafx.css.StyleableProperty; import java.util.Optional; /** * The Cell API is used for virtualized controls such as {@link ListView}, * {@link TreeView}, and {@link TableView}. * A Cell is a {@link Labeled} {@link Control}, and is used to render a single * "row" inside a ListView, TreeView or TableView. Cells are also used for each * individual 'cell' inside a TableView (i.e. each row/column intersection). See * the JavaDoc for each control separately for more detail. *
* Every Cell is associated with a single data item (represented by the * {@link #itemProperty() item} property). The Cell is responsible for * rendering that item and, where appropriate, for editing the item. An item * within a Cell may be represented by text or some other control such as a * {@link CheckBox}, {@link ChoiceBox} or any other {@link Node} such as a * {@link HBox}, {@link GridPane}, or even a {@link Rectangle}. *
* Because TreeView, ListView, TableView and other such controls can potentially * be used for displaying incredibly large amounts of data, it is not feasible * to create an actual Cell for every single item in the control. * We represent extremely large data sets using only very few Cells. Each Cell * is "recycled", or reused. This is what we mean when we say that these controls * are virtualized. *
* Since Cell is a Control, it is essentially a "model". Its Skin is responsible * for defining the look and layout, while the Behavior is responsible for * handling all input events and using that information to modify the Control * state. Also, the Cell is styled from CSS just like any other Control. * However, it is not necessary to implement a Skin for most uses of a Cell. * This is because a cell factory can be set - this is detailed more shortly. *
* Because by far the most common use case for cells is to show text to a user, * this use case is specially optimized for within Cell. This is done by Cell * extending from {@link Labeled}. This means that subclasses of Cell need only * set the {@link #textProperty() text} property, rather than create a separate * {@link Label} and set that within the Cell. However, for situations where * something more than just plain text is called for, it is possible to place * any {@link Node} in the Cell {@link #graphicProperty() graphic} property. * Despite the term, a graphic can be any Node, and will be fully interactive. * For example, a ListCell might be configured with a {@link Button} as its * graphic. The Button text could then be bound to the cells * {@link #itemProperty() item} property. In this way, whenever the item in the * Cell changes, the Button text is automatically updated. *
* Cell sets focusTraversable to false. *
** Cell Factories *
* The default representation of the Cell item
is up to the various
* virtualized container's skins to render. For example, the ListView by default
* will convert the item to a String and call {@link #setText(java.lang.String)}
* with this value. If you want to specialize the Cell used for the
* ListView (for example), then you must provide an implementation of the
* {@link ListView#cellFactoryProperty() cellFactory} callback function defined
* on the ListView. Similar API exists on most controls that use Cells (for example,
* {@link TreeView#cellFactoryProperty() TreeView},
* {@link TableView#rowFactoryProperty() TableView},
* {@link TableColumn#cellFactoryProperty() TableColumn} and
* {@link ListView#cellFactoryProperty() ListView}.
*
* The cell factory is called by the platform whenever it determines that a new * cell needs to be created. For example, perhaps your ListView has 10 million * items. Creating all 10 million cells would be prohibitively expensive. So * instead the ListView skin implementation might only create just enough cells * to fit the visual space. If the ListView is resized to be larger, the system * will determine that it needs to create some additional cells. In this case * it will call the cellFactory callback function (if one is provided) to create * the Cell implementation that should be used. If no cell factory is provided, * the built-in default implementation will be used. *
* The implementation of the cell factory is then responsible not just for * creating a Cell instance, but also configuring that Cell such that it reacts * to changes in its state. For example, if I were to create * a custom Cell which formatted Numbers such that they would appear as currency * types, I might do so like this: * *
* public class MoneyFormatCell extends ListCell<Number> { * * public MoneyFormatCell() { } * * @Override protected void updateItem(Number item, boolean empty) { * // calling super here is very important - don't skip this! * super.updateItem(item, empty); * * // format the number as if it were a monetary value using the * // formatting relevant to the current locale. This would format * // 43.68 as "$43.68", and -23.67 as "-$23.67" * setText(item == null ? "" : NumberFormat.getCurrencyInstance().format(item)); * * // change the text fill based on whether it is positive (green) * // or negative (red). If the cell is selected, the text will * // always be white (so that it can be read against the blue * // background), and if the value is zero, we'll make it black. * if (item != null) { * double value = item.doubleValue(); * setTextFill(isSelected() ? Color.WHITE : * value == 0 ? Color.BLACK : * value < 0 ? Color.RED : Color.GREEN); * } * } * }* * This class could then be used inside a ListView as such: * *
* ObservableList<Number> money = ...; * final ListView<Number> listView = new ListView<Number>(money); * listView.setCellFactory(new Callback<ListView<Number>, ListCell<Number>>() { * @Override public ListCell<Number> call(ListView<Number> list) { * return new MoneyFormatCell(); * } * });* * In this example an anonymous inner class is created, that simply returns * instances of MoneyFormatCell whenever it is called. The MoneyFormatCell class * extends {@link ListCell}, overriding the * {@link #updateItem(java.lang.Object, boolean) updateItem} method. This method * is called whenever the item in the cell changes, for example when the user * scrolls the ListView or the content of the underlying data model changes * (and the cell is reused to represent some different item in the ListView). * Because of this, there is no need to manage bindings - simply react to the * change in items when this method occurs. In the example above, whenever the * item changes, we update the cell text property, and also modify the text fill * to ensure that we get the correct visuals. In addition, if the cell is "empty" * (meaning it is used to fill out space in the ListView but doesn't have any * data associated with it), then we just use the empty String. *
* Note that there are additional * methods prefixed with 'update' that may be of interest, so be * sure to read the API documentation for Cell, and subclasses of Cell, closely. *
* Of course, we can also use the binding API rather than overriding the * 'update' methods. Shown below is a very trivial example of how this could * be achieved. * * *
* public class BoundLabelCell extends ListCell<String> { * * public BoundLabelCell() { * textProperty().bind(itemProperty()); * } * } ** *
* Changing the Cell's Colors *
* This should be extraordinarily simple in JavaFX. Each Cell can be styled * directly from CSS. So for example, if you wanted to change the default * background of every cell in a ListView to be WHITE you could do the * following CSS: * *
* .list-cell { * -fx-padding: 3 3 3 3; * -fx-background-color: white; * }* * If you wanted to set the color of selected ListView cells to be blue, you * could add this to your CSS file: * *
* .list-cell:selected { * -fx-background-color: blue; * }* * Most Cell implementations extend from {@link IndexedCell} rather than Cell. * IndexedCell adds two other pseudoclass states: "odd" and "even". Using this * you can get alternate row striping by doing something like this in your CSS * file: * *
* .list-cell:odd { * -fx-background-color: grey; * }* * Each of these examples require no code changes. Simply update your CSS * file to alter the colors. You can also use the "hover" and other * pseudoclasses in CSS the same as with other controls. *
* Another approach to the first example above (formatting a list of numbers) would * be to use style classes. Suppose you had an {@link ObservableList} of Numbers * to display in a ListView and wanted to color all of the negative values red * and all positive or 0 values black. * One way to achieve this is with a custom cellFactory which changes the * styleClass of the Cell based on whether the value is negative or positive. This * is as simple as adding code to test if the number in the cell is negative, * and adding a "negative" styleClass. If the number is not negative, the "negative" * string should be removed. This approach allows for the colors to be defined * from CSS, allowing for simple customization. The CSS file would then include * something like the following: * *
* .list-cell { * -fx-text-fill: black; * } * * .list-cell .negative { * -fx-text-fill: red; * }* *
Most virtualized controls that use the Cell architecture (e.g. {@link ListView}, * {@link TreeView}, {@link TableView} and {@link TreeTableView}) all support * the notion of editing values directly via the cell. You can learn more about * the control-specific details by going to the 'editing' section in the class * documentation for the controls linked above. The remainder of this section * will cover some of the finer details of editing with cells.
* *The general flow of editing is as follows (note that in these steps the * {@link ListView} control is used as an example, but similar API exists for * all controls mentioned above, and the process is exactly the same in general):
* *This value should only be set in subclasses of Cell by the virtualised
* user interface controls that know how to properly work with the Cell
* class.
* @return the data value associated with this cell
*/
public final ObjectProperty When a cell is empty, it can be styled differently via the 'empty'
* CSS pseudo class state. For example, it may not receive any
* alternate row highlighting, or it may not receive hover background
* fill when hovered.
* @return the representation of whether this cell has any contents
*/
public final ReadOnlyBooleanProperty emptyProperty() { return empty.getReadOnlyProperty(); }
private void setEmpty(boolean value) { empty.set(value); }
/**
* Returns a boolean representing whether the cell is considered to be empty
* or not.
* @return true if cell is empty, otherwise false
*/
public final boolean isEmpty() { return empty.get(); }
// --- selected
private ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper() {
@Override protected void invalidated() {
pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, get());
}
@Override
public Object getBean() {
return Cell.this;
}
@Override
public String getName() {
return "selected";
}
};
/**
* Indicates whether or not this cell has been selected. For example, a
* ListView defines zero or more cells as being the "selected" cells.
* @return the representation of whether this cell has been selected
*/
public final ReadOnlyBooleanProperty selectedProperty() { return selected.getReadOnlyProperty(); }
void setSelected(boolean value) { selected.set(value); }
/**
* Returns whether this cell is currently selected or not.
* @return True if the cell is selected, false otherwise.
*/
public final boolean isSelected() { return selected.get(); }
// --- Editing
private ReadOnlyBooleanWrapper editing;
private void setEditing(boolean value) {
editingPropertyImpl().set(value);
}
/**
* Represents whether the cell is currently in its editing state or not.
* @return true if this cell is currently in its editing state, otherwise
* false
*/
public final boolean isEditing() {
return editing == null ? false : editing.get();
}
/**
* Property representing whether this cell is currently in its editing state.
* @return the representation of whether this cell is currently in its
* editing state
*/
public final ReadOnlyBooleanProperty editingProperty() {
return editingPropertyImpl().getReadOnlyProperty();
}
private ReadOnlyBooleanWrapper editingPropertyImpl() {
if (editing == null) {
editing = new ReadOnlyBooleanWrapper(this, "editing");
}
return editing;
}
// --- Editable
private BooleanProperty editable;
/**
* Allows for certain cells to not be able to be edited. This is useful in
* cases where, say, a List has 'header rows' - it does not make sense for
* the header rows to be editable, so they should have editable set to
* false.
*
* @param value A boolean representing whether the cell is editable or not.
* If true, the cell is editable, and if it is false, the cell can not
* be edited.
*/
public final void setEditable(boolean value) {
editableProperty().set(value);
}
/**
* Returns whether this cell is allowed to be put into an editing state.
* @return true if this cell is allowed to be put into an editing state,
* otherwise false
*/
public final boolean isEditable() {
return editable == null ? true : editable.get();
}
/**
* A property representing whether this cell is allowed to be put into an
* editing state. By default editable is set to true in Cells (although for
* a subclass of Cell to be allowed to enter its editing state, it may have
* to satisfy additional criteria. For example, ListCell requires that the
* ListView {@link ListView#editableProperty() editable} property is also
* true.
* @return the representation of whether this cell is allowed to be put into
* an editing state
*/
public final BooleanProperty editableProperty() {
if (editable == null) {
editable = new SimpleBooleanProperty(this, "editable", true);
}
return editable;
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
/**
* Call this function to transition from a non-editing state into an editing
* state, if the cell is editable. If this cell is already in an editing
* state, it will stay in it.
*/
public void startEdit() {
if (isEditable() && !isEditing() && !isEmpty()) {
setEditing(true);
}
}
/**
* Call this function to transition from an editing state into a non-editing
* state, without saving any user input.
*/
public void cancelEdit() {
if (isEditing()) {
setEditing(false);
}
}
/**
* Call this function when appropriate (based on the user interaction requirements
* of your cell editing user interface) to do two things:
*
* In general there is no need to override this method in custom cell
* implementations - it should be sufficient to simply call this method
* when appropriate (e.g. when the user pressed the Enter key, you may do something
* like {@code cell.commitEdit(converter.fromString(textField.getText()));} It is very important that subclasses
* of Cell override the updateItem method properly, as failure to do so will
* lead to issues such as blank cells or cells with unexpected content
* appearing within them. Here is an example of how to properly override the
* updateItem method:
*
* Note in this code sample two important points:
* The default implementation of this method tests against equality, but
* developers are able to override this method to perform checks in other ways
* that are specific to their domain.
*
*
*
* protected void updateItem(T item, boolean empty) {
* super.updateItem(item, empty);
*
* if (empty || item == null) {
* setText(null);
* setGraphic(null);
* } else {
* setText(item.toString());
* }
* }
*
*
*
*
*
* @param item The new item for the cell.
* @param empty whether or not this cell represents data from the list. If it
* is empty, then it does not represent any domain data, but is a cell
* being used to render an "empty" row.
*/
protected void updateItem(T item, boolean empty) {
setItem(item);
setEmpty(empty);
if (empty && isSelected()) {
updateSelected(false);
}
}
/**
* Updates whether this cell is in a selected state or not.
* @param selected whether or not to select this cell.
*/
public void updateSelected(boolean selected) {
if (selected && isEmpty()) return;
boolean wasSelected = isSelected();
setSelected(selected);
if (wasSelected != selected) {
markCellDirty();
}
}
/**
* This method is called by Cell subclasses so that certain CPU-intensive
* actions (specifically, calling {@link #updateItem(Object, boolean)}) are
* only performed when necessary (that is, they are only performed
* when the currently set {@link #itemProperty() item} is considered to be
* different than the proposed new item that could be set).
*
* empty
condition, and if true, we
* set the text and graphic properties to null. If we do not do this,
* it is almost guaranteed that end users will see graphical artifacts
* in cells unexpectedly.