1

I would like to have a ScrollPane scroll up or down when a user drags something to its edge. The ScrollPane would have a VBox inside it and would be inside a VBox too.

I assume I need to put something in setOnDragExited. But what exactly?

Here a minimal program for an example:

package application;


import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;


import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) { 
        BorderPane root = new BorderPane();

        VBox outerBox = new VBox();
        outerBox.setMaxSize(700, 300);

        root.setCenter(outerBox);

        Label outerLabel = new Label("I am outside!");

        ScrollPane sp = new ScrollPane();
        outerBox.getChildren().addAll(outerLabel,sp);
        VBox innerBox = new VBox();
        //setting size bigger than ScrollPane's view.
        innerBox.setPrefSize(600, 600);

        sp.setContent(innerBox);

        Label dragMe = new Label("Drag me to  the edge of scroll pane! \n"+"or drop me in the scrollpane!");
        root.setTop(dragMe);

        dragMe.setOnDragDetected((MouseEvent event) ->{  
            Dragboard db = dragMe.startDragAndDrop(TransferMode.ANY); 
            db.setDragView(((Node) event.getSource()).snapshot(null, null));  

            ClipboardContent content = new ClipboardContent();           
            content.putString((dragMe.getText()));

            db.setContent(content);
            event.consume();      
        });

        sp.setOnDragOver((DragEvent event) ->{
        event.acceptTransferModes(TransferMode.MOVE);
            event.consume();          
        });

        sp.setOnDragEntered((DragEvent event) -> {     
        });   

        sp.setOnDragExited((DragEvent event) -> {
            System.out.println("-----Make the scrollpane scroll up or down depending on exiting on bottem or top------");
            event.consume();                      
        });

        sp.setOnDragDropped((DragEvent event) ->{
            Dragboard db = event.getDragboard();
            System.out.println(((VBox) sp.getContent()).getChildren().add(new Label(db.getString())));      
        });

        Scene scene = new Scene(root,1000,1000);
        primaryStage.setScene(scene);
        primaryStage.show();

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

2 Answers2

4

Found this answer here: Want to trigger scroll when dragging node outside the visible area in ScrollPane

It was not answered completely and did not use a ScrollPane so I thought I post my work/findings as an answer.

I found out you can do this by creating an animation:

private Timeline scrolltimeline = new Timeline();
....
scrolltimeline.setCycleCount(Timeline.INDEFINITE);

scrolltimeline.getKeyFrames()
     .add(new KeyFrame(Duration.millis(20), (ActionEvent) -> { dragScroll();}));

Minimal:

package application;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {
    private ScrollPane sp;
    private Timeline scrolltimeline = new Timeline();
    private double scrollVelocity = 0;

    boolean dropped;

    //Higher speed value = slower scroll.
    int speed = 200;

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();

        sp = new ScrollPane();
        sp.setPrefSize(300, 300);

        VBox outer = new VBox(sp);

        VBox innerBox = new VBox();
        innerBox.setPrefSize(200,1000);

        sp.setContent(innerBox);

        root.setCenter(outer);

        Label dragMe = new Label("drag me to edge!\n"+"or drop me in scrollpane!");
        root.setTop(dragMe);

        setupScrolling();

        dragMe.setOnDragDetected((MouseEvent event) ->{  
            Dragboard db = dragMe.startDragAndDrop(TransferMode.ANY); 
            db.setDragView(((Node) event.getSource()).snapshot(null, null));  

            ClipboardContent content = new ClipboardContent();           
            content.putString((dragMe.getText()));

            db.setContent(content);
            event.consume();      
        });


        Scene scene = new Scene(root, 640, 480);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void setupScrolling() {
        scrolltimeline.setCycleCount(Timeline.INDEFINITE);
        scrolltimeline.getKeyFrames().add(new KeyFrame(Duration.millis(20), (ActionEvent) -> { dragScroll();}));

        sp.setOnDragExited((DragEvent event) -> {

            if (event.getY() > 0) {
                scrollVelocity = 1.0 / speed;
            }
            else {
                scrollVelocity = -1.0 / speed;
            }
            if (!dropped){
                scrolltimeline.play();
            }

        });

        sp.setOnDragEntered(event -> {
            scrolltimeline.stop();
            dropped = false;
        });
        sp.setOnDragDone(event -> {
            System.out.print("test");
            scrolltimeline.stop();
        });           
        sp.setOnDragDropped((DragEvent event) ->{      
            Dragboard db = event.getDragboard();
            ((VBox) sp.getContent()).getChildren().add(new Label(db.getString())); 
            scrolltimeline.stop();
            event.setDropCompleted(true);
            dropped = true;


        });

        sp.setOnDragOver((DragEvent event) ->{
            event.acceptTransferModes(TransferMode.MOVE);                            
            });


        sp.setOnScroll((ScrollEvent event)-> {
            scrolltimeline.stop();   
        });

        sp.setOnMouseClicked((MouseEvent)->{
            System.out.println(scrolltimeline.getStatus());

        });

    }
    private void dragScroll() {
        ScrollBar sb = getVerticalScrollbar();
        if (sb != null) {
            double newValue = sb.getValue() + scrollVelocity;
            newValue = Math.min(newValue, 1.0);
            newValue = Math.max(newValue, 0.0);
            sb.setValue(newValue);
        }
    }

    private ScrollBar getVerticalScrollbar() {
        ScrollBar result = null;
        for (Node n : sp.lookupAll(".scroll-bar")) {
            if (n instanceof ScrollBar) {
                ScrollBar bar = (ScrollBar) n;
                if (bar.getOrientation().equals(Orientation.VERTICAL)) {
                    result = bar;
                }
            }
        }        
        return result;
    }

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

}
Community
  • 1
  • 1
1

JavaFX-8 has not public API to scroll a ScrollPane to a certain position (https://bugs.openjdk.java.net/browse/JDK-8102126) and cast your vote to get such API in.

A hack to scroll to a certain position in Java8 (who will break in Java9!) is to get the Skin of the ScrollPane who is of type ScrollPaneSkin and call the onTraverse-method there.

tomsontom
  • 5,856
  • 2
  • 23
  • 21
  • not sure if i found another way but, I don't think I call onTraverse –  Jan 20 '17 at 15:31