Search code examples
javajavafxjavafx-8scrollpane

How can I center a ScrollPane that wraps a fixed-sized Canvas in a BorderPane?


In a JavaFX application I need to center a fixed-sized Canvas wrapped by a StackPane, then a ScrollPane, within a BorderPane. The problem is that when I include the ScrollPane, the component tree is no longer centered in the middle of the center area of the BorderPane. One solution I tried is to fix the max size of the ScrollPane. This works (the components become centered), but creates a different problem: when the viewport area is insufficient in any one dimension (say, width), both scrollbars will appear anyways. Because ScrollPane's size is bounded, the extra space occupied by the scrollbar in one dimension will cause the viewport to shrink in the other dimension and the max size property, required for centering, will prevent allowing more space for it.

Here is a working demonstration of the problem:

public final class Minimal extends Application {

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

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(200,200) {
            @Override
            public boolean isResizable() {
                return false;
            }
        };

        StackPane pane = new StackPane(canvas);
        pane.setStyle("-fx-border-insets: 4; -fx-border-style: solid;");
        ScrollPane scroll = new ScrollPane(pane);
        // scroll.setMaxSize(212, 212); // Imperfect Solution
        BorderPane root = new BorderPane();
        root.setCenter(scroll);
        stage.setScene(new Scene(root));
        stage.show();
    }   
}

With the line commented out, when resized, the frame shows up as:

enter image description here

If I fix the max size: then both scroll bars show up at once (notice how the vertical one is not necessary:

enter image description here

The closest I found was this question, which does not really have an answer. Unfortunately, this solution, which seems to work well in Swing, does not appear to do anything since I'm already using a wrapper (the StackPane), and it's not helping. I'm hoping there's a solution that exclusively relies on layouts, without having to revert to writing listeners for resize events or binding to size properties, etc.


Solution

  • Let the StackPane fill the entire ScrollPane. The StackPane will then center the canvas for you:

    The stackpane will attempt to resize each child to fill its content area. If the child could not be sized to fill the stackpane (either because it was not resizable or its max size prevented it) then it will be aligned within the area using the alignment property, which defaults to Pos.CENTER.

    Thus, all you need to do is set the ScrollPane’s ‘fitTo…’ properties, so the StackPane will fill it:

    scroll.setFitToWidth(true);
    scroll.setFitToHeight(true);