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: ...; }.
MyTreeCellSkin.java
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.scene.control.skin.TreeCellSkin;
public class MyTreeCellSkin<T> extends TreeCellSkin<T> {
public MyTreeCellSkin(TreeCell<T> control) {
super(control);
}
@Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
TreeView<T> tree = getSkinnable().getTreeView();
int level = tree.getTreeItemLevel(getSkinnable().getTreeItem());
if (!tree.isShowRoot()) {
level--;
}
double leftMargin = getIndent() * level;
x += leftMargin;
double disclosureWidth = 18;
final int padding = 3;
// x += disclosureWidth + padding;
x += 3;
w -= (leftMargin + disclosureWidth + padding);
layoutLabelInArea(x, y, w, h);
}
}
CustomTreeCell.java
import javafx.beans.InvalidationListener;
import javafx.css.PseudoClass;
import javafx.scene.control.TreeCell;
public class CustomTreeCell<T> extends TreeCell<T> {
private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root");
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());
graphicProperty().bind(getTreeItem().graphicProperty());
}
}
}
main.java
treeView.setCellFactory(param -> {
CustomTreeCell<String> treeCell = new CustomTreeCell<>();
treeCell.setSkin(new MyTreeCellSkin<>(treeCell));
return treeCell;
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With