Reputation: 118651
I'm trying to make an irregular shaped component for JavaFX, and experience very odd behavior.
package pkg;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
Pane pane = new Pane();
for (int x = 50; x < 150; x += 50) {
for (int y = 50; y < 150; y += 50) {
pane.getChildren().add(new Triangle(x, y));
}
}
scene = new Scene(pane, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private static class Triangle extends Region {
Polygon p;
public Triangle(int x, int y) {
super();
p = new Polygon(x - 25, y + 25, x, y - 25, x + 25, y + 25);
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
p.setFill(Color.RED);
System.out.println("x: " + x + " y: " + y);
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
p.setFill(Color.BLACK);
}
});
setShape(p);
getChildren().add(p);
}
}
}
Dependencies:
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>15.0.1</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>13</version>
</dependency>
</dependencies>
When this is run, it creates 4 Triangles, each with a mouseover behavior.
However, when you mouseover each triangle, they all act as if the lower right triangle is being triggered.
This is not intuitive. I don't know quite what is doing that.
What is happening here, and how do I make these components react independently?
Upvotes: 0
Views: 126
Reputation: 209358
By default, the region's bounds will start at [0,0]
and extend to fill the space required by all children. Thus the last triangle you create actually has bounds covering all the others, and since it is top-most in the stack (because you add it last), it receives all mouse events.
If you push each triangle to the back (which isn't a particularly good solution), you see what you want:
for (int x = 50; x < 150; x += 50) {
for (int y = 50; y < 150; y += 50) {
Triangle t = new Triangle(x, y);
pane.getChildren().add(t);
t.toBack();
}
}
Another solution is to add
setPickOnBounds(false) ;
to the Triangle
constructor. This will only trigger mouse events if the mouse is over a non-transparent part of the Triangle
.
Probably though you should be creating regions which only occupy the space they really need. You likely also want the setPickOnBounds(false)
call, so the triangle only responds to a mouse being over the triangular part (not the full rectangular area containing it):
private static class Triangle extends Region {
Polygon p;
public Triangle() {
super();
p = new Polygon(0, 50, 25, 0, 50, 50);
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
p.setFill(Color.RED);
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
p.setFill(Color.BLACK);
}
});
setShape(p);
getChildren().add(p);
setPickOnBounds(false);
}
}
And then you can lay the triangles out in their containing pane in the usual way:
Pane pane = new Pane();
for (int x = 25; x < 125; x += 50) {
for (int y = 25; y < 125; y += 50) {
Triangle t = new Triangle();
pane.getChildren().add(t);
t.setLayoutX(x);
t.setLayoutY(y);
}
}
They will also work with layout panes:
GridPane pane = new GridPane();
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
Triangle t = new Triangle();
pane.add(t, x, y);
}
}
Upvotes: 2