Search code examples
javajavafxscrollpane

JavaFX scroll started and ended


I have a very costly action to do on a mouse scroll on a pane. I currently use

pane.setOnScroll({myMethod()}).

The problem is that if you scroll a lot it computes everything many times. So what I want is to do my actions only when the scroll is finished. I hoped to use setOnScrollStarted, save the starting value and setOnScrollFinished to do my actions.

But I don't know why these two methods are never called. As a test I used

pane.setOnScroll({System.out.println("proof of action"});

and it was clearly never called.

Any idea on how to call my method only at the end of the scroll?

Thanks in advance, A


Solution

  • From the javadoc of ScrollEvent (emphasis mine):

    When the scrolling is produced by a touch gesture (such as dragging a finger over a touch screen), it is surrounded by the SCROLL_STARTED and SCROLL_FINISHED events. Changing number of involved touch points during the scrolling is considered a new gesture, so the pair of SCROLL_FINISHED and SCROLL_STARTED notifications is delivered each time the touchCount changes. When the scrolling is caused by a mouse wheel rotation, only a one-time SCROLL event is delivered, without the started/finished surroundings.

    A possible workaround:

    Increment a counter variable every time a scroll is detected. In the listener start a new thread that waits 1 second and performs the action that you want only if the counter equals to 1 (the last scrolling) then decrements the counter.

    I created a Gist, but I copy here the code:

    public class ScrollablePane extends Pane {
        private Integer scrollCounter = 0;
    
        private final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEnded = new SimpleObjectProperty<>();
    
        public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollEndedProperty() {
            return onScrollEnded;
        }
    
        public ScrollablePane() {
            this.setOnScroll(e -> {
                scrollCounter++;
    
                Thread th = new Thread(() -> {
                    try {
                        Thread.sleep(1000);
                        if (scrollCounter == 1)
                            onScrollEnded.get().handle(e);
    
                        scrollCounter--;
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                });
                th.setDaemon(true);
                th.start();
            });
        }
    
        public void setOnScrollEnded(EventHandler<? super ScrollEvent> handler) {
            onScrollEnded.setValue(handler);
        }
    }
    

    To use it:

    public class MyApplication extends Application {
    
    
        @Override
        public void start(Stage primaryStage) {
            try {
                BorderPane root = new BorderPane();
                Scene scene = new Scene(root, 400, 400);
    
                ScrollablePane pane = new ScrollablePane();
                pane.setOnScrollEnded(e -> System.out.println("Scroll just has been ended"));
    
                root.setCenter(pane);
                primaryStage.setScene(scene);
                primaryStage.show();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }