GridPane takes much more space than required

Viewed 70

I have a GridPane setup:

        GridPane pane = new GridPane();
        // 3 columns, which percent widths are 40%, 27% and 33%, respectively
        setColumnPercents(pane, 40, 27, 33);
        // 4 rows, each percent height is 25%
        setRowPercents(pane, 25, 25, 25, 25);
        pane.setHgap(10);
        pane.setVgap(10);
        // getView() returns a StackPane
        // The 4 arguments of getView() specifies the stack pane's
        // prefWidth, prefHeight, minWidth and minHeight
        pane.add(setGridGrow(getView(196, 112, 106, 112)), 0, 0, 1, 4);
        pane.add(setGridGrow(getView(131, 104, 57, 104)), 1, 0, 1, 3);
        pane.add(setGridGrow(getView(93, 26, 42, 26)), 1, 3);
        pane.add(setGridGrow(getView(185, 72, 74, 72)), 2, 0, 1, 2);
        pane.add(setGridGrow(getView(140, 76, 55, 76)), 2, 2, 1, 2);
        return pane;

In this example, the first column contains 1 node spanning 4 rows, its prefHeight and minHeight both set to 112, the grid height required to contain this node is (112 - (3 * 10)) / 4 = 27.3, the second column contains 2 nodes, the node above spans 3 rows, another grid height can be calculated based on its height: (104 - 2 * 10) / 3 = 28, the node below does not span, so its respective grid height is 26, for the two nodes in the third column, each spans 2 rows, 2 more grid height can be calculated: (72 - 10) / 2 = 31 and (76 - 10) / 2 = 33. Putting these together, a minimal grid height 33 is big enough for the gridpane to contain all its children, so the minimal total height of the pane is 33 * 4 + 3 * 10 = 162.

However, when I run this code, the total height of the gridpane turns up to be 298, way bigger than the expected minimal height.

enter image description here

What's more, if the row span of the first node is changed to 3, the result gridpane height (250) is smaller, which seems unreasonable to me because the size of the node does not change, while it is required to be fit into a smaller portion of the gridpane.

//      pane.add(setGridGrow(getView(196, 112, 106, 112)), 0, 0, 1, 4);
        pane.add(setGridGrow(getView(196, 112, 106, 112)), 0, 0, 1, 3);

enter image description here

What I expect is that the height of the gridpane does not exceeds the minimal height required for all it's children too much, and if the row span of the first node is changed from 4 to 3, the result gridpane height being bigger or unchanged, not smaller. Otherwise my UI layout would break unpredictably. Is there any way I can achieve these?

The full code is pasted below. I have ran it on JavaFX 8, 11 (JDK=11) and 16 (JDK=11), Windows 10, got consistent results.

package com.example.javafxlayoutdemo;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.stage.Stage;

import java.util.Objects;

public class GridPaneLayoutDemo extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(getRoot()));
        primaryStage.show();
    }

    private GridPane getRoot() {
        GridPane pane = new GridPane();
        // 3 columns, which percent widths are 40%, 27% and 33%, respectively
        setColumnPercents(pane, 40, 27, 33);
        // 4 rows, each percent height is 25%
        setRowPercents(pane, 25, 25, 25, 25);
        pane.setHgap(10);
        pane.setVgap(10);
        // getView() returns a StackPane
        // The 4 arguments of getView() specifies the stack pane's
        // prefWidth, prefHeight, minWidth and minHeight
        pane.add(setGridGrow(getView(196, 112, 106, 112)), 0, 0, 1, 4);
        pane.add(setGridGrow(getView(131, 104, 57, 104)), 1, 0, 1, 3);
        pane.add(setGridGrow(getView(93, 26, 42, 26)), 1, 3);
        pane.add(setGridGrow(getView(185, 72, 74, 72)), 2, 0, 1, 2);
        pane.add(setGridGrow(getView(140, 76, 55, 76)), 2, 2, 1, 2);
        return pane;
    }

    private static Region getView(double prefWidth, double prefHeight, double minWidth, double minHeight) {
        Label sizeLabel = new Label();
        StackPane view = new StackPane(sizeLabel);
        sizeLabel.textProperty().bind(Bindings.createStringBinding(
                () -> getDimensionString(view),
                view.widthProperty(),
                view.heightProperty()
        ));
        setMetrics(view, prefWidth, prefHeight, minWidth, minHeight);
        view.setStyle("-fx-background-color: #ddd");
        return view;
    }

    private static String getDimensionString(Region view) {
        return String.format("%.1f x %.1f", view.getWidth(), view.getHeight());
    }

    private static void setMetrics(Region view, double prefWidth, double prefHeight, double minWidth, double minHeight) {
        view.setPrefSize(prefWidth, prefHeight);
        view.setMinSize(minWidth, minHeight);
    }

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

    public static Node setGridGrow(Node node) {
        Objects.requireNonNull(node);
        GridPane.setHgrow(node, Priority.ALWAYS);
        GridPane.setVgrow(node, Priority.ALWAYS);
        return node;
    }

    public static RowConstraints getPercentRowConstraints(double percent) {
        RowConstraints result = new RowConstraints();
        result.setPercentHeight(percent);
        return result;
    }

    public static void setRowPercents(GridPane pane, double... percents) {
        pane.getRowConstraints().clear();
        for (double percent : percents) {
            pane.getRowConstraints().add(getPercentRowConstraints(percent));
        }
    }

    public static ColumnConstraints getPercentColumnConstraints(double percent) {
        ColumnConstraints result = new ColumnConstraints();
        result.setPercentWidth(percent);
        return result;
    }

    public static void setColumnPercents(GridPane pane, double... percents) {
        pane.getColumnConstraints().clear();
        for (double percent : percents) {
            pane.getColumnConstraints().add(getPercentColumnConstraints(percent));
        }
    }
}


Update

As a workaround, I wrapped the second column and the third column into separate gridpanes and got what I expected, roughly.

enter image description here

        GridPane pane = new GridPane();
        setColumnPercents(pane, 40, 27, 33);
        pane.setHgap(10);
        pane.heightProperty().addListener(observable -> System.out.println(getDimensionString(pane)));
        pane.widthProperty().addListener(observable -> System.out.println(getDimensionString(pane)));
        pane.add(setGridGrow(getView(196, 112, 106, 112)), 0, 0);

        GridPane secondColumn = new GridPane();
        secondColumn.setVgap(10);
        setRowPercents(secondColumn, 25, 25, 25, 25);
        secondColumn.add(setGridGrow(getView(131, 104, 57, 104)), 0, 0, 1, 3);
        secondColumn.add(setGridGrow(getView(93, 26, 42, 26)), 0, 3);
        pane.add(setGridGrow(secondColumn), 1, 0);

        GridPane thirdColumn = new GridPane();
        setRowPercents(thirdColumn, 25, 25, 25, 25);
        thirdColumn.setVgap(10);
        thirdColumn.add(setGridGrow(getView(185, 72, 74, 72)), 0, 0, 1, 2);
        thirdColumn.add(setGridGrow(getView(140, 76, 55, 76)), 0, 2, 1, 2);
        pane.add(setGridGrow(thirdColumn), 2, 0);

However, this problem is not considered solved with a clean method and a clear mechanism.

0 Answers
Related