javafx.scene.control.CheckBoxTreeItem Maven / Gradle / Ivy
/*
* Copyright (c) 2012, 2022, 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.scene.control.cell.CheckBoxTreeCell;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Node;
/**
* TreeItem subclass that adds support for being in selected, unselected, and
* indeterminate states. This is useful when used in conjunction with a TreeView
* which has a {@link CheckBoxTreeCell} installed.
*
* A CheckBoxTreeItem can be {@link #independentProperty() independent} or
* dependent. By default, CheckBoxTreeItem instances are dependent, which means
* that any changes to the selection state of a TreeItem will have an impact on
* parent and children CheckBoxTreeItem instances. If a CheckBoxTreeItem is
* set to be independent, this means that any changes to that CheckBoxTreeItem
* will not directly impact the state of parent and children CheckBoxTreeItem
* instances.
*
*
The {@link #indeterminateProperty() indeterminate} property is used to
* represent the same concept as that in {@link CheckBox#indeterminateProperty()},
* namely, that the CheckBox is neither selected or unselected. This is commonly
* used inside a TreeView when some, but not all, of a branches children are
* selected.
*
*
A simple example of using the CheckBoxTreeItem class, in conjunction with
* {@link CheckBoxTreeCell} is shown below:
*
*
// create the tree model
* CheckBoxTreeItem<String> jonathanGiles = new CheckBoxTreeItem<>("Jonathan");
* CheckBoxTreeItem<String> juliaGiles = new CheckBoxTreeItem<>("Julia");
* CheckBoxTreeItem<String> mattGiles = new CheckBoxTreeItem<>("Matt");
* CheckBoxTreeItem<String> sueGiles = new CheckBoxTreeItem<>("Sue");
* CheckBoxTreeItem<String> ianGiles = new CheckBoxTreeItem<>("Ian");
*
* CheckBoxTreeItem<String> gilesFamily = new CheckBoxTreeItem<>("Giles Family");
* gilesFamily.setExpanded(true);
* gilesFamily.getChildren().addAll(jonathanGiles, juliaGiles, mattGiles, sueGiles, ianGiles);
*
* // create the treeView
* final TreeView<String> treeView = new TreeView<>();
* treeView.setRoot(gilesFamily);
*
* // set the cell factory
* treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
*
*
*
* @param The type of the value contained within the TreeItem
* @see CheckBoxTreeCell
* @see TreeItem
* @see CheckBox
* @since JavaFX 2.2
*/
// TODO the TreeModificationEvent doesn't actually bubble in the same way as
// TreeItem - it just looks that way as the 'bubbling' is done via changing the
// state of all parent items.
public class CheckBoxTreeItem extends TreeItem {
/**
* An EventType used when the CheckBoxTreeItem selection / indeterminate
* state changes. To use this, it is recommended that you use code along the
* lines of the following:
*
*
* {@code
* child1.addEventHandler(CheckBoxTreeItem.checkBoxSelectionChangedEvent(), new EventHandler>() {
* public void handle(TreeModificationEvent event) {
* ...
* }
* });}
*
*
* @param The type of the value contained within the TreeItem.
* @return the EventType used when the CheckBoxTreeItem selection / indeterminate
* state changes
*/
@SuppressWarnings("unchecked")
public static EventType> checkBoxSelectionChangedEvent() {
return (EventType>) CHECK_BOX_SELECTION_CHANGED_EVENT;
}
private static final EventType extends Event> CHECK_BOX_SELECTION_CHANGED_EVENT
= new EventType<>(TreeModificationEvent.ANY, "checkBoxSelectionChangedEvent");
/* *************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates an empty CheckBoxTreeItem.
*/
public CheckBoxTreeItem() {
this(null);
}
/**
* Creates a CheckBoxTreeItem with the value property set to the provided
* object.
*
* @param value The object to be stored as the value of this TreeItem.
*/
public CheckBoxTreeItem(T value) {
this(value, null, false);
}
/**
* Creates a CheckBoxTreeItem with the value property set to the provided
* object, and the graphic set to the provided Node.
*
* @param value The object to be stored as the value of this CheckBoxTreeItem.
* @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
*/
public CheckBoxTreeItem(T value, Node graphic) {
this(value, graphic, false);
}
/**
* Creates a CheckBoxTreeItem with the value property set to the provided
* object, the graphic set to the provided Node, and the initial state
* of the {@link #selectedProperty()} set to the provided boolean value.
*
* @param value The object to be stored as the value of this CheckBoxTreeItem.
* @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
* @param selected The initial value of the
* {@link #selectedProperty() selected} property.
*/
public CheckBoxTreeItem(T value, Node graphic, boolean selected) {
this(value, graphic, selected, false);
}
/**
* Creates a CheckBoxTreeItem with the value property set to the provided
* object, the graphic set to the provided Node, the initial state
* of the {@link #selectedProperty()} set to the provided boolean value, and
* the initial state of the {@link #independentProperty() independent}
* property to the provided boolean value.
*
* @param value The object to be stored as the value of this CheckBoxTreeItem.
* @param graphic The Node to show in the TreeView next to this CheckBoxTreeItem.
* @param selected The initial value of the
* {@link #selectedProperty() selected} property.
* @param independent The initial value of the
* {@link #independentProperty() independent} property
*/
public CheckBoxTreeItem(T value, Node graphic, boolean selected, boolean independent) {
super(value, graphic);
setSelected(selected);
setIndependent(independent);
selectedProperty().addListener(stateChangeListener);
indeterminateProperty().addListener(stateChangeListener);
}
/* *************************************************************************
* *
* Callbacks *
* *
**************************************************************************/
private final ChangeListener stateChangeListener = (ov, oldVal, newVal) -> {
updateState();
};
/* *************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- Selected
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false) {
@Override protected void invalidated() {
super.invalidated();
fireEvent(CheckBoxTreeItem.this, true);
}
};
/**
* Sets the selected state of this CheckBoxTreeItem.
* @param value the selected state of this CheckBoxTreeItem
*/
public final void setSelected(boolean value) { selectedProperty().setValue(value); }
/**
* Returns the selected state of this CheckBoxTreeItem.
* @return true if CheckBoxTreeItem is selected
*/
public final boolean isSelected() { return selected.getValue(); }
/**
* A {@link BooleanProperty} used to represent the selected state of this CheckBoxTreeItem.
* @return the selected state property of this CheckBoxTreeItem
*/
public final BooleanProperty selectedProperty() { return selected; }
// --- Indeterminate
private final BooleanProperty indeterminate = new SimpleBooleanProperty(this, "indeterminate", false) {
@Override protected void invalidated() {
super.invalidated();
fireEvent(CheckBoxTreeItem.this, false);
}
};
/**
* Sets the indeterminate state of this CheckBoxTreeItem.
* @param value the indeterminate state of this CheckBoxTreeItem
*/
public final void setIndeterminate(boolean value) { indeterminateProperty().setValue(value); }
/**
* Returns the indeterminate state of this CheckBoxTreeItem.
* @return true if CheckBoxTreeItem is indeterminate state
*/
public final boolean isIndeterminate() { return indeterminate.getValue(); }
/**
* A {@link BooleanProperty} used to represent the indeterminate state of this CheckBoxTreeItem.
* @return the indeterminate state property of this CheckBoxTreeItem
*/
public final BooleanProperty indeterminateProperty() { return indeterminate; }
// --- Independent
/**
* A {@link BooleanProperty} used to represent the independent state of this CheckBoxTreeItem.
* The independent state is used to represent whether changes to a single
* CheckBoxTreeItem should influence the state of its parent and children.
*
* By default, the independent property is false, which means that when
* a CheckBoxTreeItem has state changes to the selected or indeterminate
* properties, the state of related CheckBoxTreeItems will possibly be changed.
* If the independent property is set to true, the state of related CheckBoxTreeItems
* will never change.
* @return the independent state property of this CheckBoxTreeItem
*/
public final BooleanProperty independentProperty() { return independent; }
private final BooleanProperty independent = new SimpleBooleanProperty(this, "independent", false);
public final void setIndependent(boolean value) { independentProperty().setValue(value); }
public final boolean isIndependent() { return independent.getValue(); }
/* *************************************************************************
* *
* Private Implementation *
* *
**************************************************************************/
private static boolean updateLock = false;
private void updateState() {
if (isIndependent()) return;
boolean firstLock = ! updateLock;
// toggle parent (recursively up to root)
updateLock = true;
updateUpwards();
if (firstLock) updateLock = false;
// toggle children
if (updateLock) return;
updateDownwards();
}
private void updateUpwards() {
if (! (getParent() instanceof CheckBoxTreeItem)) return;
CheckBoxTreeItem> parent = (CheckBoxTreeItem>) getParent();
int selectCount = 0;
int indeterminateCount = 0;
for (TreeItem> child : parent.getChildren()) {
if (! (child instanceof CheckBoxTreeItem)) continue;
CheckBoxTreeItem> cbti = (CheckBoxTreeItem>) child;
selectCount += cbti.isSelected() && ! cbti.isIndeterminate() ? 1 : 0;
indeterminateCount += cbti.isIndeterminate() ? 1 : 0;
}
if (selectCount == parent.getChildren().size()) {
parent.setSelected(true);
parent.setIndeterminate(false);
} else if (selectCount == 0 && indeterminateCount == 0) {
parent.setSelected(false);
parent.setIndeterminate(false);
} else {
parent.setIndeterminate(true);
}
}
private void updateDownwards() {
// If this node is not a leaf, we also put all
// children into the same state as this branch
if (! isLeaf()) {
for (TreeItem child : getChildren()) {
if (child instanceof CheckBoxTreeItem) {
CheckBoxTreeItem cbti = ((CheckBoxTreeItem) child);
cbti.setSelected(isSelected());
}
}
}
}
private void fireEvent(CheckBoxTreeItem item, boolean selectionChanged) {
Event evt = new CheckBoxTreeItem.TreeModificationEvent<>(CHECK_BOX_SELECTION_CHANGED_EVENT, item, selectionChanged);
Event.fireEvent(this, evt);
}
/**
* A TreeModificationEvent class that works in a similar vein to the
* {@link javafx.scene.control.TreeItem.TreeModificationEvent} class, in that
* this event will bubble up the CheckBoxTreeItem hierarchy, until the parent
* node is null.
*
* @param The type of the value contained within the
* {@link CheckBoxTreeItem#valueProperty() value} property.
* @since JavaFX 2.2
*/
public static class TreeModificationEvent extends Event {
private static final long serialVersionUID = -8445355590698862999L;
private transient final CheckBoxTreeItem treeItem;
private final boolean selectionChanged;
/**
* Common supertype for all tree modification event types.
*/
public static final EventType ANY =
new EventType<> (Event.ANY, "TREE_MODIFICATION");
/**
* Creates a default TreeModificationEvent instance to represent the
* change in selection/indeterminate states for the given CheckBoxTreeItem
* instance.
* @param eventType the eventType
* @param treeItem the treeItem
* @param selectionChanged represents whether the selection has changed
*/
public TreeModificationEvent(EventType extends Event> eventType, CheckBoxTreeItem treeItem, boolean selectionChanged) {
super(eventType);
this.treeItem = treeItem;
this.selectionChanged = selectionChanged;
}
/**
* Returns the CheckBoxTreeItem that this event occurred upon.
* @return The CheckBoxTreeItem that this event occurred upon.
*/
public CheckBoxTreeItem getTreeItem() {
return treeItem;
}
/**
* Indicates the reason for this event is that the selection on the
* CheckBoxTreeItem changed (as opposed to it becoming indeterminate).
* @return has the CheckBoxTreeItem's selection changed
*/
public boolean wasSelectionChanged() {
return selectionChanged;
}
/**
* Indicates the reason for this event is that the indeterminate
* state on the CheckBoxTreeItem changed (as opposed to it becoming
* selected or unselected).
* @return has the CheckBoxTreeItem's indeterminate changed
*/
public boolean wasIndeterminateChanged() {
return ! selectionChanged;
}
}
}