简体   繁体   中英

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:

在此输入图像描述

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:

在此输入图像描述

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:

在此输入图像描述

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:

在此输入图像描述

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

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.

Building on James_D's answer , I wrote these two functions. In my application I call applyRootSizeContraints after Stage.show():

private static Bounds getBounds(final Node node,
                                final BiFunction<Node, Double, Double> widthFunction,
                                final BiFunction<Node, Double, Double> heightFunction) {
    final Orientation bias = node.getContentBias();
    double prefWidth;
    double prefHeight;
    if (bias == Orientation.HORIZONTAL) {
        prefWidth = widthFunction.apply(node, (double) -1);
        prefHeight = heightFunction.apply(node, prefWidth);
    } else if (bias == Orientation.VERTICAL) {
        prefHeight = heightFunction.apply(node, (double) -1);
        prefWidth = widthFunction.apply(node, prefHeight);
    } else {
        prefWidth = widthFunction.apply(node, (double) -1);
        prefHeight = heightFunction.apply(node, (double) -1);
    }
    return new BoundingBox(0, 0, prefWidth, prefHeight);
}

private static void applyRootSizeConstraints(final Stage stage) {
    final Parent root = stage.getScene().getRoot();
    stage.sizeToScene();
    final double deltaWidth = stage.getWidth() - root.getLayoutBounds().getWidth();
    final double deltaHeight = stage.getHeight() - root.getLayoutBounds().getHeight();
    final Bounds minBounds = getBounds(root, Node::minWidth, Node::minHeight);
    stage.setMinWidth(minBounds.getWidth() + deltaWidth);
    stage.setMinHeight(minBounds.getHeight() + deltaHeight);
    final Bounds prefBounds = getBounds(root, Node::prefWidth, Node::prefHeight);
    stage.setWidth(prefBounds.getWidth() + deltaWidth);
    stage.setHeight(prefBounds.getHeight() + deltaHeight);
    final Bounds maxBounds = getBounds(root, Node::maxWidth, Node::maxHeight);
    stage.setMaxWidth(maxBounds.getWidth() + deltaWidth);
    stage.setMaxHeight(maxBounds.getHeight() + deltaHeight);
}

Also note, that I used Node.getLayoutBounds instead of Node.getBoundsInLocal .

您应该通过编写来阻止窗口调整大小:

primaryStage.setResizable(false);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM