JavaFX ScrollPane - Detect when scrollbar is visible?

708 Views Asked by At

I have a ScrollPane as below:

    ScrollPane scroller = new ScrollPane();
    scroller.getStyleClass().add("scroller");
    scroller.setPrefWidth(width);
    scroller.setFocusTraversable(Boolean.FALSE);
    scroller.setPannable(Boolean.TRUE);
    scroller.setFitToWidth(Boolean.TRUE);
    scroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
    scroller.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
    this.setCenter(scroller);

    scroller.contentProperty().addListener((observableValue, last, now) ->
    {
        ScrollBar scrollBar = (ScrollBar) scroller.lookup(".scroll-bar:vertical");

        if (scrollBar != null)
        {
            if (scrollBar.isVisible())
            {
                log.info("Scrollbar visible, setting lower card width..");
            }
            else
            {
                log.info("Scrollbar not visible, setting default card width..");
            }
        }
    });

As you can see I've attached a listener to the content property to know when the content is set. I am trying to see if the scrollbar is visible when the content is updated. Even though I can see the scroll bar on the UI, it always goes to else part - "Scrollbar not visible".

Not sure if there is any other way to do this? Checked a lot on StackOverflow and Oracle docs - nothing solid found to suggest otherwise.

-- Adding context to the problem to better understand:

Just trying to explain what the problem is not sure if I should put it as a reply comment or edit the question, please advise and will change it:

So I have this view that brings up records from Firebase that need to be loaded on the TilePane that is hosted in ScrollPane which goes into the Center of the BorderPane.

The time by which I get the response from the Firebase is unpredictable as its async. So the UI gets loaded up with the empty TilePane and then the async call goes to fetch data. When the data is available, I need to prepare Cards (which is HBox) but the number of columns is fixed. So have to adjust the width of the cards to keep the gap (16px) and padding (16px) consistent on the TilePane at the same time maintain 5 columns. The width of each card needs to be recalculated based on the fact that whether or not there is a scrollbar on the display. Because if the scrollbar is displayed it takes some space and the TilePane will down it to 4 columns leaving a lot of empty space. Happy to explain further if this is not clear.

2

There are 2 best solutions below

0
arun On BEST ANSWER

As @James_D said, used GridPane and it worked without any listeners:

    GridPane cards = new GridPane();
    cards.setVgap(16);
    cards.setHgap(16);

    cards.setAlignment(Pos.CENTER);
    cards.setPadding(new Insets(16));

    ColumnConstraints constraints = new ColumnConstraints();
    constraints.setPercentWidth(20);
    constraints.setHgrow(Priority.ALWAYS);
    constraints.setFillWidth(Boolean.TRUE);

    cards.getColumnConstraints().addAll(constraints, constraints, constraints, constraints, constraints);

I have 5 columns, so 5 times constraints. Worked just fine.

3
Sai Dandem On

I strongly suggest to follow the suggestions given in the comments. It is all about choosing the correct layout.

The purpose of me answering this question is, in future, if someone comes across this question for dealing with scroll bar visibility, they will atleast know a way to get that (in JavaFX 8).

One way to check for the scrollbar visiblity is to register the appropriate scrollbar on layoutChildren and add a listener to its visilble property. Something like...

ScrollPane scrollPane = new ScrollPane() {
    ScrollBar vertical;

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        if (vertical == null) {
            vertical = (ScrollBar) lookup(".scroll-bar:vertical");
            vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
            updateContent(vertical.isVisible());
        }
    }
};

The updateContent(visible) method is stuff you want to do when the visibility gets updated.

A complete working demo is as below.

enter image description here

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ScrollPaneScrollBarVisibility_Demo extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        BorderPane borderPane = new BorderPane();
        Scene sc = new Scene(borderPane, 300, 300);
        stage.setScene(sc);
        stage.setTitle("ScrollBar visibility");
        stage.show();

        ScrollPane scrollPane = new ScrollPane() {
            ScrollBar vertical;

            @Override
            protected void layoutChildren() {
                super.layoutChildren();
                if (vertical == null) {
                    vertical = (ScrollBar) lookup(".scroll-bar:vertical");
                    vertical.visibleProperty().addListener((obs, old, val) -> updateContent(val));
                    updateContent(vertical.isVisible());
                }
            }
        };
        scrollPane.setContent(getContent());
        borderPane.setCenter(scrollPane);
    }

    private void updateContent(boolean scrollBarVisible) {
        System.out.println("Vertical scroll bar visible :: " + scrollBarVisible);
    }

    private VBox getContent() {
        VBox labels = new VBox();
        labels.setSpacing(5);
        for (int i = 0; i < 10; i++) {
            labels.getChildren().add(new Label("X " + i));
        }
        Button add = new Button("Add");
        add.setOnAction(e -> labels.getChildren().add(new Label("Text")));
        Button remove = new Button("Remove");
        remove.setOnAction(e -> {
            if (!labels.getChildren().isEmpty()) {
                labels.getChildren().remove(labels.getChildren().size() - 1);
            }
        });
        HBox buttons = new HBox(add, remove);
        buttons.setSpacing(15);

        VBox content = new VBox(buttons, labels);
        content.setPadding(new Insets(15));
        content.setSpacing(15);
        return content;
    }

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