7

I'm having some difficulty with ScrollPane in JavaFX 8 showing the scrollbar as needed. What I'm currently doing is simply creating a FlowPane with x number of elements, and setting that as the content of the ScrollPane.

The problem happens when I shrink down perpendicular to the orientation of the FlowPane. When elements begin to wrap and go out of bounds, the scrollbar does not appear. This does not happen when I shrink parallel to the orientation. I have a small Java program to exemplify the issue.

Start

Shrinking Parallel

Shrinking Perpendicular

Shrinking PerpendicularExaggerated Shrinking Parallel Exaggerated

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

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

       @Override
       public void start(Stage primaryStage) throws Exception {
             FlowPane flow = new FlowPane();
             flow.setStyle("-fx-border-color: red");
             addPanes(flow, 16);

             ScrollPane scroll = new ScrollPane(flow);
             scroll.setStyle("-fx-border-color: green");
             scroll.setFitToHeight(true);
             scroll.setFitToWidth(true);

             Scene scene = new Scene(scroll, 450, 450);
             primaryStage.setScene(scene);
             primaryStage.show();
       }

       public void addPanes(FlowPane root, int panes) {
             for(int i = 0; i < panes; i++) {
                    StackPane filler = new StackPane();
                    filler.setStyle("-fx-border-color: black");
                    filler.setPrefSize(100, 100);
                    root.getChildren().add(filler);
             }
       }
}
Mekiah
  • 73
  • 1
  • 6
  • Interesting, It works correctly when the `Stage`'s width is shorter than the `FlowPane`'s initial width. Use 400 on the `Scene`'s width. – SedJ601 Nov 03 '17 at 13:58
  • 1
    It definitely some bizarre behavior. Unforunately even if you initialize it to 400x400, if you expand it back to about 450x450 and shrink perpendicular it will still have the issue. – Mekiah Nov 03 '17 at 15:27

3 Answers3

2

Have a look at the code below and tell me if that's what you want to achieve. I am still not sure what cause the problem, I will have to look the documentation of ScrollPane to find out. My suspicion is at setFitToWidth & setFitToHeight methods. Although I still believe it's not a bug.

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        FlowPane flow = new FlowPane();
        flow.setStyle("-fx-border-color: red");

        addPanes(flow, 16);

        ScrollPane scroll = new ScrollPane(flow);
        scroll.setStyle("-fx-border-color: green");

        // Apparently this cause the issue here.
        // scroll.setFitToHeight(true);
        // scroll.setFitToWidth(true);


        // Instead just make the flow pane take the dimensions of the ScrollPane
        // the -5 is to not show the Bars when both of panes have the same dimensions  
        flow.prefWidthProperty().bind(Bindings.add(-5, scroll.widthProperty()));
        flow.prefHeightProperty().bind(Bindings.add(-5, scroll.heightProperty()));

        Scene scene = new Scene(scroll, 450, 450);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    public void addPanes(FlowPane root, int panes) {
        for (int i = 0; i < panes; i++) {
            HBox filler = new HBox();
            filler.setStyle("-fx-border-color: black");
            filler.setPrefSize(100, 100);
            root.getChildren().add(filler);
        }
    }
}

Looking documentation of the ScrollPane, and in specific the setFitToHeight you will find that :

Property description: If true and if the contained node is a Resizable, then the node will be kept resized to match the height of the ScrollPane's viewport. If the contained node is not a Resizable, this value is ignored.

And because the node inside the ScrollPane will be kept resized to match the width and height of the ScrollPane's viewport thats why the Vertical ScrollBar will never appear.

