Reputation: 3549
I have a JavaFX Scene
with a couple of different Group
s. Each group has several filled PolyLine
s.
I would like to trigger some computation when the user presses the n key while they are inside one of the filled PolyLine
s.
I used scene.setOnKeyPressed
to install a KeyEvent
handler and printed it out. I'm not sure how to figure out which PolyLine
the event occurred over. The target
of the event happens to be a Button
left over from an early tutorial I was following. The event printout does not display any coordinates, and even if it had some, I'm not sure how best to traverse the Node
tree to hunt down which PolyLine
is of interest.
What is the proper idiom for responding to key events that happen while the mouse is over a JavaFX Shape
?
Upvotes: 1
Views: 337
Reputation: 45776
Key events are only delivered to the Node
that has the focus. You can request a Node
obtain focus by calling Node#requestFocus()
. Using that, one solution is you can add mouse event handlers to each Shape
that request focus when the mouse enters the area. Then you add key event handlers to each Shape
rather than the Scene
. The key event will be delivered to, and handled by, the Node
with the focus, which will be the Shape
which has the mouse over it. To stop key events from being delivered to the Shape
once the mouse exits the area you can add a handler that requests focus somewhere else (e.g. a common parent or the root of the scene).
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public final class App extends Application {
@Override
public void start(Stage primaryStage) {
var root = new HBox(15, createCircle(), createTriangle(), createSquare());
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(30));
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Shapes");
primaryStage.show();
}
private Shape createCircle() {
var circle = new Circle(100);
addHandlers(circle, "Circle");
return circle;
}
private Shape createTriangle() {
var polygon = new Polygon(100, 0, 200, 200, 0, 200, 100, 0);
addHandlers(polygon, "Triangle");
return polygon;
}
private Shape createSquare() {
var rectangle = new Rectangle(200, 200);
addHandlers(rectangle, "Square");
return rectangle;
}
private void addHandlers(Shape shape, String name) {
// Notice you have access to the specific shape in the
// event handlers. The source of the event will also
// be the shape.
shape.setOnMouseEntered(event -> {
if (!shape.isFocused()) {
event.consume();
shape.requestFocus();
}
});
shape.setOnMouseExited(event -> {
if (shape.isFocused()) {
event.consume();
shape.getScene().getRoot().requestFocus();
}
});
shape.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.N) {
event.consume();
System.out.println("KEY_PRESSED['N']: " + name);
}
});
}
}
Another option to adding MOUSE_ENTERED
and MOUSE_EXITED
event handlers is to listen to the Node#hover
property of the Shape
. When it changes to true
, request the focus; when it changes to false
, remove the focus.
Requesting the Shape
have the focus will pull the focus away from the Node
that currently has the focus. For instance, if you have a TextField
with the focus then it will lose the focus whenever the mouse hovers over one of the Shape
s. This will prevent the user from being able to type into the TextField
(until they go and click on it).
If this behavior is undesirable then you can, instead of requesting focus, maintain some field/property/model that tells you which Shape
is currently hovered over. Then you'd add an event filter to a common parent of the TextField
and Shape
s. If a Shape
has the mouse hovering over it you do what is necessary and then consume the event (which will stop it from reaching whichever Node
has the focus).
Upvotes: 4