/*
* Copyright (c) 2011, 2018, 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.cell;
import javafx.beans.NamedArg;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.util.Callback;
import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;
import com.sun.javafx.property.PropertyReference;
import com.sun.javafx.scene.control.Logging;
A convenience implementation of the Callback interface, designed specifically for use within the TableColumn
cell value factory
. An example of how to use this class is:
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
In this example, Person
is the class type of the TableView
items
list. The class Person
must be declared public. PropertyValueFactory
uses the constructor argument, "firstName"
, to assume that Person
has a public method firstNameProperty
with no formal parameters and a return type of ObservableValue<String>
.
If such a method exists, then it is invoked, and additionally assumed to return an instance of Property<String>
. The return value is used to populate the TableCell
. In addition, the TableView
adds an observer to the return value, such that any changes fired will be observed by the TableView
, resulting in the cell immediately updating.
If no such method exists, then PropertyValueFactory
assumes that Person
has a public method getFirstName
or isFirstName
with no formal parameters and a return type of String
. If such a method exists, then it is invoked, and its return value is wrapped in a ReadOnlyObjectWrapper
and returned to the TableCell
. In this situation, the TableCell
will not be able to observe changes to the property, unlike in the first approach above.
For reference (and as noted in the TableColumn TableColumn.cellValueFactoryProperty()
cell value factory} documentation), the long form of the code above would be the following:
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
// p.getValue() returns the Person instance for a particular TableView row
return p.getValue().firstNameProperty();
}
});
}
Deploying an Application as a Module
If the referenced class is in a named module, then it must be reflectively accessible to the javafx.base
module. A class is reflectively accessible if the module opens
the containing package to at least the javafx.base
module. Otherwise the call(TableColumn.CellDataFeatures)
method will log a warning and return null
.
For example, if the Person
class is in the com.foo
package in the foo.app
module, the module-info.java
might look like this:
module foo.app {
opens com.foo to javafx.base;
Alternatively, a class is reflectively accessible if the module exports
the containing package unconditionally.
Type parameters: See Also: Since: JavaFX 2.0
/**
* A convenience implementation of the Callback interface, designed specifically
* for use within the {@link TableColumn}
* {@link TableColumn#cellValueFactoryProperty() cell value factory}. An example
* of how to use this class is:
*
* <pre><code>
* TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
* firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
* </code></pre>
*
* <p>
* In this example, {@code Person} is the class type of the {@code TableView}
* {@link TableView#itemsProperty() items} list.
* The class {@code Person} must be declared public.
* {@code PropertyValueFactory} uses the constructor argument,
* {@code "firstName"}, to assume that {@code Person} has a public method
* {@code firstNameProperty} with no formal parameters and a return type of
* {@code ObservableValue<String>}.
* </p>
* <p>
* If such a method exists, then it is invoked, and additionally assumed
* to return an instance of {@code Property<String>}. The return value is used
* to populate the {@link TableCell}. In addition, the {@code TableView} adds
* an observer to the return value, such that any changes fired will be observed
* by the {@code TableView}, resulting in the cell immediately updating.
* </p>
* <p>
* If no such method exists, then {@code PropertyValueFactory}
* assumes that {@code Person} has a public method {@code getFirstName} or
* {@code isFirstName} with no formal parameters and a return type of
* {@code String}. If such a method exists, then it is invoked, and its return
* value is wrapped in a {@link ReadOnlyObjectWrapper}
* and returned to the {@code TableCell}. In this situation,
* the {@code TableCell} will not be able to observe changes to the property,
* unlike in the first approach above.
* </p>
*
* <p>For reference (and as noted in the TableColumn
* {@link TableColumn#cellValueFactoryProperty()} cell value factory} documentation), the
* long form of the code above would be the following:
* </p>
*
* <pre><code>
* TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
* firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
* public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
* // p.getValue() returns the Person instance for a particular TableView row
* return p.getValue().firstNameProperty();
* }
* });
* }
* </code></pre>
*
* <p><b>Deploying an Application as a Module</b></p>
* <p>
* If the referenced class is in a named module, then it must be reflectively
* accessible to the {@code javafx.base} module.
* A class is reflectively accessible if the module
* {@link Module#isOpen(String,Module) opens} the containing package to at
* least the {@code javafx.base} module.
* Otherwise the {@link #call call(TableColumn.CellDataFeatures)} method
* will log a warning and return {@code null}.
* </p>
* <p>
* For example, if the {@code Person} class is in the {@code com.foo} package
* in the {@code foo.app} module, the {@code module-info.java} might
* look like this:
* </p>
*
<pre>{@code module foo.app {
opens com.foo to javafx.base;
}}</pre>
*
* <p>
* Alternatively, a class is reflectively accessible if the module
* {@link Module#isExported(String) exports} the containing package
* unconditionally.
* </p>
*
* @see TableColumn
* @see TableView
* @see TableCell
* @see TreeItemPropertyValueFactory
* @see MapValueFactory
* @param <S> The type of the class contained within the TableView.items list.
* @param <T> The type of the class contained within the TableColumn cells.
* @since JavaFX 2.0
*/
public class PropertyValueFactory<S,T> implements Callback<CellDataFeatures<S,T>, ObservableValue<T>> {
private final String property;
private Class<?> columnClass;
private String previousProperty;
private PropertyReference<T> propertyRef;
Creates a default PropertyValueFactory to extract the value from a given
TableView row item reflectively, using the given property name.
Params: - property – The name of the property with which to attempt to
reflectively extract a corresponding value for in a given object.
/**
* Creates a default PropertyValueFactory to extract the value from a given
* TableView row item reflectively, using the given property name.
*
* @param property The name of the property with which to attempt to
* reflectively extract a corresponding value for in a given object.
*/
public PropertyValueFactory(@NamedArg("property") String property) {
this.property = property;
}
{@inheritDoc} /** {@inheritDoc} */
@Override public ObservableValue<T> call(CellDataFeatures<S,T> param) {
return getCellDataReflectively(param.getValue());
}
Returns the property name provided in the constructor.
Returns: the property name provided in the constructor
/**
* Returns the property name provided in the constructor.
* @return the property name provided in the constructor
*/
public final String getProperty() { return property; }
private ObservableValue<T> getCellDataReflectively(S rowData) {
if (getProperty() == null || getProperty().isEmpty() || rowData == null) return null;
try {
// we attempt to cache the property reference here, as otherwise
// performance suffers when working in large data models. For
// a bit of reference, refer to RT-13937.
if (columnClass == null || previousProperty == null ||
! columnClass.equals(rowData.getClass()) ||
! previousProperty.equals(getProperty())) {
// create a new PropertyReference
this.columnClass = rowData.getClass();
this.previousProperty = getProperty();
this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty());
}
if (propertyRef != null) {
if (propertyRef.hasProperty()) {
return propertyRef.getProperty(rowData);
} else {
T value = propertyRef.get(rowData);
return new ReadOnlyObjectWrapper<T>(value);
}
}
} catch (RuntimeException e) {
// log the warning and move on
final PlatformLogger logger = Logging.getControlsLogger();
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Can not retrieve property '" + getProperty() +
"' in PropertyValueFactory: " + this +
" with provided class type: " + rowData.getClass(), e);
}
propertyRef = null;
}
return null;
}
}