defaultTreeItemStringConverter());
}
/**
* Creates a cell factory for use in a TreeView control. Unlike
* {@link #forTreeView()}, this method does not assume that all TreeItem
* instances in the TreeView are {@link CheckBoxTreeItem} instances.
*
* When used in a TreeView, the CheckBoxCell is rendered with a CheckBox
* to the right of the 'disclosure node' (i.e. the arrow). The item stored
* in {@link CheckBoxTreeItem#getValue()} will then have the StringConverter
* called on it, and this text will take all remaining horizontal space.
*
*
Unlike {@link #forTreeView()}, this cell factory does not handle
* updating the state of parent or children TreeItems - it simply toggles
* the {@code ObservableValue} that is provided, and no more. Of
* course, this functionality can then be implemented externally by adding
* observers to the {@code ObservableValue}, and toggling the state
* of other properties as necessary.
*
* @param The type of the elements contained within the {@link TreeItem}
* instances.
* @param getSelectedProperty A {@link Callback} that, given an object of
* type {@literal TreeItem}, will return an {@code ObservableValue}
* that represents whether the given item is selected or not. This
* {@code ObservableValue} will be bound bidirectionally
* (meaning that the CheckBox in the cell will set/unset this property
* based on user interactions, and the CheckBox will reflect the state
* of the {@code ObservableValue}, if it changes externally).
* @return A {@link Callback} that will return a TreeCell that is able to
* work on the type of element contained within the TreeView root, and
* all of its children (recursively).
*/
public static Callback, TreeCell> forTreeView(
final Callback,
ObservableValue> getSelectedProperty) {
return forTreeView(getSelectedProperty, CellUtils.defaultTreeItemStringConverter());
}
/**
* Creates a cell factory for use in a TreeView control. Unlike
* {@link #forTreeView()}, this method does not assume that all TreeItem
* instances in the TreeView are {@link CheckBoxTreeItem}.
*
* When used in a TreeView, the CheckBoxCell is rendered with a CheckBox
* to the right of the 'disclosure node' (i.e. the arrow). The item stored
* in {@link TreeItem#getValue()} will then have the the StringConverter
* called on it, and this text will take all remaining horizontal space.
*
*
Unlike {@link #forTreeView()}, this cell factory does not handle
* updating the state of parent or children TreeItems - it simply toggles
* the {@code ObservableValue} that is provided, and no more. Of
* course, this functionality can then be implemented externally by adding
* observers to the {@code ObservableValue}, and toggling the state
* of other properties as necessary.
*
* @param The type of the elements contained within the {@link TreeItem}
* instances.
* @param getSelectedProperty A Callback that, given an object of
* type {@literal TreeItem}, will return an {@code ObservableValue}
* that represents whether the given item is selected or not. This
* {@code ObservableValue} will be bound bidirectionally
* (meaning that the CheckBox in the cell will set/unset this property
* based on user interactions, and the CheckBox will reflect the state of
* the {@code ObservableValue}, if it changes externally).
* @param converter A StringConverter that, give an object of type
* {@literal TreeItem}, will return a String that can be used to represent the
* object visually. The default implementation in {@link #forTreeView(Callback)}
* is to simply call .toString() on all non-null items (and to just
* return an empty string in cases where the given item is null).
* @return A {@link Callback} that will return a TreeCell that is able to
* work on the type of element contained within the TreeView root, and
* all of its children (recursively).
*/
public static Callback, TreeCell> forTreeView(
final Callback, ObservableValue> getSelectedProperty,
final StringConverter> converter) {
return tree -> new CheckBoxTreeCell(getSelectedProperty, converter);
}
/***************************************************************************
* *
* Fields *
* *
**************************************************************************/
private final CheckBox checkBox;
private ObservableValue booleanProperty;
private BooleanProperty indeterminateProperty;
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a default {@link CheckBoxTreeCell} that assumes the TreeView is
* constructed with {@link CheckBoxTreeItem} instances, rather than the
* default {@link TreeItem}.
* By using {@link CheckBoxTreeItem}, it will internally manage the selected
* and indeterminate state of each item in the tree.
*/
public CheckBoxTreeCell() {
// getSelectedProperty as anonymous inner class to deal with situation
// where the user is using CheckBoxTreeItem instances in their tree
this(item -> {
if (item instanceof CheckBoxTreeItem>) {
return ((CheckBoxTreeItem>)item).selectedProperty();
}
return null;
});
}
/**
* Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a
* cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this
* method does not assume that all TreeItem instances in the TreeView are
* {@link CheckBoxTreeItem}.
*
* To call this method, it is necessary to provide a
* {@link Callback} that, given an object of type {@literal TreeItem}, will return
* an {@code ObservableValue} that represents whether the given
* item is selected or not. This {@code ObservableValue} will be
* bound bidirectionally (meaning that the CheckBox in the cell will
* set/unset this property based on user interactions, and the CheckBox will
* reflect the state of the {@code ObservableValue}, if it changes
* externally).
*
* If the items are not {@link CheckBoxTreeItem} instances, it becomes
* the developers responsibility to handle updating the state of parent and
* children TreeItems. This means that, given a TreeItem, this class will
* simply toggles the {@code ObservableValue} that is provided, and
* no more. Of course, this functionality can then be implemented externally
* by adding observers to the {@code ObservableValue}, and toggling
* the state of other properties as necessary.
*
* @param getSelectedProperty A {@link Callback} that will return an
* {@code ObservableValue} that represents whether the given
* item is selected or not.
*/
public CheckBoxTreeCell(
final Callback, ObservableValue> getSelectedProperty) {
this(getSelectedProperty, CellUtils.defaultTreeItemStringConverter(), null);
}
/**
* Creates a {@link CheckBoxTreeCell} for use in a TreeView control via a
* cell factory. Unlike {@link CheckBoxTreeCell#CheckBoxTreeCell()}, this
* method does not assume that all TreeItem instances in the TreeView are
* {@link CheckBoxTreeItem}.
*
* To call this method, it is necessary to provide a {@link Callback}
* that, given an object of type {@literal TreeItem}, will return an
* {@code ObservableValue} that represents whether the given item
* is selected or not. This {@code ObservableValue} will be bound
* bidirectionally (meaning that the CheckBox in the cell will set/unset
* this property based on user interactions, and the CheckBox will reflect
* the state of the {@code ObservableValue}, if it changes
* externally).
*
* If the items are not {@link CheckBoxTreeItem} instances, it becomes
* the developers responsibility to handle updating the state of parent and
* children TreeItems. This means that, given a TreeItem, this class will
* simply toggles the {@code ObservableValue} that is provided, and
* no more. Of course, this functionality can then be implemented externally
* by adding observers to the {@code ObservableValue}, and toggling
* the state of other properties as necessary.
*
* @param getSelectedProperty A {@link Callback} that will return an
* {@code ObservableValue} that represents whether the given
* item is selected or not.
* @param converter {@literal A StringConverter that, give an object of type
* TreeItem, will return a String that can be used to represent the
* object visually.}
*/
public CheckBoxTreeCell(
final Callback, ObservableValue> getSelectedProperty,
final StringConverter> converter) {
this(getSelectedProperty, converter, null);
}
private CheckBoxTreeCell(
final Callback, ObservableValue> getSelectedProperty,
final StringConverter> converter,
final Callback, ObservableValue> getIndeterminateProperty) {
this.getStyleClass().add("check-box-tree-cell");
setSelectedStateCallback(getSelectedProperty);
setConverter(converter);
this.checkBox = new CheckBox();
this.checkBox.setAllowIndeterminate(false);
// by default the graphic is null until the cell stops being empty
setGraphic(null);
}
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- converter
private ObjectProperty>> converter =
new SimpleObjectProperty>>(this, "converter");
/**
* The {@link StringConverter} property.
* @return the {@link StringConverter} property
*/
public final ObjectProperty>> converterProperty() {
return converter;
}
/**
* Sets the {@link StringConverter} to be used in this cell.
* @param value the {@link StringConverter} to be used in this cell
*/
public final void setConverter(StringConverter> value) {
converterProperty().set(value);
}
/**
* Returns the {@link StringConverter} used in this cell.
* @return the {@link StringConverter} used in this cell
*/
public final StringConverter> getConverter() {
return converterProperty().get();
}
// --- selected state callback property
private ObjectProperty, ObservableValue>>
selectedStateCallback =
new SimpleObjectProperty, ObservableValue>>(
this, "selectedStateCallback");
/**
* Property representing the {@link Callback} that is bound to by the
* CheckBox shown on screen.
* @return the property representing the {@link Callback} that is bound to
* by the CheckBox shown on screen
*/
public final ObjectProperty, ObservableValue>> selectedStateCallbackProperty() {
return selectedStateCallback;
}
/**
* Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
* @param value the {@link Callback} that is bound to by the CheckBox shown on screen
*/
public final void setSelectedStateCallback(Callback, ObservableValue> value) {
selectedStateCallbackProperty().set(value);
}
/**
* Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
* @return the {@link Callback} that is bound to by the CheckBox shown on screen
*/
public final Callback, ObservableValue> getSelectedStateCallback() {
return selectedStateCallbackProperty().get();
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
StringConverter> c = getConverter();
TreeItem treeItem = getTreeItem();
// update the node content
setText(c != null ? c.toString(treeItem) : (treeItem == null ? "" : treeItem.toString()));
checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic());
setGraphic(checkBox);
// uninstall bindings
if (booleanProperty != null) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
if (indeterminateProperty != null) {
checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty);
}
// install new bindings.
// We special case things when the TreeItem is a CheckBoxTreeItem
if (treeItem instanceof CheckBoxTreeItem) {
CheckBoxTreeItem cbti = (CheckBoxTreeItem) treeItem;
booleanProperty = cbti.selectedProperty();
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
indeterminateProperty = cbti.indeterminateProperty();
checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty);
} else {
Callback, ObservableValue> callback = getSelectedStateCallback();
if (callback == null) {
throw new NullPointerException(
"The CheckBoxTreeCell selectedStateCallbackProperty can not be null");
}
booleanProperty = callback.call(treeItem);
if (booleanProperty != null) {
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}
}
}
}
@Override void updateDisplay(T item, boolean empty) {
// no-op
// This was done to resolve RT-33603, but will impact the ability for
// TreeItem.graphic to change dynamically.
}
}