'JavaFX: How to pan a ScrollPane that contains Button children?

In JavaFX, I have a ScrollPane which contains a TilePane which contains various Buttons.

This application will run on a touch screen, so the user will scroll by dragging the finger through the ScrollPane, but if the finger starts dragging over a child Button, then the ScrollPane doesn't scroll. Due to the way touch UIs work today, this is unexpected behavior for the user.

I want the buttons to respond to clicks, but I want the drag events to be sent to the underlying ScrollPane instead (if it pans using the drag events).

I've set the setPannable() property of the ScrollPane to true, and I've tried to set handlers on the Button's various setOnDrag*() methods to then fire the ScrollPane's fireEvent() method, but nothing happened.

Example:

    button.setOnDragDone(new EventHandler<DragEvent>() {
        @Override
        public void handle(DragEvent event) {
            scrollPane.fireEvent(event);
        }
    });

How should I go about doing this?



Solution 1:[1]

The problem for buttons is that the ButtonBehavior registers a default mapping for MouseEvent.MOUSE_PRESSED. This mapping consumes the event and the ScrollPane does not receive it anymore for panning calculation. In previous JFX versions it was possible to simply call ButtonSkin.consumeMouseEvents(false) to fix that. With current versions this does not work anymore. I think this a bug and I might report it when I have time.

As a workaround I redirect the MouseEvent.MOUSE_PRESSED to the viewPort of the ScrollPane:

   ScrollPane pane = new ScrollPane(bar)
    {
      @Override
      protected Skin<?> createDefaultSkin()
      {
        Skin<?> skin = super.createDefaultSkin();
        for (Node ch : getChildren())
        {
          if (ch.getStyleClass().contains("viewport"))
            viewPort = ch;
        }
        return skin;
      }
    };
    pane.addEventFilter(MouseEvent.MOUSE_PRESSED, e ->
    {
      if (lastFiredEvent != e)
      {
        lastFiredEvent = e.copyFor(e.getSource(), viewPort);
        viewPort.fireEvent(lastFiredEvent);
      }
    });

Edit: I reported the bug. Will post the link here when it is avaiable.

https://bugs.openjdk.java.net/browse/JDK-8284878

Solution 2:[2]

try this, consume TouchEvent on the ScrollPane such as ...

ScrollPane.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
        @Override
        public void handle(TouchEvent t) { 
            t.consume();
        }
    });     

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Politic Revolutionnaire