如何防止 JavaFX 中的窗口太小?

Posted

技术标签:

【中文标题】如何防止 JavaFX 中的窗口太小?【英文标题】:How can I prevent a window from being too small in JavaFX? 【发布时间】:2018-02-04 21:36:18 【问题描述】:

我有这个非常简单的表单,我将所有 UI 控件的最小宽度和高度设置为 USE_PREF_SIZE,所以它们不能太小:

代码如下所示:

<?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>

我的问题是窗口仍然会变得太小:

如何防止这种情况发生?

我当然可以设置一个固定的最小宽度和高度,这很简单:

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

但这需要记住每次 UI 更改时都更新它,并且在其他具有较长字符串的语言(例如西班牙语)中是不够的:

由于 FXML 似乎无法实现这一点,我正在尝试使用代码。我开始使用的基本代码是通常的 FXML 加载方法:

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();

FXML 加载的对象 root 具有看起来很有希望的 prefWidth 和 prefHeight 属性。我添加了这个调试打印:

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());

结果是:

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

由于初始尺寸是首选尺寸,我希望作为此 UI 的最低尺寸,明智的人会期望得到答案(您只需先show() 舞台):

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));

但是这段代码的结果是我仍然可以让Window变小,但只能一点点:

我完全被这个结果弄糊涂了。我尝试从 fxml 中删除填充和间隙,但奇怪的行为仍然存在。

【问题讨论】:

是否手动调整大小?然后我会说禁用手动调整大小。 是的,我手动将其调整为小尺寸。我不想禁用手动调整大小,因为有人可能想让它更大。我只是不希望他们将其缩小并隐藏其中的一部分。 如何在代码中处理该属性,因为该属性仅适用于创建?比如保存窗口大小并制作一个调整大小的监听器,它不允许调整大小小于保存的尺寸? 设置最小高度和宽度。 【参考方案1】:

在James_D's answer 的基础上,我编写了这两个函数。在我的应用程序中,我在 Stage.show() 之后调用 applyRootSizeContraints:

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);

另外请注意,我使用了Node.getLayoutBounds 而不是Node.getBoundsInLocal。

【讨论】:

【参考方案2】:

调用Stage.sizeToScene(),然后在显示舞台后将最小宽度和高度设置为当前宽度和高度对我有用:

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() 在这里是多余的,因为这是此场景中的默认行为,但如果您稍后替换舞台的场景,您可能需要它(并且明确化从来都不是一件坏事)。

如果您想将舞台的大小显式设置为允许场景为其首选大小的大小以外的大小,但仍强制执行相同的最小大小要求,这会有点棘手。展示完舞台后,你可以计算舞台大小与场景根部大小(本质上是窗饰占用的空间)之间的差异,并计算出根部的首选大小,然后使用那些计算舞台的最小尺寸。这看起来像

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);
    

我没有在所有平台上对此进行测试。

【讨论】:

感谢您的回答。我还没有实现它,但我希望窗口记住它的最后大小。看起来 sizeToScene 会阻止它正常工作,对吧? 我不确定我理解你的意思。 @Pablo 我想我明白你的意思。我用另一种(更复杂的)方法更新了答案,该方法应该满足该要求。 我只是说他的问题/要求会随着每个新答案而变化。 @SedrickJefferson 我的意思是我不明白 Pablo 的评论。我想我现在明白了。【参考方案3】:

您应该通过以下方式阻止窗口调整大小:

primaryStage.setResizable(false);

【讨论】:

他的要求是允许用户把它变大,你的解决方案禁止这样做。 我现在看到了问题。所以我认为的解决方案是创建一个帧侦听器并在它低于某个限制时阻止调整大小。 frame.addComponentListener(new ComponentListener() public void componentResized(ComponentEvent e) Rectangle r = frame.getBounds(); h = r.height; w = r.width; if (h &lt; 400 || w &lt; 400) frame.setResizable(false); ); 那么问题是如何以编程方式找到这些限制。 通过更改框架的大小来测试您的 GUI,直到找到框架的限制。我建议了一个特定的限制,即 400 x 400。

以上是关于如何防止 JavaFX 中的窗口太小?的主要内容,如果未能解决你的问题,请参考以下文章

如何防止在 JavaFX 中按下 SPACE 键时关闭 AutoCompleteCombobox 弹出菜单

防止JavaFX TabPane在滑动时切换选项卡

如何防止水晶报表中的数据库登录窗口?

在 JavaFX 8 中防止中间绑定被垃圾收集的推荐方法是啥

Javafx:GridPane:当文本太长时防止列增加宽度

调用stage.close()后如何防止代码执行?