Will Hartung
Will Hartung

Reputation: 118651

Creating irregular shaped JavaFX component

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

Answers (1)

James_D
James_D

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

Related Questions