Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I prevent a window from being too small in JavaFX?

I have this very simple form and I set the min width and height of all UI controls to be USE_PREF_SIZE, so, they cannot get too small:

enter image description here

The code looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<AnchorPane minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <children>
      <GridPane hgap="10.0" layoutX="64.0" layoutY="122.0" vgap="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <columnConstraints>
          <ColumnConstraints halignment="RIGHT" hgrow="NEVER" />
          <ColumnConstraints hgrow="SOMETIMES" />
        </columnConstraints>
        <rowConstraints>
          <RowConstraints vgrow="NEVER" />
          <RowConstraints vgrow="NEVER" />
          <RowConstraints vgrow="NEVER" />
            <RowConstraints valignment="TOP" vgrow="SOMETIMES" />
        </rowConstraints>
         <children>
            <Label minHeight="-Infinity" minWidth="-Infinity" text="Log in" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" />
            <Label minHeight="-Infinity" minWidth="-Infinity" text="Email address:" GridPane.rowIndex="1" />
            <Label minHeight="-Infinity" minWidth="-Infinity" text="Password:" GridPane.rowIndex="2" />
            <Button minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" text="Log in" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="3" />
            <PasswordField minHeight="-Infinity" minWidth="-Infinity" GridPane.columnIndex="1" GridPane.rowIndex="2" />
            <TextField minHeight="-Infinity" minWidth="-Infinity" GridPane.columnIndex="1" GridPane.rowIndex="1" />
         </children>
         <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </padding>
      </GridPane>
   </children>
</AnchorPane>

My problem is that the window can still get too small:

enter image description here

How do I prevent that from happening?

I could of course set a fixed min width and height, that's trivial:

stage.setMinWidth(280);
stage.setMinHeight(180);

but that requires remembering to update it every time the UI changes and it wouldn't be sufficient in other languages that have longer strings, such as Spanish:

enter image description here

Since it seems this cannot be achieved with FXML, I'm trying with code. The basic code I'm starting with is the usual FXML loading method:

public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("/simple_form.fxml"));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
}

The FXML loaded object, root, has a prefWidth and prefHeight property that look promising. I added this debugging prints:

public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("/simple_form.fxml"));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    System.out.println("Preferred width before showing: " + root.prefWidth(-1));
    System.out.println("Preferred height before showing: " + root.prefHeight(-1));
    stage.show();
    System.out.println("Preferred width after showing: " + root.prefWidth(-1));
    System.out.println("Preferred height after showing: " + root.prefHeight(-1));
    System.out.println("Actual width: " + scene.getWidth());
    System.out.println("Actual height: " + scene.getHeight());
}

and the result is:

Preferred width before showing: 30.0
Preferred height before showing: 50.0
Preferred width after showing: 255.0
Preferred height after showing: 142.0
Actual width: 255.0
Actual height: 142.0

Since the initial size is the preferred size, the one I want as a minimum for this UI, a sensible person would have expected to have the answer (you just need to show() the stage first):

public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("/simple_form.fxml"));
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
    stage.setMinWidth(root.prefWidth(-1));
    stage.setMinHeight(root.prefHeight(-1));
}

But the result of this code is that I can still make the Window smaller, but only a little bit:

enter image description here

I'm utterly confused by that result. I tried removing the paddings and gaps from the fxml, but the odd behavior remained.

like image 729
pupeno Avatar asked Aug 27 '17 12:08

pupeno


People also ask

How do I stop windows from resizing JavaFX?

You can do it with stage. setResizable(false); You can also remove window buttons with stage. initStyle(StageStyle. UNDECORATED);

How do I increase the size of the GridPane in JavaFX?

getColumnConstraints(). add(new ColumnConstraints(200)); By default the GridPane will resize rows/columns to their preferred sizes even if the gridpane is resized larger than its preferred size. To support dynamic column/row sizes, both contstaints class provides three property: min size, max size and preferred size.


Video Answer


1 Answers

Calling Stage.sizeToScene() and then setting the minimum width and height to the current width and height after showing the stage works for me:

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Login extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Scene scene = new Scene(FXMLLoader.load(getClass().getResource("LoginForm.fxml")));
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();
        primaryStage.show();
        primaryStage.setMinWidth(primaryStage.getWidth());
        primaryStage.setMinHeight(primaryStage.getHeight());
    }

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

sizeToScene() is redundant here, as this is the default behavior in this scenario, though you might need this if you replace the stage's scene at a later time (and making things explicit is never a bad thing).

If you want to explicitly set the size of the stage to something other than the size that allows the scene to be its preferred size, but still enforce the same minimum size requirements, it gets a bit trickier. After showing the stage, you can calculate the difference between the size of the stage and the size of the root of the scene (essentially the space taken up by the window decorations), and compute the preferred size of the root, and then use those to compute the minimum size of the stage. This looks like

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Login extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Scene scene = new Scene(FXMLLoader.load(getClass().getResource("LoginForm.fxml")));


        primaryStage.setScene(scene);

        primaryStage.setWidth(400);
        primaryStage.setHeight(280);

        primaryStage.show();


        Node root = scene.getRoot();
        Bounds rootBounds = root.getBoundsInLocal();
        double deltaW = primaryStage.getWidth() - rootBounds.getWidth();
        double deltaH = primaryStage.getHeight() - rootBounds.getHeight();

        Bounds prefBounds = getPrefBounds(root);

        primaryStage.setMinWidth(prefBounds.getWidth() + deltaW);
        primaryStage.setMinHeight(prefBounds.getHeight() + deltaH);
    }

    private Bounds getPrefBounds(Node node) {
        double prefWidth ;
        double prefHeight ;

        Orientation bias = node.getContentBias();
        if (bias == Orientation.HORIZONTAL) {
            prefWidth = node.prefWidth(-1);
            prefHeight = node.prefHeight(prefWidth);
        } else if (bias == Orientation.VERTICAL) {
            prefHeight = node.prefHeight(-1);
            prefWidth = node.prefWidth(prefHeight);
        } else {
            prefWidth = node.prefWidth(-1);
            prefHeight = node.prefHeight(-1);
        }
        return new BoundingBox(0, 0, prefWidth, prefHeight);
    }

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

I haven't tested this on all platforms.

like image 84
James_D Avatar answered Sep 28 '22 14:09

James_D