Reputation: 313
I have a situation where a TreeView is being displayed, with two levels of entries (parents and children), like so:
root (invisible)
|_ parent item 1
|_ child item 1-1
|_ child item 1-2
|_ parent item 2
|_ child item 2-1
These items are all standard CheckBoxTreeItem
s. What I want to do, is to have CTRL-clicking on a parent item
's checkbox select a set of it's children, according to some function. For example, here I might want only the first child item (i.e. child item 1-1
and child item 2-1
) in each child list to be selected upon CTRL-clicking the parent checkbox.
Is this possible? As far as I can see, there's no good way to access the checkbox and give it e.g. an onMouseClick event handler, which is the solution that would make sense to me.
The code for the example tree layout given above:
TreeViewTest.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TreeViewTest extends Application {
@Override
public void start(final Stage stage) {
StackPane sceneRoot = new StackPane();
// create the tree model
CheckBoxTreeItem<String> parent1 = new CheckBoxTreeItem<>("parent 1");
CheckBoxTreeItem<String> parent2 = new CheckBoxTreeItem<>("parent 2");
CheckBoxTreeItem<String> child1_1 = new CheckBoxTreeItem<>("child 1-1");
CheckBoxTreeItem<String> child1_2 = new CheckBoxTreeItem<>("child 1-2");
CheckBoxTreeItem<String> child2_1 = new CheckBoxTreeItem<>("child 2-1");
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>("root");
// attach the nodes
parent1.getChildren().addAll(child1_1, child1_2);
parent2.getChildren().addAll(child2_1);
root.getChildren().addAll(parent1, parent2);
// display everything
root.setExpanded(true);
parent1.setExpanded(true);
parent2.setExpanded(true);
// create the treeView
final TreeView<String> treeView = new TreeView<>();
treeView.setShowRoot(false);
treeView.setRoot(root);
// set the cell factory
treeView.setCellFactory(CheckBoxTreeCell.forTreeView());
// display the tree
sceneRoot.getChildren().addAll(treeView);
Scene scene = new Scene(sceneRoot, 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Main.launch(args);
}
}
Upvotes: 1
Views: 418
Reputation: 313
As suggested by @James_D in the comments on the question, one solution is to create a custom TreeCell implementation which exposes the CheckBox.
The modified TreeViewTest.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeViewTest extends Application {
@Override
public void start(final Stage stage) {
StackPane sceneRoot = new StackPane();
// create the tree model
CheckBoxTreeItem<String> parent1 = new CheckBoxTreeItem<>("parent 1");
CheckBoxTreeItem<String> parent2 = new CheckBoxTreeItem<>("parent 2");
CheckBoxTreeItem<String> child1_1 = new CheckBoxTreeItem<>("child 1-1");
CheckBoxTreeItem<String> child1_2 = new CheckBoxTreeItem<>("child 1-2");
CheckBoxTreeItem<String> child2_1 = new CheckBoxTreeItem<>("child 2-1");
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>("root");
// attach the nodes
parent1.getChildren().addAll(child1_1, child1_2);
parent2.getChildren().addAll(child2_1);
root.getChildren().addAll(parent1, parent2);
// display everything
root.setExpanded(true);
parent1.setExpanded(true);
parent2.setExpanded(true);
// create the treeView
final TreeView<String> treeView = new TreeView<>();
treeView.setShowRoot(false);
treeView.setRoot(root);
// set the cell factory UPDATED
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>() {
@Override
public TreeCell<String> call(TreeView<String> param) {
TreeCell<String> cell = new MyTreeCell<>();
((MyTreeCell) cell).getCheckBox().setOnMouseClicked(e -> {
if (!cell.getTreeItem().isLeaf())
if (e.isControlDown() && e.getButton() == MouseButton.PRIMARY)
System.out.println("CTRL-clicked");
});
return cell;
}
});
// display the tree
sceneRoot.getChildren().addAll(treeView);
Scene scene = new Scene(sceneRoot, 200, 200);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
Main.launch(args);
}
}
MyTreeCell.java (slightly tweaked code copied from CheckBoxTreeCell)
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.util.Callback;
import javafx.util.StringConverter;
public class MyTreeCell<T> extends TreeCell<T> {
private final CheckBox checkBox;
private ObservableValue<Boolean> booleanProperty;
private BooleanProperty indeterminateProperty;
public MyTreeCell() {
this.getStyleClass().add("check-box-tree-cell");
this.checkBox = new CheckBox();
this.checkBox.setAllowIndeterminate(false);
// by default the graphic is null until the cell stops being empty
setGraphic(null);
}
// --- checkbox
public final CheckBox getCheckBox() { return checkBox; }
// --- selected state callback property
private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>
selectedStateCallback =
new SimpleObjectProperty<>(
this, "selectedStateCallback");
public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
return selectedStateCallback;
}
public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) {
selectedStateCallbackProperty().set(value);
}
public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() {
return selectedStateCallbackProperty().get();
}
@Override public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
TreeItem<T> treeItem = getTreeItem();
// update the node content
setText((treeItem == null ? "" : treeItem.getValue().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<T> cbti = (CheckBoxTreeItem<T>) treeItem;
booleanProperty = cbti.selectedProperty();
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
indeterminateProperty = cbti.indeterminateProperty();
checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty);
} else {
Callback<TreeItem<T>, ObservableValue<Boolean>> 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);
}
}
}
}
}
Upvotes: 0
Reputation: 209358
You need a custom implementation of TreeCell
. This should give you a starting point that allows you to implement the additional functionality you need:
public class MyCheckBoxCell extends TreeCell<String> {
private final CheckBox checkBox = new CheckBox();
private BooleanProperty currentSelectedBinding ;
// only need this if you are using the indeterminateProperty() of your
// CheckBoxTreeItems
private BooleanProperty currentIndeterminateBinding ;
public MyCheckBoxCell() {
// add extra event handling to the check box here...
}
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item);
setGraphic(checkBox);
if (currentSelectedBinding != null) {
checkBox.selectedProperty().unbindBidirectional(currentSelectedBinding);
}
if (currentIndeterminateBinding != null) {
checkBox.indeterminateProperty().unbindBidirectional(currentIndeterminateBinding);
}
if (getTreeItem() instanceof CheckBoxTreeItem) {
CheckBoxTreeItem cbti = (CheckBoxTreeItem<?>) getTreeItem();
currentSelectedBinding = cbti.selectedProperty();
checkBox.selectedProperty().bindBidirectional(currentSelectedBinding);
currentIndeterminateBinding = cbti.indeterminateProperty();
checkBox.indeterminateProperty().bindBidirectional(currentIndeterminateBinding);
}
}
}
}
Upvotes: 1