Search code examples
javajavafxjavafx-8mouseevent

How to get Node while mouse dragged by event position in JavaFX 8?


I have created code example:

package stackoverflow;

import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;

public class GetNodeByMousePositionWhileDragged extends Application {

    private Line line;
    private Group group = new Group();

    @Override
    public void start(Stage stage) throws Exception {

        AnchorPane anchorPane = new AnchorPane();

        Circle source = new Circle(30, Color.LIGHTGREEN);
        source.setStroke(Color.BLACK);
        source.setCenterX(100);
        source.setCenterY(100);

        source.setOnMousePressed(me -> {

            me.consume();

            drawLine(source, me);
        });

        source.setOnMouseDragged(me -> translateLineEnd(getPoint(me)));

        source.setOnMouseReleased(event -> group.getChildren().remove(line));

        Circle target = new Circle(30, Color.LIGHTBLUE);
        target.setStroke(Color.BLACK);
        target.setCenterX(400);
        target.setCenterY(100);

        target.setOnMousePressed(me -> {

            me.consume();

            drawLine(target, me);
        });

        target.setOnMouseDragged(me -> translateLineEnd(getPoint(me)));

        target.setOnMouseReleased(event -> group.getChildren().remove(line));

        group.getChildren().addAll(source, target);

        anchorPane.getChildren().addAll(group);

        stage.setScene(new Scene(anchorPane, 600, 400));
        stage.setMaximized(true);
        stage.show();

    }

    private void drawLine(Circle source, MouseEvent me) {

        line = new Line();
        line.setStroke(Color.BLACK);
        line.setStrokeWidth(1);

        line.startXProperty().bind(source.centerXProperty());
        line.startYProperty().bind(source.centerYProperty());

        translateLineEnd(getPoint(me));

        group.getChildren().add(line);
    }

    private void translateLineEnd(Point2D point) {

        line.setEndX(point.getX());
        line.setEndY(point.getY());
    }

    private Point2D getPoint(MouseEvent me) {

        return new Point2D(me.getSceneX(), me.getSceneY());
    }
}

Here I am just adding two circles and I want to connect them with a line by simply dragging from one circle to another. But the problem is that I want to verify whether mouse entered target circle while I am dragging mouse from source circle. When it is entered I want just bind end points of the line to the target circle center points or remove line on mouse released if it is not entered any circle besides source one.

Unfortunately while dragging one circle another one is not catching mouseevents. But it is possible to get mouse position on scene. I tried to solve this problem by simply storing all circle (I have bunch of them, 10K+), iterating each time and checking circle.contains(me.getSceneX(), me.getSceneY()). It seems to me a bit expensive way or like inventing wheel.

There is a question is it possible in JavaFX 8 to get node according scene position in proper way by using built-in JavaFX features?


Solution

  • You need to modify the code a bit:

    • Use MouseDragEvents by calling startFullDrag for the source node in the onDragDetected event.
    • Set mouseTransparent to true for the line to allow JavaFX to deliver the MouseEvents to the target circle instead of the Line.
    • Modify the event handlers to yield different results, if the mouse was released on the target circle.
    private void drawLine(Circle source, MouseEvent me) {
    
        line = new Line();
        line.setMouseTransparent(true);
        ...
    
    private Group group = new Group();
    
    private boolean removeLine = true;
    
    source.setOnMousePressed(me -> {
        me.consume();
    
        drawLine(source, me);
        me.setDragDetect(true); // trigger dragDetected event immediately
    });
    source.setOnDragDetected(evt -> {
        source.startFullDrag();
        removeLine = true;
    });
    
    ...
    
    source.setOnMouseReleased(event -> {
        if (removeLine) {
            group.getChildren().remove(line);
        }
    });
    
    target.setOnMouseDragReleased(me -> removeLine = false);