javajavafxjavafx-9

Is specifying JavaFX CSS borders on GridPane contents supposed to collapse the sizing?


I was attempting to layout a JavaFX stage using a GridPane when I ran into the following problem. If I setup the grid with the appropriate constraints and add newly instantiated StackPanes to it, the default sizing of the scene, stage, and it's contents ensures that the contents are visible:

Without CSS

However, if I add a JavaFX CSS style specifying a border to the newly instantiated StackPane before adding it to the GridPane, then the default sizing of things seems to collapse complete:

With CSS

My code is as follows:

public static void main(final String[] args) {
    Platform.startup(() -> {});
    Platform.runLater(() -> {

        final GridPane gridPane = new GridPane();
        final Scene scene = new Scene(gridPane);
        final Stage stage = new Stage();
        stage.setScene(scene);

        final List<StackPane> panes = new ArrayList<>();
        for (int i = 0; i < 4; i++) {

            // Create a new pane with a random background color for
            // illustration
            final StackPane p = createNewPane();
            panes.add(p);

            // The addition / removal of the following line affects the
            // layout.
            p.setStyle("-fx-border-width:2px;-fx-border-color:red");
        }

        for (int r = 0; r < 2; r++) {
            final RowConstraints rc = new RowConstraints();
            rc.setPercentHeight(50);
            gridPane.getRowConstraints().add(rc);
        }
        for (int c = 0; c < 2; c++) {
            final ColumnConstraints cc = new ColumnConstraints();
            cc.setPercentWidth(50);
            gridPane.getColumnConstraints().add(cc);
        }

        for (int r = 0, i = 0; r < 2; r++) {
            for (int c = 0; c < 2; c++) {
                gridPane.add(panes.get(i++), c, r);
            }
        }
        stage.show();
    });
}

Curiously, if I move the stage.show() to right after I set the Scene, then everything works fine even with the CSS.

Can anyone help me understand, one, whether this is the expected behavior, and two, why the execution order of the stage.show() makes a difference?

Thanks!


Solution

  • What the issue is

    Your example is a bit ambiguous. You don't set the preferred size of anything added to the Stage at any time. So, the JavaFX platform can really do whatever it wants in terms of sizing things. Setting a preferred percent size is not the same as setting a preferred absolute size. A percent size is relative, so the question becomes, relative to what? and the answer to that is unclear.

    As to why this occurs:

    // The addition / removal of the following line affects the
    // layout.
    p.setStyle("-fx-border-width:2px;-fx-border-color:red");
    

    I couldn't say. My guess is that the use of CSS is triggering some additional layout logic which effects the resizing in the absence of any size hints.

    How to fix it

    Anyway, the solution is just to make things more clear and specify preferred sizing for at least something in the application, then the application will initially be sized to that preferred sizing.

    Here is an example:

    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.scene.layout.*;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    public class Starter {
        public static void main(String[] args) {
            Platform.startup(() -> {});
            Platform.runLater(() -> {
                final GridPane gridPane = new GridPane();
                final Scene scene = new Scene(gridPane);
                final Stage stage = new Stage();
                stage.setScene(scene);
    
                final List<StackPane> panes = new ArrayList<>();
                for (int i = 0; i < 4; i++) {
    
                    // Create a new pane with a random background color for
                    // illustration
                    final StackPane p = createNewPane();
                    panes.add(p);
    
                    // The addition / removal of the following line affects the
                    // layout.
                    p.setStyle("-fx-border-width:2px;-fx-border-color:red");
                }
    
                for (int r = 0; r < 2; r++) {
                    final RowConstraints rc = new RowConstraints();
                    rc.setPercentHeight(50);
                    gridPane.getRowConstraints().add(rc);
                }
                for (int c = 0; c < 2; c++) {
                    final ColumnConstraints cc = new ColumnConstraints();
                    cc.setPercentWidth(50);
                    gridPane.getColumnConstraints().add(cc);
                }
    
                for (int r = 0, i = 0; r < 2; r++) {
                    for (int c = 0; c < 2; c++) {
                        gridPane.add(panes.get(i++), c, r);
                    }
                }
                stage.show();
            });
        }
    
        private static final Random random = new Random(42);
    
        private static StackPane createNewPane() {
            StackPane pane = new StackPane();
    
            pane.setBackground(
                    new Background(
                            new BackgroundFill(
                                    randomColor(), null, null
                            )
                    )
            );
    
            pane.setPrefSize(150, 100);
    
            return pane;
        }
    
        private static Color randomColor() {
            return Color.rgb(
                    random.nextInt(256),
                    random.nextInt(256),
                    random.nextInt(256)
            );
        }
    }
    

    The key part of the solution is the call:

    pane.setPrefSize(150, 100);
    

    which sets the preferred size for the stack panes which have been placed in your layout.

    Alternatively, rather than doing the bottom up preferred sizing by setting a preferred size on each of the StackPanes, you could also accomplish a similar thing from a top-down perspective by setting appropriate constraints on the GridPane instead, for example:

     gridPane.setPrefSize(300, 200);
    

    Note

    I'd advise using a JavaFX Application class instead of Platform.startup() call unless there is a really good reason to use the latter (which there is in this case - interfacing with Swing, as you have noted in your comment).