Issue
My setup
I have added an EventFilter
to an Hbox
node which also contains two VBoxes.
In one of the VBoxes I have a Button
, a Label
, and a TextArea
class instance.
The EventHandler
is:
public static EventHandler<MouseEvent> pageEventFilter = new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
EventTarget target = e.getTarget();
String name = target.getClass().getName();
System.out.println("NAME: " + name);
}
};
In the event filter, I want to get the id string for buttons or labels that the user clicks on.
I cannot determine the button or label clicked
When I click on a part of the button or label, I get the following class names reported:
- click on button text ->
com.sun.javafx.scene.control.LabeledText
- click outside button text ->
javafx.scene.control.Button
orjavafx.scene.control.Label
Trying to cast the event target of LabeledText
to a Button
or a Label
gives me a ClassCastException
.
If I try to create a variable:
LabeledText lbt = target
I get a compilation error that the LabeledText
class does not exist.
Similarly for TextArea
When I click on the TextArea
, I get javafx.scene.control.skin.TextAreaSkin$ContentView
as the name of the target class. I am unable to click anywhere to get TextArea
reported as the event target class. When I click the text in the TextArea, I get javafx.scene.text.Text
, I cannot get the id
of the TextArea
node from either of these.
My Question
How do I find the parent of the target node, for the types of nodes I am interested in (buttons, labels and perhaps some other types), for which I can then get the node ID string using:
String nodeId = targetParentNode.getId();
Solution
In the screenshot, the user has clicked on a descendent node of a TextArea which is of type ContentView.
On intercepting the click event in a filter, the application has walked up the scene graph branch via repeated getParent()
calls until it has found a higher level node of one of the specified types. In this case it has found a TextArea. It has then queried that text area for its "id" value and reported it in the appropriate label.
IdReporterApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class IdReporterApp extends Application {
private static final String CSS =
"""
data:text/css,
.label {
-fx-padding: 3px;
-fx-background-color: lightblue;
}
VBox {
-fx-spacing: 10px;
-fx-padding: 10px;
-fx-background-color: lemonchiffon;
}
HBox {
-fx-padding: 10px;
-fx-background-color: palegreen;
}
""";
@Override
public void start(Stage stage) {
Scene scene = new Scene(new IdController().getUI());
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
IdController.java
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.List;
class IdController {
private final Label clickedTargetTypeLabel = new Label();
private final Label reportedTargetTypeLabel = new Label();
private final Label reportedTargetIdLabel = new Label();
private final EventTargetFinder eventTargetFinder = new EventTargetFinder(
List.of(
Label.class, Button.class, TextArea.class
)
);
public Parent getUI() {
final VBox layout = new VBox(
new VBox(
id("box1", new Label("Box 1")),
id("button1", new Button("Button"))
),
new VBox(
id("box2", new Label("Box 2")),
id("textArea1", new TextArea("Text Area"))
),
new HBox(
id("clickTargetLabel", new Label("Clicked target Type")),
id( "clickTargetValueLabel", clickedTargetTypeLabel)
),
new HBox(
id("reportedTargetLabel", new Label("Reported target Type")),
id( "reportedTargetTypeLabel", reportedTargetTypeLabel)
),
new HBox(
id("reportedTargetIdLabel", new Label("Reported target ID")),
id("reportedTargetIdValueLabel", reportedTargetIdLabel)
)
);
layout.addEventFilter(
MouseEvent.MOUSE_CLICKED,
this::updateTargetLabels
);
return layout;
}
private Node id(String id, Node node) {
node.setId(id);
return node;
}
private void updateTargetLabels(MouseEvent e) {
EventTargetFinder.TargetSearchResult searchResult =
eventTargetFinder.findTargetsForMouseEvent(e);
clickedTargetTypeLabel.setText(
searchResult
.clickedTarget()
.getClass()
.getSimpleName()
);
reportedTargetTypeLabel.setText(
searchResult.reportedTarget() == null
? "null"
: searchResult
.reportedTarget()
.getClass()
.getSimpleName()
);
reportedTargetIdLabel.setText(
searchResult.reportedTarget() == null
? "none"
: searchResult.reportedTarget().getId() == null
? "null"
: searchResult.reportedTarget().getId()
);
}
}
EventTargetFinder.java
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import java.util.Collections;
import java.util.List;
record EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
this.filterTargetTypes = Collections.unmodifiableList(filterTargetTypes);
}
record TargetSearchResult(
Node clickedTarget,
Node reportedTarget
) {}
public TargetSearchResult findTargetsForMouseEvent(MouseEvent event) {
Node target = (Node) event.getTarget();
Node clickedTarget = target;
while (target != null && !isTargetFiltered(target)) {
target = target.getParent();
}
Node reportedTarget = target;
return new TargetSearchResult(clickedTarget, reportedTarget);
}
private boolean isTargetFiltered(Node target) {
return filterTargetTypes.stream()
.anyMatch(
t -> t.isInstance(target)
);
}
}
Answered By - jewelsea