JKostikiadis
  • 2,847
  • 2
  • 22
  • 34
  • The thing is I don't want a max size for the pane. I want the user to be able to resize the window and the flow pane will freely shift elements around as needed to any size, but when shrunk down too small I want scroll bars to appear, which is not happening in this specific case. – Mekiah Nov 03 '17 at 12:27
  • "when shrunk down too small I want scroll bars to appear" can you be more specific? how small? In that case just set the setMinWidth() only. For example if you want the horizontal bar to be appeared when the user shrink the pane more than 250 then set the `flow.setMinWidth(250);` and only that. This will not affect the maximum size but it will stop the shrinking when the user tries to reduce the pane width more than 250. – JKostikiadis Nov 03 '17 at 12:32
  • As for your initial solution, it forces the pane to always be 4 columns wide. The second solution still has the original issue. I can attach screenshot exaggerating what the initial screenshot intended to present if that will help you understand. They have been edited into the question. As you can see the vertical scrollbar does not show when you shrink in the direction of the screenshot. The application has the capability of making a window that will display a 1x16, or 16x1 of boxes if expanded as such, and I want to keep that instead of a work around that would otherwise remove that feature. – Mekiah Nov 03 '17 at 14:44
  • I think the new solution is the best work around. But you have to agree the way scroll bar appearance behavior is incorrect, right? Scollbar is meant to show when content width/height is higher than the ScrollPane's. I checked both objects in memory and that case holds true in both resizing scenarios. Definite bug, but thanks for the workaround. The only issue with this specific work around is the padding you get on your content pane, but that shouldn't impact what I plan. Thanks. – Mekiah Nov 03 '17 at 19:02
  • @Mekiah no is not wrong. In fact that's the reason the ScrollBar behave the way you saw. By setting the `setFitToWidth(true)` (or height ) the node will be kept resized to match the width of the ScrollPane's viewport and that's why the scrollBar will never appear. – JKostikiadis Nov 03 '17 at 19:16
  • That doesn't explain why the scrollbar _does_ appear when resizing parallel in the example screenshots. The node is not resized to match the height of the ScrollPane's viewport (see red border). The node height then becomes greater than the ScrollPane's height, thus scrollbar shows per correct behavior. In the perpendicular example, it is the same scenario, node height becomes greater than ScrollPane height, but the ScrollPane does not detect this event properly and doesn't show the scrollbar. – Mekiah Nov 03 '17 at 19:21
  • I believe something is happening by the minHeight of the flowPane inside the scrollpane and triggers the bar to show when you actually try to reduce the height less than that. In any case that's indeed very interesting and I would like to find the answer also. – JKostikiadis Nov 03 '17 at 19:44
2

You can add the code below to always show your vertical scrollbar.

scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
SedJ601
  • 12,173
  • 3
  • 41
  • 59
  • 1
    I appreciate the work around, but I already had this in mind if I couldn't find a real solution, or if it turned out to be a bug with JavaFX which I'm getting a feeling it is. – Mekiah Nov 03 '17 at 14:28
  • 1
    I am guessing it's a bug. – SedJ601 Nov 03 '17 at 14:30
0

When the required height of the FlowPane inside the ScrollPane is calculated a width value of -1 is passed. The flow pane will then report the height required when all its content fits into a single line.

As a workaround you could pass the width from the last layout calculation in this case.

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Main extends Application {

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

       @Override
       public void start(Stage primaryStage) throws Exception {
             FlowPane flow = new FlowPane() {
                @Override protected double computeMinHeight(double width) {
                    double minHeight = super.computeMinHeight(width != -1 ? width : 
                        /* When no width is specified, use the current contol size*/ 
                        getWidth());
                    return minHeight;
                }
             };
             flow.setStyle("-fx-border-color: red");
             addPanes(flow, 16);

             ScrollPane scroll = new ScrollPane(flow);
             flow.maxWidthProperty().bind(scroll.widthProperty());
             scroll.widthProperty().addListener((observable, oldValue, newValue)->{
                 /* clearSizeCache */
                 flow.requestLayout();
             });

             scroll.setStyle("-fx-border-color: green");
             scroll.setFitToHeight(true);
             scroll.setFitToWidth(true);

             Scene scene = new Scene(scroll, 450, 450);
             primaryStage.setScene(scene);
             primaryStage.show();
       }

       public void addPanes(FlowPane root, int panes) {
             for(int i = 0; i < panes; i++) {
                    StackPane filler = new StackPane();
                    filler.setStyle("-fx-border-color: black");
                    filler.setPrefSize(100, 100);
                    root.getChildren().add(filler);
             }
       }
}