Reputation: 6248
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:
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
Reputation: 75
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
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