1
votes

I have a very large StackPane(3000x2000) inside of a ScrollPane. The idea is to use it like a large "drawing board", in which users can create nodes and drag them around, for creating Mindmaps and such. The problem is drag and drop: It works fine if the scrollPane is in its "start position", so that both Hvalue and Vvalue are 0. But once you scrolled a bit, the values that DragEvent.getX() and .getY() return are relative to the visible part of the pane, not to its entire size. That means you cannot drag and drop something properly. I created a test class to illustrate the problem without any obsolete code:

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class DragTestClass extends Application{

    public static void problem(Circle circle, ScrollPane scrollPane, StackPane pane, DataFormat dataFormat){
        circle.setOnDragDetected(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
                    Dragboard db = circle.startDragAndDrop(TransferMode.ANY);
                    ClipboardContent content = new ClipboardContent();
                    content.put(dataFormat,0); // normally, ID of node
                    db.setContent(content);
                    event.consume();
                }
            });

        scrollPane.setOnDragDropped(new EventHandler<DragEvent>() {
                @Override
                public void handle(DragEvent event) {
                    Dragboard db = event.getDragboard();
                    if(db.hasContent(dataFormat) && db.getContent(dataFormat) instanceof Integer){
                        int index = (Integer) db.getContent(dataFormat);
                        Circle node = (Circle) pane.getChildren().get(index);
                        node.setManaged(false);
                        // this is the problematic part
                        node.setTranslateX(event.getX() - node.getCenterX());
                        node.setTranslateY(event.getY() - node.getCenterY());

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

        scrollPane.setOnDragOver(new EventHandler<DragEvent>() {
            public void handle(DragEvent event) {
                event.acceptTransferModes(TransferMode.ANY);
                event.consume();
            }
        });
    }

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

        ScrollPane scrollPane = new ScrollPane();
        StackPane pane = new StackPane();
        DataFormat dataFormat = new DataFormat("DragDropFormat");
        int width = 1000;
        int height = 1000;
        pane.setMinHeight(height);
        pane.setMaxHeight(height);
        pane.setMinWidth(width);
        pane.setMaxWidth(width);
        Circle circle = new Circle(50,50,20); // normally a StackPane
        circle.setManaged(false);
        pane.getChildren().add(circle);
        pane.setAlignment(Pos.TOP_LEFT);
        scrollPane.setContent(pane);

        problem(circle, scrollPane, pane, dataFormat);

        Scene scene = new Scene(scrollPane, 400, 400);
        primaryStage.setTitle("Problem");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

My idea was to add something to the translation of the nodes, like scrollPane.getHvalue() * width, but that doesn't seem to be working. Maybe I just googled the wrong keywords, but I haven't found anything helpful, so I'm sorry if the question was answered elsewhere. Thanks in advance for your help!

1
Instead of editing the question to write solved and placing the answer in the question, you can create a self-answer instead and mark that correct. - jewelsea
Aside: I would advise using a Pane rather than a StackPane to contain the nodes that you drag around. A StackPane manages the layout of items added to it (usually by centering them inside the StackPane). A Pane does not automatically change the layoutX, layoutY values of an object from what you set them to be, which is probably what you want here. See sample of draggable elements in a Pane (MessageBoard object). - jewelsea
Oh, thanks a lot for the advice, I didn't know that! I chose a StackPane because that made it very easy to hide the lines of my mind map behind the circles that I use as nodes, simply by adding them in the beginning of the list of children. But thank you anyways, this is a useful piece of knowledge. - T. Klein
The same "hiding" you mention can be implemented using a Pane and its children list. Pane paints its children in order of first in the list to last, so the last item always paints on top of the first, just the same as StackPane. - jewelsea

1 Answers

0
votes

I figured it out! Setting the EventHandler on the StackPane (the content of the ScrollPane, instead of the ScrollPane itself) did the trick. Apparently, the coordinates that event.getX() returns are relative to the node that has the EventHandler. In this example:

pane.setOnDragDropped...