javafx.scene.control.Cell Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* Copyright (c) 2010, 2013, 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.CssMetaData;
import com.sun.javafx.scene.control.accessible.AccessibleListItem;
import com.sun.javafx.accessible.providers.AccessibleProvider;
import javafx.css.PseudoClass;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.css.StyleableProperty;
/**
* 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 TextFieldCell() {
* textProperty().bind(itemProperty());
* }
* }
*
*
* Key Design Goals
*
* - Both time and memory efficient for large data sets
* - Easy to build and use libraries for custom cells
* - Easy to customize cell visuals
* - Easy to customize display formatting (12.34 as $12.34 or 1234% etc)
* - Easy to extend for custom visuals
* - Easy to have "panels" of data for the visuals
* - Easy to animate the cell size or other properties
*
*
* Key Use Cases
* Following are a number of key use cases used to drive the Cell API design,
* along with code examples showing how those use cases are satisfied by this
* API. This is by no means to be considered the definitive list of capabilities
* or features supported, but rather, to provide some guidance as to how to use
* the Cell API. The examples below are focused on the ListView, but the same
* philosophy applies to TreeCells or other kinds of cells.
*
* 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;
* }
*
*
* @param The type of the item contained within the Cell.
*
* @since JavaFX 2.0
*/
public class Cell extends Labeled {
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a default Cell with the default style class of 'cell'.
*/
public Cell() {
setText(null); // default to null text, to match the null item
// focusTraversable is styleable through css. Calling setFocusTraversable
// makes it look to css like the user set the value and css will not
// override. Initializing focusTraversable by calling set on the
// CssMetaData ensures that css will be able to override the value.
((StyleableProperty)focusTraversableProperty()).applyStyle(null, Boolean.FALSE);
getStyleClass().addAll(DEFAULT_STYLE_CLASS);
/**
* Indicates whether or not this cell has focus. For example, a
* ListView defines zero or one cell as being the "focused" cell. This cell
* would have focused set to true.
*/
super.focusedProperty().addListener(new InvalidationListener() {
@Override public void invalidated(Observable property) {
pseudoClassStateChanged(PSEUDO_CLASS_FOCUSED, isFocused()); // TODO is this necessary??
// The user has shifted focus, so we should cancel the editing on this cell
if (!isFocused() && isEditing()) {
cancelEdit();
}
}
});
// initialize default pseudo-class state
pseudoClassStateChanged(PSEUDO_CLASS_EMPTY, true);
}
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- item
private ObjectProperty item = new SimpleObjectProperty(this, "item");
/**
* The data value associated with this Cell. This value is set by the
* virtualized Control when the Cell is created or updated. This represents
* the raw data value.
*
* 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.
*/
public final ObjectProperty itemProperty() { return item; }
/**
* Sets the item to the given value - should not be called directly as the
* item is managed by the virtualized control.
*/
public final void setItem(T value) { item.set(value); }
/**
* Returns the data value associated with this Cell.
*/
public final T getItem() { return item.get(); }
// --- empty
private ReadOnlyBooleanWrapper empty = new ReadOnlyBooleanWrapper(true) {
@Override protected void invalidated() {
final boolean active = get();
pseudoClassStateChanged(PSEUDO_CLASS_EMPTY, active);
pseudoClassStateChanged(PSEUDO_CLASS_FILLED, !active);
}
@Override
public Object getBean() {
return Cell.this;
}
@Override
public String getName() {
return "empty";
}
};
/**
* A property used to represent whether the cell has any contents.
* If true, then the Cell contains no data and is not associated with any
* data item in the virtualized Control.
*
* 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.
*/
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.
*/
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.
*/
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.
*/
public final boolean isEditing() {
return editing == null ? false : editing.get();
}
/**
* Property representing 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.
*/
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.
*/
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 to transition from an editing state into a non-editing
* state, and in the process saving any user input.
*
* @param newValue The value as input by the end user, which should be
* persisted in the relevant way given the data source underpinning the
* user interface.
*/
public void commitEdit(T newValue) {
if (isEditing()) {
setEditing(false);
}
}
/***************************************************************************
* *
* Expert API *
* *
**************************************************************************/
/**
*
* Updates the item associated with this Cell. This method should
* only be called by Skin implementations of ListView, TableView,
* TreeView, or other controls using Cells. It is not intended to be called
* by application developers.
*
*
* Because null
is a perfectly valid value in the application
* domain, Cell needs some way to distinguish whether or not the cell
* actually holds a value. The empty
flag indicates this.
* It is an error to supply a non-null item
but a true value for
* empty
.
*
* @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.
* @expert
*/
protected void updateItem(T item, boolean empty) {
if (isEditing()) cancelEdit();
setItem(item);
setEmpty(empty);
if (empty && isSelected()) {
updateSelected(false);
}
}
/**
* Updates whether this cell is in a selected state or not.
* @expert
* @param selected whether or not to select this cell.
*/
public void updateSelected(boolean selected) {
if (selected && isEmpty()) return;
setSelected(selected);
}
private AccessibleListItem accListItem ;
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@Deprecated @Override public AccessibleProvider impl_getAccessible() {
if( accListItem == null)
accListItem = new AccessibleListItem(this);
return (AccessibleProvider)accListItem ;
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "cell";
private static final PseudoClass PSEUDO_CLASS_SELECTED =
PseudoClass.getPseudoClass("selected");
private static final PseudoClass PSEUDO_CLASS_FOCUSED =
PseudoClass.getPseudoClass("focused");
private static final PseudoClass PSEUDO_CLASS_EMPTY =
PseudoClass.getPseudoClass("empty");
private static final PseudoClass PSEUDO_CLASS_FILLED =
PseudoClass.getPseudoClass("filled");
/**
* Most Controls return true for focusTraversable, so Control overrides
* this method to return true, but Cell returns false for
* focusTraversable's initial value; hence the override of the override.
* This method is called from CSS code to get the correct initial value.
* @treatAsPrivate implementation detail
*/
@Deprecated @Override
protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
return Boolean.FALSE;
}
}