Alexandru Severin
Alexandru Severin

Reputation: 6248

Apply style to TreeView children nodes in javaFX

I am using JavaFX TreeView and I want to implement a function where if I hover on a tree item, the item and all its children would be highlighted.

So far I managed to use setCellFactory to highlight the targeted item like this:

treeCell.setOnMouseEntered(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent mouseEvent) {
        redrawTree()
        treeCell.setStyle("-fx-background-color: #0093ff;");
    }
});

Result being:

enter image description here

But I don't know how to target and apply style to treeCell's children. Also this solution requires redrawing the tree a lot which is laggy for big trees.

Can anyone help me go forward or give me an alternative? I think a css solution would be better.

Upvotes: 2

Views: 12591

Answers (2)

A solution to achieve this is to add and remove a defined a style class on the fly by setting a listener to the row factory.

So the setRowFactory method is responsible to highlight the children nodes.Basically it attaches a listener to an onHover property to each row.

When the changed event is fired we have to check if the row contains a node which is expanded and have children. If so all children will be highlighted on mouse over, and highlighted off on mouse out.

private static final String HIGHLIGHT_STYLE = "child-highlight";
private TreeTableView<MyRowContentObject> treeTableView = new TreeTableView<>();
private void setRowFactory() {
    treeTableView.setRowFactory(e -> {
        TreeTableRow<MyRowContentObject> hoverRow = new TreeTableRow<MyRowContentObject>();
        hoverRow.hoverProperty().addListener(new ChangeListener<Boolean>() {
             //you can use a lambda expression for the listener if you want
            @Override
            public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldStatus, Boolean newStatus) {
                List<Node> rows = getRowsList();
                //on mouse hover
                if(newStatus){
                    TreeItem<MyRowContentObject> item = hoverRow.getTreeItem();
                    //validates if there is a node on the hovered row, is expanded and has children
                    if(item != null && item.isExpanded() && item.getChildren().size() > 0) {
                        //row index of the last child of the expanded and hovered node
                        int lastChildIndex = treeTableView.getRow(item.getChildren().get(item.getChildren().size() - 1));
                        //highlights the childs
                        for (int i = hoverRow.getIndex() + 1; i < lastChildIndex + 1; i++)
                            rows.get(i).getStyleClass().add(HIGHLIGHT_STYLE);
                    }
                } else {
                    //on mouse out cleans every single row, because its hard to keep track of the highlighted rows
                    for (Node row : rows)
                        row.getStyleClass().remove(HIGHLIGHT_STYLE);
                }
            }
        });
        return hoverRow;
    });
}

//fetches all rows from the treeTableView
private List<Node> getRowsList(){
    return new ArrayList<>(treeTableView.lookupAll("TreeTableRow"));
}

And add this to you styles.css or whatever other name you defined:

.child-highlight {
    -fx-background-color: rgba(255, 11, 44, 0.28);
}

Upvotes: 2

James_D
James_D

Reputation: 209724

In an external css file, do

.tree-cell:hover {
  -fx-background-color: #0093ff ;
}

Also note that (for moderately complex reasons) if you use -fx-background instead of -fx-background-color, the text color will react appropriately to the change in background color.

To set styles for child nodes (i.e. nodes that are added to the TreeCell as part of its graphic property), just do something like

.tree-cell:hover .label {
  /* styles... */
}

which would style all labels inside "hovered-over" tree cells.

Here's a complete example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeTest extends Application {  
    @Override  
    public void start(Stage primaryStage) throws Exception {  
        final StackPane stackPane = new StackPane();  

        TreeItem<Integer> root = createTreeItem(1);

        final TreeView<Integer> tree = new TreeView<>(root);  
        tree.setCellFactory(treeView -> {  
            final Label label = new Label();
            final Label anotherLabel = new Label("Item:");
            label.getStyleClass().add("highlight-on-hover");
            final HBox hbox = new HBox(5, anotherLabel, label);
            TreeCell<Integer> cell =  new TreeCell<Integer>() {  
                @Override  
                protected void updateItem(Integer item, boolean empty) {  
                    super.updateItem(item, empty);  
                    if (empty) {
                        setGraphic(null);
                    } else {
                        setGraphic(hbox);
                    }
                }  
            };  
            cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            cell.itemProperty().addListener((obs, oldItem, newItem) -> 
                label.setText(newItem != null ? String.valueOf(newItem) : ""));
            return cell ;
        });  
        stackPane.getChildren().add(tree);  
        final Scene scene = new Scene(stackPane);  

        scene.getStylesheets().add(getClass().getResource("tree-hover.css").toExternalForm());

        primaryStage.setScene(scene);  
        primaryStage.setTitle(getClass().getSimpleName());  
        primaryStage.show();  
    }  

    private TreeItem<Integer> createTreeItem(int value) {
        TreeItem<Integer> item = new TreeItem<>(value);
        if (value < 10000) {
            for (int i=0; i<10; i++) {
                item.getChildren().add(createTreeItem(10*value+i));
            }
        }
        return item ;
    }

    public static void main(String[] args) {  
        launch(args);  
    }  
}  

with the tree-hover.css file:

.tree-cell:hover {
    -fx-background-color: #0093ff ;
}

.tree-cell:hover .highlight-on-hover {
    -fx-text-fill: red ;
}

Upvotes: 7

Related Questions