Search code examples
javajavafxdrag-and-dropwindowmouseevent

JavaFX detecting mouse events


I'm trying to drag and drop a node from one window to another. The code creates two windows and adds a dragable lable to one of them. The goal is too move the node from one window to the other and back again. This is what I have currently.

public class Main extends Application {

    private static Node transferNode;

    @Override
    public void start(Stage primaryStage) {
        initFirstWindow();
        initSecondWindow();
    }

    public static void initFirstWindow() {
        BorderPane borderPane = new BorderPane();
        Label label = new Label("clickable");
        label.setOnDragDetected(event -> {
            label.startFullDrag();
            System.out.println("Full drag");
            event.consume();
        });
        borderPane.setCenter(label);

        Stage stage = new Stage();
        stage.setTitle("Window number 1");
        stage.setScene(new Scene(borderPane, 300, 275));
        makeSceneDragAware(stage.getScene());
        stage.show();
    }

    public static void initSecondWindow() {
        Stage stage = new Stage();
        stage.setTitle("Window number 2");
        stage.setScene(new Scene(new BorderPane(), 300, 275));
        makeSceneDragAware(stage.getScene());
        stage.show();
    }

    public static void makeSceneDragAware(Scene scene) {
        scene.setOnMouseDragReleased(event -> {
            System.out.println("Released drag");
            if (transferNode != null) {
                ((BorderPane)scene.getRoot()).setCenter(transferNode);
                transferNode = null;

                event.consume();
            }
        });
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The problem with this, is that setOnMouseDragReleased doesn't get triggered when releasing over the target window. It only gets triggered in the same window as the dragging node.

UPDATE: Making an update to hopefully get a bit more traction. I wanna point out some code that uses JavaFX's D&D feature.

public class Main extends Application {
    private final DataFormat NODE_TYPE = new DataFormat("transferable-node");
    private Node transferNode;

    @Override
    public void start(Stage primaryStage) {
        initFirstWindow();
        initSecondWindow();
    }

    public void initFirstWindow() {
        BorderPane borderPane = new BorderPane();
        Label label = new Label("clickable");
        label.setOnDragDetected(event -> {
            System.out.println("Full drag");
            Dragboard db = label.startDragAndDrop(TransferMode.MOVE);
            ClipboardContent content = new ClipboardContent();

            transferNode = label;
            content.put(NODE_TYPE, "");

            db.setContent(content);
            event.consume();
        });
        borderPane.setCenter(label);

        Stage stage = new Stage();
        stage.setTitle("Window number 1");
        stage.setScene(new Scene(borderPane, 300, 275));
        makeSceneDragAware(stage.getScene());
        stage.show();
    }

    public void initSecondWindow() {
        Stage stage = new Stage();
        stage.setTitle("Window number 2");
        stage.setScene(new Scene(new BorderPane(), 300, 275));
        makeSceneDragAware(stage.getScene());
        stage.show();
    }

    public void makeSceneDragAware(Scene scene) {
        scene.setOnDragOver(event -> {
            event.acceptTransferModes(TransferMode.ANY);
            event.consume();
        });
        scene.setOnDragDropped(event -> {
            if (transferNode != null) {
                ((BorderPane)scene.getRoot()).setCenter(transferNode);
                transferNode = null;

                event.setDropCompleted(true);
                event.consume();
            }
        });
    }

    public static void main(String[] args) {
        launch(args);
    }
}

This code will technically work and satisfies the goal of moving a node between two windows, however it won't work for me as I don't want any dragged element to have the dragging interaction with external programs. Using D&D will also stop firing the setOnMouseDragged event which I need for other parts of my application.


Solution

  • So I've managed to achive the goal with the following code.

    public class Main extends Application {
        private Node transferNode;
        private final ArrayList<Stage> stageList = new ArrayList<>();
    
        @Override
        public void start(Stage primaryStage) {
            initFirstWindow();
            initSecondWindow();
        }
    
        public void initFirstWindow() {
            BorderPane borderPane = new BorderPane();
            Label label = new Label("clickable");
            label.setOnDragDetected(event -> {
                label.startFullDrag();
                transferNode = label;
            });
    
            label.setOnMouseReleased(event -> {
                Point point = MouseInfo.getPointerInfo().getLocation().getLocation();
                Stage stage = windowUnderPoint(point);
                if (stage != null) {
                    MouseDragEvent mouseEvent = new MouseDragEvent(
                            MouseDragEvent.MOUSE_DRAG_RELEASED, 0, 0, point.getX(), point.getY(),
                            MouseButton.PRIMARY, 1,
                            false, false, false, false, false, false, false,
                            false, false, new PickResult(stage, point.getX(), point.getY()), label);
                    stage.fireEvent(mouseEvent);
                }
            });
    
            borderPane.setCenter(label);
    
            Stage stage = new Stage();
            stage.setTitle("Window number 1");
            stage.setScene(new Scene(borderPane, 300, 275));
            stage.setX(400);
            makeStageDragAware(stage);
            stage.show();
            stageList.add(stage);
        }
    
        public void initSecondWindow() {
            Stage stage = new Stage();
            stage.setTitle("Window number 2");
            stage.setScene(new Scene(new BorderPane(), 300, 275));
            stage.setX(800);
            makeStageDragAware(stage);
            stage.show();
            stageList.add(stage);
        }
    
        public void makeStageDragAware(Stage stage) {
            stage.addEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, event -> {
                if (transferNode != null && transferNode.getScene().getWindow() != stage) {
                    ((BorderPane) stage.getScene().getRoot()).setCenter(transferNode);
                    transferNode = null;
                    event.consume();
                }
            });
        }
    
        public Stage windowUnderPoint(Point point) {
            for (Stage stage : stageList) {
                if (inRange(stage.getX(), stage.getX() + stage.getWidth(), point.getX()) &&
                        inRange(stage.getY(), stage.getY() + stage.getHeight(), point.getY())) {
                    return stage;
                }
            }
            return null;
        }
    
        public boolean inRange(double low, double high, double number) {
            return (number >= low && number <= high);
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Basically when the dragging node is released it gets the current mouse coordinates relative to the display and checks if any known/registered stages exists under it. If if finds a valid stage, it'll trigger the stages MOUSE_DRAG_RELEASED event.