Hope everyone is doing well.
My question is pretty basic: How do I make the arrow at the root of a TreeView hidden, if it's possible? A good example of what I want to achieve can be seen in Windows Event Viewer.
While my javafx application looks like the image below
By default, that arrow next to vehicles is present.
I read the documentation here, but I only found .showRoot(), which doesn't achieve what I want. I would really appreciate any input, even if it's a cheat way to do it (using the x-offset property crossed my mind).
The arrow is part of the disclosureNode
of the TreeCell
. When not specified it is the responsibility of the TreeCell
's skin to provide a default disclosure node (e.g. the triangle). This is stated by the property-setter documentation:
The node to use as the "disclosure" triangle, or toggle, used for expanding and collapsing items. This is only used in the case of an item in the tree which contains child items. If not specified, the TreeCell's Skin implementation is responsible for providing a default disclosure node.
Yet looking at the TreeCellSkin
it appears it does not provide the default disclosure node. Instead, this seems to be handled by the TreeViewSkin
(in both JavaFX 8 and JavaFX 11). Looking at the implementation the default disclosure node is a StackPane
with a child StackPane
that functions as the actual arrow. Their style class is tree-disclosure-node
and arrow
, respectively. Note that this doesn't appear documented anywhere, including the JavaFX CSS Reference Guide.
The easiest and least bug-prone method to hiding the root disclosure node is, in my opinion, to use CSS. The only problem here is that TreeCell
provides no way to target only the root cell. But this can still be accomplished by subclassing TreeCell
and providing our own PseudoClass
. Then we set the cellFactory
on our TreeView
.
To set the graphic of the root set the TreeItem.graphic
property of the root TreeItem
.
CustomTreeCell
import javafx.beans.InvalidationListener;
import javafx.css.PseudoClass;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.util.Callback;
public class CustomTreeCell<T> extends TreeCell<T> {
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
public static <T> Callback<TreeView<T>, TreeCell<T>> forTreeView() {
return treeView -> new CustomTreeCell<>();
}
public CustomTreeCell() {
getStyleClass().add("custom-tree-cell");
InvalidationListener listener = observable -> {
boolean isRoot = getTreeView() != null && getTreeItem() == getTreeView().getRoot();
pseudoClassStateChanged(ROOT, isRoot);
};
treeViewProperty().addListener(listener);
treeItemProperty().addListener(listener);
}
@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
graphicProperty().unbind();
setGraphic(null);
} else {
setText(item.toString()); // Really only works if item is a String. Change as needed.
graphicProperty().bind(getTreeItem().graphicProperty());
}
}
}
CSS File
.custom-tree-cell:root .tree-disclosure-node,
.custom-tree-cell:root .arrow {
-fx-min-width: 0;
-fx-pref-width: 0;
-fx-max-width: 0;
-fx-min-height: 0;
-fx-pref-height: 0;
-fx-max-height: 0;
}
/* Related to question asked in the comments by OP */
.custom-tree-cell > .tree-disclosure-node > .arrow {
-fx-shape: "M 0 0 L 10 5 L 0 10 L 0 8 L 8 5 L 0 2 Z";
}
Some notes:
TreeCell.graphic
property to the TreeItem.graphic
property in my cell implementation you won't be able to set the graphic from CSS. You can modify this to simply set the graphic, rather than bind, in order to enable that functionality. Then you won't have to set the graphic of the root TreeItem
through code but could do .custom-tree-cell:root { -fx-graphic: ...; }
.