javafx

JavaFX sizeToScene() not respecting the Stage's max values


I am encountering an issue when I am calling sizeToScene on a transparent window that has max sizes set.

Let's say, I set the prefWidth of StackPane to something bigger than the Stage's maxWidth. And when I show the Stage, it considers the maxWidth of Stage and adjust the StackPane width accordingly. But after that if I set the same prefWidth to the StackPane (or in fact any value greater than Stage's maxWidth) and call sizeToScene() the content stage renders in a different way.

In the below demo, I set the transparent stage max width to 200px. And the StackPane prefWidth to 300px. When the Stage is shown, the StackPane adjusts its width to 200px, which is fine.

When I click on label, if I call the sizeToScene, then the content width is updated as per prefSizes and does not respect the max values of the Stage.

enter image description here

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

public class SizeToSceneOnTransparentWindow_Demo extends Application {

    Stage stage;

    @Override
    public void start(Stage primaryStage) throws Exception {
        Button show = new Button("Show");
        show.setOnAction(e -> {
            if(stage==null) {
                stage = new Stage();
                stage.initStyle(StageStyle.TRANSPARENT);
                stage.setMaxWidth(200);

                Label label = new Label("Test");
                StackPane container = new StackPane(label);
                container.setStyle("-fx-background-color:#FF000050;-fx-border-width:2px;-fx-border-color:#888888;");
                container.setPrefSize(300, 300);

                label.setOnMouseClicked(e1 -> {
                    stage.sizeToScene();
                });

                StackPane root = new StackPane(container);
                stage.setScene(new Scene(root));
                stage.setX(200);
                stage.setY(200);
            }
            stage.show();
        });
        Button hide = new Button("Hide");
        hide.setOnAction(e -> stage.hide());

        Scene scene = new Scene(new HBox(10,show, hide), 200,200);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Is this a JavaFX bug or it is supposed to behave like that? Because what I observe is the sizeToScene() method is defined in Window class and the max sizes are defined in Stage class and it is quite obvious that sizeToScene() method will not consider the max values.

And also the documentation for sizeToScene is as below:

Set the width and height of this Window to match the size of the content of this Window's Scene.

This request might be ignored if the Window is not allowed to do so, for example a Stage may be maximized or in fullScreen and therefore does not allow this request. If that is the case, this request is remembered and reapplied later when allowed.

The documentation says about resizing of window, but in the above case that is not what happening. The scene or contents are resized.


Solution

  • That would seem to be a bug, you could file it if you like.

    You can work around it by setting the max dimensions of the scene root, for example, for this root:

    Node root = stage.getScene().getRoot();
    

    Set the value one time:

    root.setMaxWidth(stage.maxWidthProperty());
    

    OR, track the value:

    root.maxWidthProperty().bind(stage.maxWidthProperty());
    

    If you also wanted to account for scene changes in the stage or window max/min functions, then things would be more complicated.

    FAQ

    Because what I observe is the sizeToScene() method is defined in Window class and the max sizes are defined in Stage class and it is quite obvious that sizeToScene() method will not consider the max values.

    It is not as obvious as it appears, because the implementation of sizeToScene delegates to helper methods. Also, Stage (or your code in a Stage subclass) could override the sizeToScene method, although Stage doesn't do that in JavaFX 23.

    If you are curious, the code in Window for handling size changes is:

    /**
     * Indicates if a user requested the window to be sized to match the scene
     * size.
     */
    private boolean sizeToScene = false;
    /**
     * Set the width and height of this Window to match the size of the content
     * of this Window's Scene.
     */
    public void sizeToScene() {
        if (getScene() != null && peer != null) {
            SceneHelper.preferredSize(getScene());
            adjustSize(false);
        } else {
            // Remember the request to reapply it later if needed
            sizeToScene = true;
        }
    }
    
    private void adjustSize(boolean selfSizePriority) {
        if (getScene() == null) {
            return;
        }
        if (peer != null) {
            double sceneWidth = getScene().getWidth();
            double cw = (sceneWidth > 0) ? sceneWidth : -1;
            double w = -1;
            if (selfSizePriority && widthExplicit) {
                w = getWidth();
            } else if (cw <= 0) {
                w = widthExplicit ? getWidth() : -1;
            } else {
                widthExplicit = false;
            }
            double sceneHeight = getScene().getHeight();
            double ch = (sceneHeight > 0) ? sceneHeight : -1;
            double h = -1;
            if (selfSizePriority && heightExplicit) {
                h = getHeight();
            } else if (ch <= 0) {
                h = heightExplicit ? getHeight() : -1;
            } else {
                heightExplicit = false;
            }
    
            peerBoundsConfigurator.setSize(w, h, cw, ch);
            applyBounds();
        }
    }
    

    When the stage is shown or the scene is invalidated, then selfSizePriority passed to adjustSize is true, so it gives size priority to the window constraints, but when you size to scene selfSizePriority is false and it gives size priority to the scene constraints alone.